Generating and Consuming custom tokens with the Generic Issue Login Modules

The Generic Issued token generator and consumer, GenericIssuedTokenGenerateLoginModule, and GenericIssuedTokenConsumeLoginModule, can be used in conjunction with the GenericSecurityTokenFactory and GenericSecurityToken SPIs to implement an end-to-end solution for a custom token. Generating and consuming custom tokens with the Generic Issue Login Modules can be done with either policy and bindings, or WSSAPIs.

Before you begin

You must have a functioning set of JAX-WS service client and provider applications to which you can add new JAAS login module classes.

About this task

Complete the following steps if you want to enable a set JAX-WS service client and provider applications to use a custom token. Within these steps MyToken is the name of the token being created.

As you complete this task:
  1. Two JAAS login modules are created; one to generate the token, and one to consume it.
  2. The token is generated and consumed with the assistance of the Generic Issued token consumer and generator.
  3. The security constraints are then applied to the applications with policy sets and bindings.

Procedure

  1. Create the following generator JAAS login module and make it available to your application code
    package test.tokens;
    
    import java.util.ArrayList;
    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 javax.xml.soap.SOAPFactory;
    import javax.xml.soap.SOAPElement;
    import javax.xml.namespace.QName;
    import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
    import com.ibm.websphere.wssecurity.wssapi.token.SecurityToken;
    
    public class MyCustomGenerator 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 {
    
        GenericSecurityTokenFactory factory = null;
        try {
          factory = GenericSecurityTokenFactory.getInstance();
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
        if (factory == null) {
          throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
        }
        SecurityToken myToken = null;
        try {
          SOAPElement tokenElement = createCustomElement(factory);
          myToken = factory.getToken(tokenElement, new QName("http://www.acme.com","MyToken"));
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
        if (myToken == null) {
          throw new LoginException("myToken is null");
        }
    
        //Put the token in a list on the shared state where it will be available to be used by
        //stacked login modules
        factory.putGeneratorTokenToSharedState(_sharedState, myToken);
    
        return true;
      }
    
      private SOAPElement createCustomElement(GenericSecurityTokenFactory gstFactory) throws Exception {
        /*
          <acme:MyToken xmlns:acme="http://www.acme.com" 
                xmlns:utl="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" utl:Id="cust_3">
            <acme:EMail>joe.smith@acme.com</acme:EMail>
          </acme:MyToken>
        */
        SOAPFactory factory = SOAPFactory.newInstance();
    
        //Create the MyToken element
        SOAPElement tokElement = factory.createElement("MyToken", "acme", "http://www.acme.com");
        //Add the Id attribute
        tokElement.addAttribute(new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Id", "utl"), gstFactory.createUniqueId());
    
        //Create the Email element
        SOAPElement emailElement = factory.createElement("Email", "acme", "http://www.acme.com");
        emailElement.addTextNode("joe.smith@acme.com");
    
        //Add the EMail element to the MyToken
        tokElement.addChildElement(emailElement);
    
        return tokElement;
      }
    
      public boolean logout() throws LoginException {
        return false;
      } 
      public boolean abort() throws LoginException {
        return false;
      }
      public boolean commit() throws LoginException {
        return true;
      }
    }
  2. Create the following consumer JAAS login module and make it available to your application code
    package test.tokens;
    
    import java.util.Map;
    import javax.xml.namespace.QName;
    import org.apache.axiom.om.OMElement;
    import javax.security.auth.Subject;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
    import com.ibm.websphere.wssecurity.callbackhandler.PropertyCallback;
    import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
    import com.ibm.websphere.wssecurity.wssapi.token.SecurityToken;
    import com.ibm.wsspi.wssecurity.wssapi.OMStructure;
    
    public class MyCustomConsumer implements LoginModule {
    
      private CallbackHandler _handler;
      private Map _sharedState;
    
      public void initialize(Subject subject, CallbackHandler callbackHandler,
                             Map<String, ?> sharedState, Map<String, ?> options) {
        this._handler = callbackHandler;
        this._sharedState = sharedState;
      }
    
      public boolean login() throws LoginException {
        PropertyCallback propertyCallback = new PropertyCallback(null);
        Callback[] callbacks = new Callback[] { propertyCallback};
    
        try {
          this._handler.handle(callbacks);
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
        //Get the GenericSecurityTokenFactory
        GenericSecurityTokenFactory factory = null;
        try {
          factory = GenericSecurityTokenFactory.getInstance();
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
        if (factory == null) {
          throw new LoginException("GenericSecurityTokenFactory.getInstance() returned null");
        }
        //Get the token that was consumed by the GenericIssuedConsumeLoginModule
        SecurityToken myToken = factory.getConsumerTokenFromSharedState(_sharedState, 
                                                                        new QName("http://www.acme.com","MyToken"));
    
        if (myToken == null) {
          throw new LoginException("no token");
        }
    
        //Get the token's element
        Object obj = myToken.getXML();
        if (obj == null) {
          throw new LoginException("token is empty");
        }
        if (!(obj instanceof OMStructure)) {
          throw new LoginException("XML is not OMStructure");
        }
        OMElement tokenElement = ((OMStructure)obj).getNode();
        //you can use the org.apache.axis2.util.XMLUtils.toDOM method
        //if you want to work with the a w3c.dom element instead of an
        //Axiom element
    
        //Do some processing on the contents of the token element
        OMElement el = tokenElement.getFirstChildWithName(new QName("http://www.acme.com","Email"));
        if (el == null) {
          throw new LoginException("no email element");
        }
        String value = el.getText();
    
        if (value != null && value.equals("joe.smith@acme.com")) {
          return true;
        } else {
          throw new LoginException("email value is bad");
        }
      }
      public boolean commit() throws LoginException {
        return true;
      }
      public boolean logout() throws LoginException {
        return false;
      }
      public boolean abort() throws LoginException {
        return false;
      }
    
    }
  3. Create New JAAS login configurations.
    1. In the administrative console, click Security > Global security
    2. Under Authentication, click Java Authentication and Authorization Service > System logins
    3. Create the JAAS login configuration for the custom generator.
      1. Click New
        • Specify Alias = test.generate.custom
      2. Add the MyCustomGenerator class
        1. Click New
          • Specify Module class name = test.tokens.MyCustomGenerator
        2. Click Use login module proxy
        3. Click OK
      3. Add the GenericIssuedTokenGenerateLoginModule class
        1. Click New
          • Specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.GenericIssuedTokenGenerateLoginModule
        2. Click OK
      4. Click OK
    4. Create the JAAS login configuration for the custom consumer
      1. Click New
        • Specify Alias = test.consume.custom
      2. Add the GenericIssuedTokenConsumeLoginModule class
      3. Click New
        • Specify Module class name = com.ibm.ws.wssecurity.wssapi.token.impl.GenericIssuedTokenConsumeLoginModule
      4. Click OK
      5. Add the MyCustomConsumer class
        1. Click New
          • Specify Module class name = test.tokens.MyCustomConsumer
        2. Click Use login module proxy
        3. Click OK
      6. Click OK
    5. Click Save
  4. Create the custom policy set.
    1. Click Services > Policy sets > Application Policy sets
    2. Click New
      1. Specify Name = CustomTokenPolicy
    3. Click Apply
    4. Under Policies, click Add > WS-Security
  5. Edit the custom policy set
    1. Click WS-Security > Main Policy
    2. Remove the unwanted elements.
      1. Deselect Include timestamp in security header
      2. Deselect Message level protection
    3. Click Apply
    4. Add the custom token
      1. Click Request token policies
      2. Click Add token type > Custom, and then specify:
        • Custom token name = myToken
        • Local part = MyToken
        • Namespace URI = http://www.acme.com
      3. Click OK
    5. Click Save
  6. Configure the client to use the new policy set
    1. Click Services > Service clients
    2. Click your service client
    3. In the check box, select the resource at the top level.
    4. Click Attach Policy Set
    5. Click ACustomTokenPolicy
  7. Create a custom binding for the client
    1. In the check box, select the resource at the top level.
    2. Click Assign Binding
    3. Click New Application Specific Binding
      1. Specify Bindings configuration name = customTokenClientBinding
    4. Click Add > WS-Security
      1. If the Main Message Security Policy Bindings panel does not display, click WS-Security
  8. Configure the custom bindings for the client
    1. Click Authentication and protection
    2. Configure the custom token generator

    3. Click request:myToken
      1. In the JAAS login drop-down list, click test.generate.custom.
      2. Click Apply
      3. Click Callback handler
        • If the User name and Password fields are pre-filled by your browser, clear the fields
      4. Under Custom properties, enter the following values:
        • Name: passThroughToken
        • Value: true
      5. Click OK
    4. Click Save
  9. Configure the provider to use the new policy set
    1. Click Services > Service provider
    2. Click your service provider
    3. In the check box, select the resource at the top level.
    4. Click Attach Policy Set
    5. Click ACustomTokenPolicy
  10. Create a custom binding for the provider
    1. In the check box, select the resource at the top level.
    2. Click Assign Binding
    3. Click New Application Specific Binding
      1. Specify Bindings configuration name = customTokenProviderBinding
    4. Click Add > WS-Security
      1. If the Main Message Security Policy Bindings panel does not display, click WS-Security
  11. Configure the custom bindings for the provider
    1. Click Authentication and protection
    2. Configure the custom token consumer

    3. Click request:myToken
      1. In the JAAS login drop-down list, click test.consume.custom.
      2. Click Apply
      3. Click Callback handler
      4. Under Custom properties, click New, then enter the following two custom properties:
        • Name: passThroughToken, Value: true
        • Name: alwaysGeneric, Value: true
      5. Click OK
  12. Click Save to save your configuration changes.
  13. Restart the application server to Apply the JAAS configuration changes.
  14. Test your service.

Example

The following example illustrates the SOAP Security header that is produced when you follow the preceding procedure.

<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
    <acme:MyToken xmlns:acme="http://www.acme.com" xmlns:utl="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" utl:Id="cust_3">
        <acme:Email>joe.smith@acme.com</acme:Email>
    </acme:MyToken>
</wsse:Security>