Chapter 4. Application Configuration
Chapter 4. Application Configuration 4.1. Configure Web Applications to Use Elytron or Legacy Security for Authentication After you have configured the elytron or legacy security subsystem for authentication, you need to configure your application to use it. Configure your application's web.xml . Your application's web.xml needs to be configured to use the appropriate authentication method. When using the elytron subsystem, this is defined in the http-authentication-factory you created. When using the legacy security subsystem, this depends on your login module and the type of authentication you want to configure. Example web.xml with BASIC Authentication <web-app> <security-constraint> <web-resource-collection> <web-resource-name>secure</web-resource-name> <url-pattern>/secure/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>Admin</role-name> </auth-constraint> </security-constraint> <security-role> <description>The role that is required to log in to /secure/*</description> <role-name>Admin</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> <realm-name>exampleApplicationDomain</realm-name> </login-config> </web-app> Configure your application to use a security domain. You can configure your application's jboss-web.xml to specify the security domain you want to use for authentication. When using the elytron subsystem, this is defined when you created the application-security-domain . When using the legacy security subsystem, this is the name of the legacy security domain. Example jboss-web.xml <jboss-web> <security-domain>exampleApplicationDomain</security-domain> </jboss-web> Using jboss-web.xml allows you to configure the security domain for a single application only. Alternatively, you can specify a default security domain for all applications using the undertow subsystem. This allows you to omit using jboss-web.xml to configure a security domain for an individual application. Important Setting default-security-domain in the undertow subsystem will apply to ALL applications. If default-security-domain is set and an application specifies a security domain in a jboss-web.xml file, the configuration in jboss-web.xml will override the default-security-domain in the undertow subsystem. Note The security domain for Jakarta Enterprise Beans is defined in the Jakarta Enterprise Beans configuration, either in the ejb3 subsystem, the descriptor for Jakarta Enterprise Beans in the jboss-ejb3.xml file, or by using the @SecurityDomain annotation. For more information, see Jakarta Enterprise Beans Application Security in the Developing Jakarta Enterprise Beans Applications guide. Silent BASIC Authentication You can configure elytron to perform a silent BASIC authentication. When the silent authentication is enabled, a user is not prompted to log in for accessing the web application. An alternative authentication mechanism is used instead. If the user's request contains an Authorization header, then the BASIC authentication mechanism is used. To enable the silent BASIC authentication, set the value of auth-method attribute as the following: Using Elytron and Legacy Security Subsystems in Parallel You can define authentication in both the elytron and legacy security subsystems and use them in parallel. If you use both jboss-web.xml and default-security-domain in the undertow subsystem, JBoss EAP will first try to match the configured security domain in the elytron subsystem. If a match is not found, then JBoss EAP will attempt to match the security domain with one configured in the legacy security subsystem. If the elytron and legacy security subsystem each have a security domain with the same name, the elytron security domain is used. Note If you have a web servlet defined using one security domain and you are calling Jakarta Enterprise Beans from another EAR module, which uses Jakarta Enterprise Beans specific security domain, one of the following might happen: If the WAR and the Jakarta Enterprise Beans are mapped to different Elytron security domains, you need to configure the outflow or the trusted security domains so that their identities propagate from one deployment domain to the one. Unless this is done, once the call reaches the Jakarta Enterprise Beans, the identity becomes anonymous. For more information on how to configure security identities for authentication, see Configuring Trusted Security Domain Outflows . If the WAR and the Jakarta Enterprise Beans references different security domain names but they are mapped to the same Elytron security domain, their identity will propagate without requiring any additional steps. When migrating, it is best to migrate the entire application. Migrating the Jakarta Enterprise Beans and WAR separately and using both elytron and legacy security subsystems in parallel is not suggested. For more information on how to migrate your application to use Elytron, see Migrating to Elytron in the JBoss EAP Migration Guide. 4.2. Configure Client Authentication with Elytron Client Clients connecting to JBoss EAP, such as Jakarta Enterprise Beans, can authenticate using Elytron Client. Elytron Client is a client-side framework that enables remote clients to authenticate using Elytron. Elytron Client has the following components: Authentication Configuration The authentication configuration contains authentication information such as usernames, passwords, allowed SASL mechanisms, as well as which security realm to use during digest authentication. The connection information specified in the authentication configuration overrides any values that are specified in the PROVIDER_URL of the initial context. MatchRule A rule used for deciding which authentication configuration to use. Authentication Context A set of rules and authentication configurations to use with a client for establishing a connection. When a connection is established, the client makes use of an authentication context. This authentication context contains rules to choose which authentication configuration to use for each outbound connection. For example, you could have rules that use one authentication configuration when connecting to server1 and another authentication configuration when connecting with server2 . The authentication context is comprised of a set of authentication configurations and a set of rules that define how they are selected when establishing a connection. An authentication context can also reference ssl-context and can be matched with rules. To create a client that uses security information when establishing a connection: Create one or more authentication configurations. Create an authentication context by creating rule and authentication configuration pairs. Create a runnable for establishing your connection. Use your authentication context to run your runnable. When you establish your connection, Elytron Client will use the set of rules provided by the authentication context to match the correct authentication configuration to use during authentication. You can use one of the following approaches to use security information when establishing a client connection. Important When using Elytron Client to make Jakarta Enterprise Beans calls, any hard-coded programmatic authentication information, such as setting Context.SECURITY_PRINCIPAL in the javax.naming.InitialContext , will override the Elytron Client configuration. 4.2.1. The Configuration File Approach The configuration file approach involves creating an XML file with your authentication configuration, authentication context, and match rules. Example: custom-config.xml <configuration> <authentication-client xmlns="urn:elytron:client:1.2"> <authentication-rules> <rule use-configuration="monitor"> <match-host name="127.0.0.1" /> </rule> <rule use-configuration="administrator"> <match-host name="localhost" /> </rule> </authentication-rules> <authentication-configurations> <configuration name="monitor"> <sasl-mechanism-selector selector="DIGEST-MD5" /> <providers> <use-service-loader /> </providers> <set-user-name name="monitor" /> <credentials> <clear-password password="password1!" /> </credentials> <set-mechanism-realm name="ManagementRealm" /> </configuration> <configuration name="administrator"> <sasl-mechanism-selector selector="DIGEST-MD5" /> <providers> <use-service-loader /> </providers> <set-user-name name="administrator" /> <credentials> <clear-password password="password1!" /> </credentials> <set-mechanism-realm name="ManagementRealm" /> </configuration> </authentication-configurations> </authentication-client> </configuration> You can then reference that file in your client's code by setting a system property when running your client. Important If you use the programmatic approach , it will override any provided configuration files even if the wildfly.config.url system property is set. When creating rules, you can look for matches on various parameters, such as hostname , port , protocol , or user-name . A full list of options for MatchRule are available in the Javadocs. Rules are evaluated in the order in which they are configured. When no match settings are included in a rule, then the whole rule matches and the authentication configuration is chosen. If more than one match setting is included in a rule, then all must match for the authentication configuration to be chosen. Table 4.1. Common Rules Attribute Description match-local-security-domain Takes a single name attribute specifying the local security domain to match against. match-host Takes a single name attribute specifying the hostname to match against. For example, the host 127.0.0.1 would match on http://127.0.0.1:9990/my/path . match-no-user Matches against URIs with no user. match-path Takes a single name attribute specifying the path to match against. For example, the path /my/path/ would match on http://127.0.0.1:9990/my/path . match-port Takes a single name attribute specifying the port to match against. For example, the port 9990 would match on http://127.0.0.1:9990/my/path . match-protocol Takes a single name attribute specifying the protocol to match against. For example, the protocol http would match on http://127.0.0.1:9990/my/path . match-urn Takes a single name attribute specifying the URN to match against. match-user Takes a single name attribute specifying the user to match against. An example wildfly-config.xml file can be found in Example wildfly-config.xml . For more information about how to configure the wildfly-config.xml file, see Client Configuration Using the wildfly-config.xml File . 4.2.2. The Programmatic Approach The programmatic approach configures all Elytron Client configuration in the client's code: //create your authentication configuration AuthenticationConfiguration adminConfig = AuthenticationConfiguration.empty() .useProviders(() -> new Provider[] { new WildFlyElytronProvider() }) .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("DIGEST-MD5")) .useRealm("ManagementRealm") .useName("administrator") .usePassword("password1!"); //create your authentication context AuthenticationContext context = AuthenticationContext.empty(); context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), adminConfig); //create your runnable for establishing a connection Runnable runnable = new Runnable() { public void run() { try { //Establish your connection and do some work } catch (Exception e) { e.printStackTrace(); } } }; //use your authentication context to run your client context.run(runnable); When adding configuration details to AuthenticationConfiguration and AuthenticationContext , each method call returns a new instance of that object. For example, if you wanted separate configurations when connecting over different hostnames, you could do the following: //create your authentication configuration AuthenticationConfiguration commonConfig = AuthenticationConfiguration.empty() .useProviders(() -> new Provider[] { new WildFlyElytronProvider() }) .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("DIGEST-MD5")) .useRealm("ManagementRealm"); AuthenticationConfiguration administrator = commonConfig .useName("administrator") .usePassword("password1!"); AuthenticationConfiguration monitor = commonConfig .useName("monitor") .usePassword("password1!"); //create your authentication context AuthenticationContext context = AuthenticationContext.empty(); context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), administrator); context = context.with(MatchRule.ALL.matchHost("localhost"), monitor); Table 4.2. Common Rules Rule Description matchLocalSecurityDomain(String name) This is the same as match-domain in the configuration file approach. matchNoUser() This is the same as match-no-user in the configuration file approach. matchPath(String pathSpec) This is the same as match-path in the configuration file approach. matchPort(int port) This is the same as match-port in the configuration file approach. matchProtocol(String protoName) This is the same as match-port in the configuration file approach. matchPurpose(String purpose) Create a new rule which is the same as this rule, but also matches the given purpose name. matchUrnName(String name) This is the same as match-urn in the configuration file approach. matchUser(String userSpec) This is the same as match-userinfo in the configuration file approach. Also, instead of starting with an empty authentication configuration, you can start with the currently configured one by using captureCurrent() . //create your authentication configuration AuthenticationConfiguration commonConfig = AuthenticationConfiguration.captureCurrent(); Using captureCurrent() will capture any previously established authentication context and use it as your new base configuration. An authentication context is established once it has been activated by calling run() . If captureCurrent() is called and no context is currently active, it will try and use the default authentication if available. You can find more details about this in the following sections: The Configuration File Approach The Default Configuration Approach Using Elytron Client with Clients Deployed to JBoss EAP AuthenticationConfiguration.empty() should only be used as a base to build a configuration on top of, and should not be used on its own. It provides a configuration that uses the JVM-wide registered providers and enables anonymous authentication. When specifying the providers on top of the AuthenticationConfiguration.empty() configuration, you can specify a custom list, but most users should use WildFlyElytronProvider() providers. When creating an authentication context, using the context.with(... ) will create a new context that merges the rules and authentication configuration from the current context with the provided rule and authentication configuration. The provided rule and authentication configuration will appear after the ones in the current context. 4.2.3. The Default Configuration Approach The default configuration approach relies completely on the configuration provided by Elytron Client: //create your runnable for establishing a connection Runnable runnable = new Runnable() { public void run() { try { //Establish your connection and do some work } catch (Exception e) { e.printStackTrace(); } } }; // run runnable directly runnable.run(); To provide a default configuration, Elytron Client tries to auto-discover a wildfly-config.xml file on the filesystem. It looks in the following locations: The location specified by the wildfly.config.url system property set outside of the client code. The classpath root directory. The META-INF directory on the classpath. The current user's home directory. The current working directory. You can use the following example as the basic configuration for your client wildfly-config.xml file. Basic wildfly-config.xml <configuration> <authentication-client xmlns="urn:elytron:client:1.2"> <authentication-rules> <rule use-configuration="default" /> </authentication-rules> <authentication-configurations> <configuration name="default"> <sasl-mechanism-selector selector="#ALL" /> <set-mechanism-properties> <property key="wildfly.sasl.local-user.quiet-auth" value="true" /> </set-mechanism-properties> <providers> <use-service-loader/> </providers> </configuration> </authentication-configurations> </authentication-client> </configuration> Note The ANONYMOUS mechanism does not support authorization as a non-anonymous user. This means that set-authorization-name does not work with set-anonymous in the Elytron client configuration file. Instead, if you configure the set-authorization-name , you must also specify a set-user-name for the authorized identity. 4.2.4. Using Elytron Client with Clients Deployed to JBoss EAP Clients deployed to JBoss EAP can also make use of Elytron Client. The AuthenticationContext is automatically parsed and created from the default-authentication-context setting in the JBoss EAP configuration. If the default-authentication-context is not configured, but you have a wildfly-config.xml file included with your deployment or set using the wildfly.config.url system property, the AuthenticationContext is automatically parsed and created from that file. Example: Set the Default Authentication Context To load a configuration file outside of the deployment, you can use the parseAuthenticationClientConfiguration(URI) method. This method returns an AuthenticationContext that you can then use in your client code using the programmatic approach . Additionally, clients will also automatically parse and create an AuthenticationContext from the client configuration provided by the elytron subsystem. The client configuration in the elytron subsystem can also take advantage of other components defined in the elytron subsystem, such as credential stores. If the client configuration is provided by both the deployment and the elytron subsystem, the elytron subsystem's configuration is used. Note The AuthenticationContext from the elytron subsystem can only be used when this authentication-context is set as the default for the elytron subsystem. 4.2.5. Configuring a Jakarta Management Client Using the wildfly-config.xml File Starting in JBoss EAP 7.1, Jakarta Management clients, including JConsole, can be configured using the wildfly-config.xml file. You specify the file path to the configuration file using the -Dwildfly.config.url system property when starting the Jakarta Management client. Note When using JConsole, the -Dwildfly.config.url system property must be prefixed with -J , for example: For more information, see Client Configuration Using the wildfly-config.xml File in the JBoss EAP Development Guide . 4.2.6. Using the ElytronAuthenticator to Propagate Identities Warning Using the ElytronAuthenticator in JBoss EAP is not supported or recommended due to known credential limitations in Java 8. Be aware of the following limitations when using this class to propagate identities. Security identity propagation does not work for calls to protected servlets due to Java 8 design limitations. Do not use the ElytronAuthenticator on the server, for example, in Jakarta Enterprise Beans. Credentials caching can impact its use in a standalone client JVM. JBoss EAP 7.1 introduced the ElytronAuthenticator class, which uses the current security context to perform the authentication. The org.wildfly.security.auth.util.ElytronAuthenticator class is an implementation of java.net.Authenticator . It has one constructor, ElytronAuthenticator() , that constructs a new instance. It has one method, getPasswordAuthentication() , that returns the PasswordAuthentication instance. The following is an example of client code that creates and uses the ElytronAuthenticator class to propagate an identity to the server. Example: Code Using the ElytronAuthenticator // Create the authentication configuration AuthenticationConfiguration httpConfig = AuthenticationConfiguration.empty().useName("bob"); // Create the authentication context AuthenticationContext context = AuthenticationContext.captureCurrent().with(MatchRule.ALL, httpConfig.usePassword(createPassword(httpConfig, "secret"))); String response = context.run((PrivilegedExceptionAction<String>) () -> { Authenticator.setDefault(new ElytronAuthenticator()); HttpURLConnection connection = HttpURLConnection.class.cast(new URL("http://localhost:" + SERVER_PORT ).openConnection()); try (InputStream inputStream = connection.getInputStream()) { return new BufferedReader(new InputStreamReader(inputStream)).lines().findFirst().orElse(null); } }); 4.3. Configuring Trusted Security Domain Outflows For any security invocation, a security identity is established for the security domain. As the invocation is handled, the SecurityIdentity is associated with the current thread. For subsequent calls to getCurrentSecurityIdentity() on the same security domain, the associated identity is returned. Within the application server, there can be multiple SecurityDomain instances for a single invocation or thread. Each SecurityDomain instance can be associated with a different SecurityIdentity . The correct security identity is returned when you call that security domain's getCurrentSecurityIdentity() method. Deployments can invoke other deployments during request handling. Each deployment is associated with a single security domain. If the invoked deployments use the same security domain, then the notion of a single security domain with a current security identity remains. However, each deployment can reference its own security domain. It is possible to import a security identity that is associated with a security domain into another security domain, as described in the section. Importing a Security Identity To import a security identity from a security domain into another security domain to obtain a security identity for this domain, there are predominantly three processing flows. Same Security Domain A security domain can always import its own security identities. In this case, the security domain always trusts itself. Common Security Realm During the import process, the security domain takes the principal from the security identity being imported, passes it through its configured principal transformers and realm mappers, and maps it to an identity within that security domain. If the same security realm is used within the security domain as was used in the security domain that created the identity, both are backed by the same underlying identity and the import is accepted. Trusted Security Domain If the identity is successfully mapped but there is no common security realm, the security domain handling the import is tested to see if it trusts the original security domain. If it does, the import is accepted. Note The identity must exist in the security domain handling the import. The security identity is never trusted in its entirety. Outflow A security domain can be configured to automatically outflow its security identities to a different security domain. In the security domain, if the security identity is established and used for the current invocation, the list of outflow security domains is iterated and the security identity is imported for each of them. This model is more appropriate where multiple invocations to a deployment using a different security domain are likely to occur, for example, when a web application calls five different Jakarta Enterprise Beans using a common security domain.
<web-app> <security-constraint> <web-resource-collection> <web-resource-name>secure</web-resource-name> <url-pattern>/secure/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>Admin</role-name> </auth-constraint> </security-constraint> <security-role> <description>The role that is required to log in to /secure/*</description> <role-name>Admin</role-name> </security-role> <login-config> <auth-method>BASIC</auth-method> <realm-name>exampleApplicationDomain</realm-name> </login-config> </web-app> <jboss-web> <security-domain>exampleApplicationDomain</security-domain> </jboss-web> /subsystem=undertow:write-attribute(name=default-security-domain, value="exampleApplicationDomain") <auth-method>BASIC?silent=true</auth-method> <configuration> <authentication-client xmlns="urn:elytron:client:1.2"> <authentication-rules> <rule use-configuration="monitor"> <match-host name="127.0.0.1" /> </rule> <rule use-configuration="administrator"> <match-host name="localhost" /> </rule> </authentication-rules> <authentication-configurations> <configuration name="monitor"> <sasl-mechanism-selector selector="DIGEST-MD5" /> <providers> <use-service-loader /> </providers> <set-user-name name="monitor" /> <credentials> <clear-password password="password1!" /> </credentials> <set-mechanism-realm name="ManagementRealm" /> </configuration> <configuration name="administrator"> <sasl-mechanism-selector selector="DIGEST-MD5" /> <providers> <use-service-loader /> </providers> <set-user-name name="administrator" /> <credentials> <clear-password password="password1!" /> </credentials> <set-mechanism-realm name="ManagementRealm" /> </configuration> </authentication-configurations> </authentication-client> </configuration> java -Dwildfly.config.url=/path/to/custom-config.xml //create your authentication configuration AuthenticationConfiguration adminConfig = AuthenticationConfiguration.empty() .useProviders(() -> new Provider[] { new WildFlyElytronProvider() }) .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("DIGEST-MD5")) .useRealm("ManagementRealm") .useName("administrator") .usePassword("password1!"); //create your authentication context AuthenticationContext context = AuthenticationContext.empty(); context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), adminConfig); //create your runnable for establishing a connection Runnable runnable = new Runnable() { public void run() { try { //Establish your connection and do some work } catch (Exception e) { e.printStackTrace(); } } }; //use your authentication context to run your client context.run(runnable); //create your authentication configuration AuthenticationConfiguration commonConfig = AuthenticationConfiguration.empty() .useProviders(() -> new Provider[] { new WildFlyElytronProvider() }) .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("DIGEST-MD5")) .useRealm("ManagementRealm"); AuthenticationConfiguration administrator = commonConfig .useName("administrator") .usePassword("password1!"); AuthenticationConfiguration monitor = commonConfig .useName("monitor") .usePassword("password1!"); //create your authentication context AuthenticationContext context = AuthenticationContext.empty(); context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), administrator); context = context.with(MatchRule.ALL.matchHost("localhost"), monitor); //create your authentication configuration AuthenticationConfiguration commonConfig = AuthenticationConfiguration.captureCurrent(); //create your runnable for establishing a connection Runnable runnable = new Runnable() { public void run() { try { //Establish your connection and do some work } catch (Exception e) { e.printStackTrace(); } } }; // run runnable directly runnable.run(); <configuration> <authentication-client xmlns="urn:elytron:client:1.2"> <authentication-rules> <rule use-configuration="default" /> </authentication-rules> <authentication-configurations> <configuration name="default"> <sasl-mechanism-selector selector="#ALL" /> <set-mechanism-properties> <property key="wildfly.sasl.local-user.quiet-auth" value="true" /> </set-mechanism-properties> <providers> <use-service-loader/> </providers> </configuration> </authentication-configurations> </authentication-client> </configuration> /subsystem=elytron/authentication-context= AUTH_CONTEXT :add /subsystem=elytron:write-attribute(name=default-authentication-context,value= AUTH_CONTEXT ) -Dwildfly.config.url= path/to /wildfly-config.xml -J-Dwildfly.config.url= path/to /wildfly-config.xml // Create the authentication configuration AuthenticationConfiguration httpConfig = AuthenticationConfiguration.empty().useName("bob"); // Create the authentication context AuthenticationContext context = AuthenticationContext.captureCurrent().with(MatchRule.ALL, httpConfig.usePassword(createPassword(httpConfig, "secret"))); String response = context.run((PrivilegedExceptionAction<String>) () -> { Authenticator.setDefault(new ElytronAuthenticator()); HttpURLConnection connection = HttpURLConnection.class.cast(new URL("http://localhost:" + SERVER_PORT ).openConnection()); try (InputStream inputStream = connection.getInputStream()) { return new BufferedReader(new InputStreamReader(inputStream)).lines().findFirst().orElse(null); } });