Java EE Security API

The Java™ EE Security API 1.0 specification defines a set of security-related annotations that you can use for authentication. The specification also defines a common set of APIs that you can use for programmatic security.

The Java EE Security API 1.0 specification extends self-contained application security capabilities to port across different application servers and uses modern programming concepts, such as Expression Language (EL) and Contexts and Dependency Injection (CDI). The following list contains information that is specific to the Liberty profile to support the specification.

appSecurity-3.0

The appSecurity-3.0 feature provides the core capability for the Java EE Security API 1.0 specification (JSR 375 specification). This feature includes the el-3.0 and cdi-2.0 features. If you want to configure the DatabaseIdentityStore annotation, configure one of the jdbc-4.x features. The javaee-8.0 and webProfile-8.0 features include all the required features.

The appSecurity-3.0 feature supports the specification in the Liberty profile. This feature supports the SecurityContext API and all of the annotations that are defined in the specification. To validate your credentials and collect group information, the specification includes support for authentication mechanisms that use annotations, such as BASIC, FORM, and CustomFORM, as well as Lightweight Directory access Protocol (LDAP) and database identity stores. The appSecurity-3.0 feature also specifies a common set of APIs in the SecurityContext class to perform programmatic security checks. The Liberty profile provides CDI bean implementations for all of the annotations in the specification. In addition, the applications can provide their own custom CDI bean implementations for these annotations.

LDAPIdentityStore

You can use the password utilities capability from the Liberty profile to encrypt or encode the password for the bindDNPassword attribute. For more information on encrypting passwords with the securityUtility command, see securityUtility command.

DataBaseIdentityStore

The database identity store requires a data source configuration to provide the database information. The dataSourceLookup element on the DatabaseIdentityStore annotation references the data source that is defined in the Liberty configuration, such as the server.xml file, or in a Datasource annotation. Also, define the JDBC driver library for the database type in the Liberty configuration.

You can configure a data source in multiple ways. For more information about defining an authentication alias on the data source, see Configuring authentication aliases for Liberty. If the data source uses an authentication alias to define the user and password for the datasource, the datasourceLookup parameter on the DatabaseIdentityStore annotation must be an indirect JNDI lookup of the data source to find the additional resources. For example, the indirect JNDI lookup that is defined on the datasourceLookup parameter on the DatabaseIdentityStore annotation would be java:comp/env/jdbc/MyDatasource.

Authorization

Choose how to set up authorization for your application based on your requirements.

  • Configure authorization to use the automatic role to group mapping:
    • Do not configure any authorization table or role bindings in the application-bnd element in the ibm-application-bnd.xmi, ibm-application-bnd.xml, or server.xml files.
    • Make sure that the group name that is returned by the identity store is the same as the role name.
      • When you use the LDAPIdentityStore annotation, the group name corresponds to the groupNameAttribute attribute of the annotation. For example, assume that the following LDAP group object exists:
        cn=group1, o=mycompany, c=us
        The LdapIdnetityStore annotation returns group1 as a group name when the value of the groupNameAttribute attribute is set to cn. Therefore, the role name must be configured to group1.
  • Specify a set of users or groups for authorization authority:
    • Configure the role bindings in the application-bnd entry for that application.
    • You can configure in different ways based on the identity store configurations in the server.
      When you use the LDAPIdentityStore annotation, the group name corresponds to the groupNameAttribute attribute of the annotation. This name must correspond to the access ID of the group attribute in the security role. For example, assume that the following group object exists:
      cn=group1, o=mycompany, c=us
      The LdapIdnetityStore annotation returns cn=group1, o=mycompany, c=us as a group name when the value of the groupNameAttribute attribute is set to dn. Therefore, the access-id in the application-bnd element must be set to cn=group1, o=myCompany, c=us.

Refer to the following examples to configure role bindings in different ways:

  • In the following examples, the access-id and name fields are required. The complete format for the access-id field is user:realmName/userUniqueID for users and group:realmName/groupUniqueID for groups. The user and group uniqueIDs correspond to the information that the application identity providers return.

    For example, when you use the LDAPIdentityStore annotation, the userUniqueID is the distinguished name (DN) for the user, and the groupUniqueID is based on the groupNameAttribute setting in the LDAPIdentityStore annotation. These values also correspond to the getCallerUniqueID and getCallerGroups methods in the CredentialValidationResult object that is returned by the validate method identity store. The realmName corresponds to the IdentityStoreID of the identity store that is used.

  • If an application uses only one identity store, you can configure the application-bnd entry as shown in the following example. You do not need to provide the realmName or IdentityStoreID because only one identity store is configured:
    <application type="war" id="sample" name="sample" location="sample.war">
        <application-bnd>
                    <security-role name="role1">
                       <user name="bob" access-id="cn=Bob Smith, o=myCompany, c=us" />                                      
                       <group name="group1" access-id="cn=group1, o=myCompany, c=us" />
                    </security-role>          
        </application-bnd>
    </application>
  • If an application uses multiple identity stores, configure the corresponding realmName or IdentityStoreID where the user and group exist.

    In the following example, ldapHostName:636 is the IdentityStoreID returned by the LDAP identity store. When you use the database identity store, the IdentityStoreID is the dataSourceLookup name that is used for the database, which is the full JNDI name of the data source. The realmName and IdentityStoreID are used to distinguish users and groups across different identity stores. When you use your custom identity stores, make the values match the corresponding identity store identifiers.

    • LDAP identity store example:
      <application type="war" id="sample" name="sample" location="sample.war">
          <application-bnd>
                      <security-role name="role1">
                         <user name="bob" access-id="ldapHostName:636/cn=Bob Smith, o=myCompany, c=us" />                                      
                         <group name="group1" access-id="ldapHostName:636/cn=group1,o=myCompany,c=us" />
                      </security-role>          
          </application-bnd>
      </application>
    • Database identity store example:
      <application type="war" id="sample" name="sample" location="sample.war">
          <application-bnd>
                      <security-role name="role1">
                         <user name="bob" access-id="jdbc/myDataSource/fullNameofBob" />                                      
                         <group name="group1" access-id="jdbc/myDataSource/fullGroupNameofGroup1" />
                      </security-role>          
          </application-bnd>
      </application>
      
  • In addition, the server supports the current extended format to specify access-id for the user and the group with the realmName and uniqueID information. Use the extended format to explicitly configure the complete access-id file.
    • LDAP identity store example:
      <application type="war" id="sample" name="sample" location="sample.war">
          <application-bnd>
                      <security-role name="role1">
                         <user name="bob" access-id="user:ldapHostName:636/cn=Bob Smith, o=myCompany, c=us" />                                      
                         <group name="group1" access-id="group:ldapHostName:636/cn=group1, o=myCompany, c=us" />
                      </security-role>          
          </application-bnd>
      </application>
      
    • Database identity store example:
      
      <application type="war" id="sample" name="sample" location="sample.war">
              <application-bnd>
                      <security-role name="role1">
                            <user name="bob" access-id="user:jdbc/myDataSource/fullNameofBob" />                                      
                            <group name="group1" access-id="group:jdbc/myDataSource/fullGroupNameofGroup1" />
                      </security-role>          
            </application-bnd>
      </application>

EAR file

When you configure the security annotations for applications in an EAR file, consider the following points:
  • If any EJB modules are part of the application, use the user registry configuration in the server.xml file for all the modules in that application instead of using identity stores.
  • If identity stores need to be configured for the web modules, use the same identity stores for all the web modules. If the EAR file also contains EJB modules, the user registry that is configured in the server.xml file must match the identity stores configured. The information that must match includes the user and group, and the realm name of the user registry to the identity store ID.
  • If the modules communicate with each other, configure the complete access IDs as specified in the authorization section when one of these conditions occurs:
    • The modules in the application contain different identity stores.
    • The user registry in the server.xml file does not match the identity stores.
    This configuration can lead to complex configurations based on the communication that is needed among the modules in the application.

Override the application provided authentication method or mechanism.

You can configure the authentication mechanism that is specified at the server level to override any authentication mechanism that an application uses.
  • Configure the override to use the basic authentication mechanism.
    <webAppSecurity overrideHttpAuthMethod="BASIC" basicAuthenticationMechanismRealmName="myRealm" />
    
    If the basicAuthenticationMechanismRealmName attribute is not specified, the value of defaultRealm is used.
  • Configure the override to use the form authentication mechanism.
    Specify both the login form page and the login error page. These two pages must be part of a separate WAR file.
    • Specify the context root for the login pages by using the contextRootForFormAuthenticationMechanism element.
      <webAppSecurity loginFormURL="/global/login/globalLogin.jsp" loginErrorURL="/global/login/globalLoginError.jsp"
           overrideHttpAuthMethod="FORM" contextRootForFormAuthenticationMechanism="/global/login" />
      
          <application type="war" context-root="/global/login" id="globalLogin" name="globalLogin" location="globalLogin.war"/>
  • Configure the override to use the client certificate.
    You can use a client certificate for authentication of all the applications by setting the overideHttpAuthMethod method to CLIENT_CERT:
    <webAppSecurity overrideHttpAuthMethod="CLIENT_CERT"/>
    If the client certificate authentication fails, you can configure the failover to a different authentication method by specifying the allowAuthenticationFailOverToAuthMethod element. The following values are acceptable for the allowAuthenticationFailOverToAuthMethod element:
    • BASIC: Basic Authentication is used.
      Example of a failover to the basic login:
      <webAppSecurity overrideHttpAuthMethod="CLIENT_CERT" allowAuthenticationFailOverToAuthMethod="BASIC"/>
    • FORM: Form login is used. When the value is set to FORM, make sure that the loginFormURL and loginErrorURL attributes are set.

      Example of a failover to the form login:

      Specify the loginForm and loginError URLs and the .war file that packages those files:
      <webAppSecurity overrideHttpAuthMethod="CLIENT_CERT" allowAuthenticationFailOverToAuthMethod="FORM" loginFormURL="globalLogin/globalLogin.jsp" loginErrorURL="globalLogin/globalLoginError.jsp"/>
          <application type="war" id="globalLogin" name="globalLogin" location="globalLogin.war"/>  
    • APP_DEFINED: The login method that is used is the one that the application is configured with in either the web.xml file or the annotation.

      Example of a failover to the authentication method defined in the application by using the APP_DEFINED value:

       <webAppSecurity overrideHttpAuthMethod="CLIENT_CERT" allowAuthenticationFailOverToAuthMethod="APP_DEFINED"/>

    When you use client authentication, set the clientAuthentication or clientAuthenticationSupported attribute of the SSL element to true so that the client sends over the certificate.

Things to consider when you implement a custom HTTP authentication mechanism or an identity store.

When you implement a custom HTTP authentication mechanism with the HttpAuthenticationMechanism interface or an identity store with the IdentifyStore interface, consider the following points:
  • Annotate your custom HttpAuthenticationMechanism interface so that it uses the RememberMeIdentityStore interface that you implemented. By implementing the custom HttpAuthenticationMechanism interface, you have single sign-on for your environment so that each subsequent login for a user can use the information in the RememberMe cookie and avoid the need to log in every time.
  • Do not use both the RememberMe cookie and the AutoApplySession annotation as they serve similar purposes, and using both can result in inconsistent authorization behavior.
  • If you provided your own IdentityStore implementation that sets the ID in the CredentialValidationResult class and annotated your custom HttpAuthenticationMechanism implementation with the RememberMe annotation, then ensure that the realm name is not set in the access-id field of the role binding. You want to ensure that the realm name is not set this way because the IdentityStore ID is used as the realm for authorization, but when the RememberMe cookie is processed, the IdentityStore ID is not retained.
  • Create the CredentialValidationResult class with the IdentityStore ID so that the ID can be used as the realm name.

Implementing a custom password hash for a database identity store

A default PasswordHash implementation is provided for an identity store using a database. A PasswordHash is required to validate user passwords in the database. To provide a custom implementation for the PasswordHash, implement javax.security.enterprise.identitystore.PasswordHash, and make it available as a CDI bean in a shared library.

  1. Develop a javax.security.enterprise.identitystore.PasswordHash implementation. The following example shows a sample dummy password hash:
    package com.ibm.ws.security.pwdhash.test;
    
    import javax.security.enterprise.identitystore.PasswordHash;
    
    /**
    * Test PasswordHash sample for DatabaseIdentityStore. For testing purposes only.
    */
    public class TestHash implements PasswordHash {
    
      public TestHash() {
        System.out.println("Init TestHash");
      }
    
      @Override
      public boolean verify(char[] incomingPwd, String existingPwd) {
        System.out.println("TestHash is for testing purposes only.");
        String pwd = String.valueOf(incomingPwd) + "_DUMMY_HASH";
        if (pwd.equals(existingPwd)) {
            return true;
        }
        return false;
      }
    
      @Override
      public String generate(char[] pwdToHash) {
        return String.valueOf(pwdToHash) + "_DUMMY_HASH";
      }
    }
  2. Package the PasswordHash implementation in a jar and make the PasswordHash class available as a CDI bean. There are different ways to mark the class as a bean.
    1. One option is to add a beans.xml file with bean-discovery-mode=all to the META-INF folder of the jar. The following is an example beans.xml file
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_2.xsd"
             bean-discovery-mode="all" version="1.1">
      </beans>
    2. A different option is to add the @dependent annotation to the PasswordHash class
      package com.ibm.ws.security.pwdhash.test;
      
      import javax.enterprise.context.Dependent;
      import javax.security.enterprise.identitystore.PasswordHash;
      
      /**
      * Test PasswordHash sample for DatabaseIdentityStore. For testing purposes only.
      */
      @Dependent
      public class TestHash implements PasswordHash {
      ...
  3. Add the PasswordHash implementation jar as a library element to the server.xml, where dir attribute points to the location of the JAR file. The following is an example of adding a library element
    <library id="CustomHashLib" fat.modify="true">
        <fileset dir="${server.config.dir}" includes="CustomPasswordHash.jar"/>
    </library>
  4. Add the commonLibraryRef reference to the database IdentityStore application in the server.xml. The following is an example of adding a classloader element to an application.
     <application type="war" id="DatabaseAnnotatedCustomHashServlet" name="DatabaseAnnotatedCustomHashServlet" location="DatabaseAnnotatedCustomHash.war">
      <application-bnd>
        <security-role name="javaeesec_basic">
          <user name="blue1" access-id="user:java:comp/DefaultDataSource/blue1" />
       </application-bnd>
       ...
       <classloader commonLibraryRef="CustomHashLib" />
    </application>
  5. The hashAlgorithm property on the DatabaseIdentityStoreDefinition annotation should match the class that is provided in the jar. The following is an example of the DatabaseIdentityStoreDefinition annotation
    @DatabaseIdentityStoreDefinition( 
    callerQuery = "select password from callers where name = ?", 
    groupsQuery = "select group_name from caller_groups where caller_name = ?", 
    hashAlgorithm = com.ibm.ws.security.pwdhash.test.TestHash.class)
  6. Use the same custom hash algorithm to generate passwords that are stored in the database accessed by the database IdentityStore.

Encrypting the password for database identity store users using the default password hash provider

If you want to get the default passwordHash implementation that handles the encryption and decryption of the user passwords in the DatabaseIdentityStores interface, you can use CDI:
Instance<? extends PasswordHash> instance = CDI.current().select(Pbkdf2PasswordHash.class);
PasswordHash passwordHash = instance.get();
passwordHash.initialize(..)
password.generate(...)

Use LTPA Token for Single Sign on (SSO)

The Java EE Security API 1.0 (JSR 375) supports the RememberMeIdentityStore interface. A JSR 375 provider can implement this interface so that the user information is remembered after initial authentication. The cookie that is generated by the RememberMeIdentityStore implementation can be used to achieve SSO without having to prompt the user again until it expires. This capability gives more flexibility for the provider to achieve SSO and is the required when you have different identity stores configured in your applications.

In addition to the specification defined RememberMeIdentityStore interface, you can also use the LTPA token in the LTPA cookie that is generated by the Liberty profile to achieve SSO. Be aware of the following information when you use the LTPA cookie in a JSR 375 application.

  • When you use the JSR 375 specification, additional information is added to the LTPA token in the LTPA cookie. When this LTPA token is sent back again or to another server in the same SSO domain, it is validated. If the validation succeeds, the HttpAuthenticationMechanism implementation, or a JASPIC provider if it is configured instead, is called to complete the authentication.
  • If the SSO is only among JSR 375 enabled applications and only the default HttpAuthMechanism implementations are used, the LTPA token is validated. If the validation is successful, the user is not prompted again to log in. Validation of the token requires that the LTPA keys are exchanged and that the user registry is the same, or that the same identity stores are configured in all the applications. If validation fails, the user is prompted to log in based on the HttpAuthMechanism implementation configured in the application. For more information on LTPA token-based SSO that is based on a token, see Customizing SSO configuration using LTPA cookies in Liberty.
  • If an LTPA cookie from a non-JSR 375 application is presented to a JSR 375 enabled application, the HttpAuthenticationMechanism implementation is called even after the token is validated. This call is done according to the specification requirements. This requirement also holds true when a JASPIC provider is configured. The HttpAuthMechanism implementation then prompts the user again.
  • One might not want to be prompted as described in the previous list item. For instance, you might want to use the validated LTPA token to authenticate the user without calling the HttpAuthenticationMechanism implementation or a JASPIC provider. In this situation, set the following property in the server.xml file:
    <webAppSecurity useLtpaSSOForJaspic="true" />
  • When this property is set and a valid LTPA token is presented, the token is used to authenticate the user without calling the HttpAuthenticationMechanism implementation or the JASPIC provider. Set this flag to true only if the user registry is the same across all the servers that are participating in SSO. If any identity stores are specified in the code, do not set the flag as the identitystores implementation might not be the same in all the applications in all the servers. A typical scenario where this flag is set would be where only the HttpAuthMechanism annotation is specified in the code without any identity stores specified. In this case, the user registry that is configured in the server.xml file is used.
  • If you want a specific LTPA cookie for a server, you can set up a specific custom LTPA cookie name for each server by setting the following ssoCookieName attribute. In this case, only the specific LTPA cookie that is configured is generated and verified by that server. Any other LTPA cookies that are generated by other servers are not used.
    <webAppSecurity useOnlyCustomCookieName="true", ssoCookieName="myServerXCookieName" />  ------> Change the cookieName and make it unique across all the servers
  • If the LTPA cookie that is generated from a JSR 375 server or a JASPIC server is presented to a non-JSR 375 server or a non-JASPIC server, it is validated and used for SSO without prompting the user.