Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module

By default, the Web services security UsernameToken consumer, UNTConsumeLoginModule, always validates the username and password that are contained within the token against the WebSphere registry. You can use the SPIs that GenericSecurityTokenFactory provides to bypass this authentication method.

About this task

If you want to replace the authentication method that UNTConsumeLoginModule uses, you must provide your own custom JAAS login module to do the authentication. The custom login module is stacked under UNTConsumeLoginModule in a custom JAAS configuration. The UNTConsumeLoginModule consumes and validates the token XML. The validation of the values provided for username and password is deferred to the custom stacked login module.

Because the use of UNTConsumeLoginModule carries with it the assumption that the username and password will be authenticated, more requirements are put on a stacked login module that intends to perform this function than are put on login modules that are only intended to provide dynamic token functionality.

To indicate to UNTConsumeLoginModule that it should not authenticate the username and password, you must set the following property on the configured callback handler:

com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true

Like most WS-Security login modles, UNTConsumeLoginModule always puts the consumed token in the shared state map to which all login modules in the stack have access. When authDeferred=true is specified, in the commit phase, UNTConsumeLoginModule ensures that the same UsernameToken object that had originally been put on the shared state has been put in another location in the shared state. If this UsernameToken object cannot be found, a LoginException occurs. Therefore, you cannot just set authDeferred=true on the callback handler without having an accompanying login module return the token to the shared state.

Procedure

  1. Develop a JAAS login module to do the authentication and make it available to your application code. This new login module stacks under the com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule.

    This login module must:

    1. Use the following method to get the UsernameToken that UNTConsumeLoginModule consumes.
      UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(sharedState,UsernameToken.ValueType);
      In this code example, factory is an instance of com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory.
    2. Check the username and password in the manner that you choose.

      You can call unt.getUsername() and unt.getPassword() to get the username and password.

      Your login module should throw a LoginException if there is an authentication error.

    3. Put the UsernameToken, that was obtained in the previous substep, back on the shared state.

      Use the following method to put the UsernameToken back on the shared state.

      factory.putAuthenticatedTokenToSharedState(sharedState, unt);

    Following is an example login module:

    package test.tokens;
    
    import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
    import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.security.auth.Subject;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
    import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
    
    import java.util.ArrayList;
    
    import com.ibm.wsspi.security.registry.RegistryHelper;
    import com.ibm.websphere.security.UserRegistry;
    
    public class MyUntAuthenticator implements LoginModule {
    
      private Map _sharedState;
      private Map _options;
      private CallbackHandler _handler;
    
      public void initialize(Subject subject, CallbackHandler callbackHandler,
                             Map<String, ?> sharedState, Map<String, ?> options) {
    
        this._handler = callbackHandler;
        this._sharedState = sharedState;
        this._options = options;  
      }
    
      public boolean login() throws LoginException {
        //For the sake of readability, this login module does not
        //protect against all NPE's
    
        GenericSecurityTokenFactory factory = null;
        WSSUtilFactory utilFactory = null;
        try {
          factory = GenericSecurityTokenFactory.getInstance();
          utilFactory = WSSUtilFactory.getInstance();
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
        if (factory == null) {
          throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
        }
    
        UsernameToken unt = (UsernameToken)factory.getConsumerTokenFromSharedState(this._sharedState,UsernameToken.ValueType);
    
        String username = unt.getUsername();
        char [] password = unt.getPassword();
    
        //authenticate the username and password 
        //to validate a PasswordDigest password (fixpack 8.5.5.8 and later)
        //String pw = yourCodeToLookUpPasswordForUsername(username);
        //boolean match = utilFactory.verifyDigestedPassword(unt, pw.toCharArray());
        //if (!match) throw new LoginException("Digested passwords do not match");
        //Example:
        try {
          simpleUserGroupCheck(username, password, "cn=group1,o=ibm,c=us");
        } catch (Exception e) {
          LoginException le = new LoginException(e.getMessage());
          le.initCause(e);
          throw le;
        }
    
        //Put the authenticated token to the shared state
        factory.putAuthenticatedTokenToSharedState(this._sharedState, unt);
    
        return true;
      }
    
      private boolean simpleUserGroupCheck(String username, char [] password, String group) throws Exception {
        String allowedGroup = null;
    
        //get the default user registry
        UserRegistry user_reg = RegistryHelper.getUserRegistry(null);
    
        //authenticate the user against the user registry
        user_reg.checkPassword(username, new String(password));
    
        //get the list of groups that the user belongs to
        java.util.List<String> groupList = user_reg.getGroupsForUser(username);
    
        //you can either use a hard-coded group
        allowedGroup = group;
    
        //or get the value from your own custom property on the callback handler
        //WSSUtilFactory util = WSSUtilFactory.getInstance();
        //Map map = util.getCallbackHandlerProperties(this._handler);
        //allowedGroup = (String) map.get("MY_ALLOWED_GROUP_1");
    
        //check if the user belongs to an allowed group
        if (!groupList.contains(allowedGroup)) {
          throw new LoginException("user ["+username+"] is not in allowed group ["+allowedGroup+"]");
        }
        return true;
    }
      //implement the rest of the methods required by the
      //LoginModule interface
    }
  2. Create a new JAAS login configuration.
    1. In the administrative console, select Security > Global security.
    2. Under Authentication, select Java Authentication and Authorization Service.
    3. Select System logins.
    4. Click New, and then specify Alias = test.consume.unt.
    5. Click New, and then specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.UNTConsumeLoginModule
    6. Click OK.
    7. Click New, and then specify Module class name = test.tokens.MyUntAuthenticator
    8. Select Use login module proxy.
    9. Click OK, and then click SAVE.
  3. Configure your UsernameToken token consumer to use the new JAAS configuration.
    1. Open your bindings configuration that you want to change.

      In the administrative console, select WS-Security > Authentication and protection.

    2. Under Authentication tokens, select the UsernameToken inbound token that you want to change.
    3. Select JAAS login = test.consume.unt.
  4. Set the required property on the callback handler that is configured for the UsernameToken consumer.
    1. Click Callback handler.
    2. Add the com.ibm.wsspi.wssecurity.token.UsernameToken.authDeferred=true custom property.
    3. Click OK.
  5. Click SAVE.
  6. Restart the application server to apply the JAAS configuration changes.
  7. Test your service.