Contents


Security for JAX-RPC Web services, Part 2

Consuming custom tokens

Comments

Content series:

This content is part # of # in the series: Security for JAX-RPC Web services, Part 2

Stay tuned for additional content in this series.

This content is part of the series:Security for JAX-RPC Web services, Part 2

Stay tuned for additional content in this series.

In Part 1 of this series, you learned how to generate and propagate custom tokens. In Part 2 you'll learn how to consume those custom tokens. The custom token used in this article is XML-based, as in Part 1, but you can use the same process for other token types.

The programming model is based on the pluggable token framework introduced in the Web services security engine in WebSphere Application Server V6. We’ll use Rational Application Developer V6.1 with WebSphere global security enabled/

The scenario

This scenario we'll use is the second part of the scenario described in Part 1. To recap, Listing 1 shows the XML-based custom token we used, with the sample XML token format highlighted in bold.

Listing 1. Sample XML token format
<soapenv:Envelope xmlns:soapenv="……" >
  <soapenv:Header>
    <wsse:Security xmlns:wsse="……" soapenv:mustUnderstand="1">
      <a:MyToken xmlns:a="http://www.acme.com" >
        <a:EMail>joe.smith@acme.com</a:EMail>
      </a:MyToken>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body>
    ……
  </soapenv:Body>
</soapenv:Envelope>

In Part 2, you'll learn about the components and implementation and configuration steps required to enable a Web service to consume this custom token, to map the identity to a user in user registry, and to create the WebSphere credential, which can be used for J2EE authorization checks.

Receiver components

In order for the Web services security run-time to understand a custom token, you need to create a number of distinct components. Each of these is a separate Java™ class, and they work together to parse the token, authenticate the identity in the token, and create the relevant credentials. Each of the components has a distinct responsibility. Figure 1 shows the necessary components.

Figure 1. Receiver-side components
Receiver-side components
Receiver-side components

The components (highlighted in red) that you need to implement to consume the custom XML token MyToken are:

  • MyTokenConsumer, which implements the token consumer interface, and is responsible for parsing the XML representation of the security token from the SOAP message. It calls the Java Authentication and Authorization Service (JAAS) Login Module via the JAAS programming model to validate the security token.
  • MyLoginModule, which implements the JAAS Login Module interface, and is responsible for validating the security token obtained from the callback handler and creating the relevant credentials if the authentication succeeds.
  • MyCallbackHandler, which implements the JAAS callback handler interface, and is responsible for supplying security information to the JAAS Login Module.
  • MyToken, which extends the WSSToken abstract class, and is a Java object that represents the external token. This is distinct from (although related to) the external token.

The interactions of these components are explained in Develop the components.

Overview of the process

To configure the Web Services security run-time to authenticate the custom tokens, you need to complete the following steps, which are described in detail in this article:

  1. Develop the required code components described in the previous section, and deploy them to the application server.
  2. Create a JAAS Login Configuration alias with a JAAS login module for the token consumer to use.
  3. Create a custom token configuration in the application deployment descriptor and bindings using the Web Services editor in the development tooling (for instance in Rational Application Developer 6.1 or the Application Server Toolkit)
  4. Deploy the Web services application to the application server.

The result is a Web service configured to accept a custom token and use it for authentication. Configure the Web services application to consume the custom token describes the detailed steps using Rational Application Developer.

Develop the components

This section describes how to create the components required to consume the XML custom token.

Implement the token consumer

The token consumer MyTokenConsumer is responsible for parsing the XML representation of the security token from the SOAP message. The XML representation of the security token is passed in the invoke method as an XML Element parameter. In our sample, the implementation parses the XML token and passes the token information using the JAAS callback handler (MyCallbackHandler) to the JAAS Login Module (MyLoginModule) for token validation and mapping. The implementations of the callback handler and the JAAS Login Module are described in the following sections.

MyTokenConsumer must implement the com.ibm.wsspi.wssecurity.token.TokenConsumerComponent Java interface. The key method is the invoke method, which is where the majority of the implementation is.

Listing 2. Implementation of MyTokenConsumer.invoke method
   public void invoke(Node target, final Map context) throws 
SoapSecurityException {
    ……
    
    final TokenConsumerConfig config =
     (TokenConsumerConfig) context.get(TokenConsumerConfig.CONFIG_KEY);
    // get the SOAP MessageContext
    SOAPMessageContext messageContext = (SOAPMessageContext)
      context.get(com.ibm.wsspi.wssecurity.Constants.
WSSECURITY_MESSAGE_CONTEXT);
    
    if(target != null) {
      if (target.getNodeType() == Node.ELEMENT_NODE) {
        // parse the XML token from SOAP Message
        Element tokenElement = Util.clone((Element) target);
        String idattr = Util.getIdAttribueName(tokenElement);
        String tokenId = null;
        if (idattr != null) {
          tokenId = tokenElement.getAttribute(idattr);
        }
        Element emailElem = Util.getOneElement(tokenElement, 
Util.MYTOKEN_NS, "EMail");
        String email = Util.getStringValue(emailElem);
        
        final String jaasLC = config.getJAASConfig();
        ……
        final Map properties = new HashMap();  // could pass extra properties
        properties.put(Util.MSG_CONTEXT, messageContext);
        properties.put(Util.TOKEN_ID, tokenId);
        properties.put(Util.TOKEN_XML_ELEMENT, tokenElement);
        final MyCallbackHandler ch = new MyCallbackHandler
(email, properties);
        MyToken t = null;
        try {      
          t = AccessController.doPrivileged(new 
PrivilegedExceptionAction<MyToken>() {
            public MyToken run() throws LoginException {
              Subject subject = (Subject)
                context.get
(com.ibm.wsspi.wssecurity.Constants.WSSECURITY_SUBJECT); 
              LoginContext lc = new LoginContext(jaasLC, subject, ch);
              lc.login();
              // Making sure we get back the token created by this 
              // Login Module
              MyToken token = (MyToken)
          properties.get
(com.ibm.wsspi.wssecurity.Constants.WSSECURITY_TOKEN_LOGININFO);
              token.setUsedTokenConsumer(config);  
		// set the token consumer configuration used
              return token;
            }
          });
          System.out.println(quot;Token created: quot; + t.toString());
        } catch (PrivilegedActionException pae) {
          ……
        }
      } else {
        throw new SoapSecurityException(……
      }
    } else {
      throw new SoapSecurityException(……
    }
  }

The XML token is passed as a DOM Element in the invoke method, with a parameter name of target. The context object is the current run-time context, which is a hash map and contains contextual information like:

  1. SOAPMessageContext (SOAP message context), which is obtained by using com.ibm.wsspi.wssecurity.Constants.WSSECURITY_MESSAGE_CONTEXT as key from the context.
  2. TokenConsumerConfig (Token consumer configuration), which is obtained by using TokenConsumerConfig.CONFIG_KEY as key from the context.
  3. Subject (the temporary JAAS subject), which is the container for all security tokens processed in the current request. The contents are used to create the WebSphere JAAS Caller and Invocation Subjects. It is obtained by using com.ibm.wsspi.wssecurity.Constants.WSSECURITY_SUBJECT as key from the context.

After the token is parsed, MyTokenConsumer reads the JAAS Login Configuration name from the TokenConsumerConfig object. The TokenConsumerConfig is populated with the configuration data from the deployment descriptor and bindings (described in Configure the Web services application to consume the custom token). You create the JAAS Login Configuration name using the administration console, also described in that section, and should contain the MyLoginModule JAAS Login Module. MyTokenConsumer invokes MyLoginModule using the JAAS APIs. The token information is passed to MyLoginModule using MyCallbackHandler.

If the authentication is successful, MyTokenConsumer sets the TokenConsumerConfig to MyToken using the setUsedTokenConsumer API. The TokenConsumerConfig is used in the run-time to identify which token consumer configuration to use to process the token.

Note that you should use SoapSecurityException to communicate any failure to the Web services security run-time.

Implement the JAAS Login Module

The JAAS Login Module is responsible for validating the security token, authenticating the token, and identity mapping. In our example, MyLoginModule performs a simple mapping of the email address to an identity in the user registry.

MyLoginModule must implement the JAAS javax.security.auth.spi.LoginModule interface. The two key methods of the JAAS Login Module are the login and commit methods. The login method is where authentication, validation, and identity mapping should occur and the commit method creates the security credential and sets it to the subject. For detailed information about the life cycle of a JAAS Login Module, see Interface LoginModule in the JDK 5.0 Documentation.

Listing 3 shows the implementation of the MyLoginModule.login method

Listing 3. Implementation of MyLoginModule.login method
  public boolean login() throws LoginException {
    MyEmailCallback ec = new MyEmailCallback();
    PropertyCallback pc = new PropertyCallback(null);
    Callback[] callbacks = new Callback[] { ec, pc };
    
    try {
      this._ch.handle(callbacks);
    } catch (UnsupportedCallbackException e) {
      ……
    }
    
    // Perform the authentication, here is just a simple mapping
    this._email = ec.getEmail();
    if (this._email == null || this._email.length() == 0) {
      throw new LoginException("No email found!");
    }
    int i = this._email.indexOf('@');
    if (i == -1) {
      throw new LoginException(this._email + " is not a valid email");
    }
    this._username = this._email.substring(0, i);
    this._properties = pc.getProperties();
    if (this._properties != null) {
      this._tokenId = (String) this._properties.get(Util.TOKEN_ID);
      this._tokenElement = (Element) this._properties.get
(Util.TOKEN_XML_ELEMENT);
    }
    
    this._loginSuccess = true;
    
    return this._loginSuccess;
  }

This method creates an array of callback objects and passes them to the CallbackHandler.handle method. The callback objects are MyEmailCallback and PropertyCallback. The callback handler supplies the information through the callback objects to the JAAS Login Module, as described in Implement the JAAS Callback Handler.

After the JAAS Login Module obtains the information, it can perform authentication and validation of the token information. In our sample, an identity mapping is performed on the email by stripping of the string after @, and saved it as this._username, which is the principal used in constructing the MyToken security token. The returned principal (MyToken.getPrincipal) is then used to map it to a user identity in the user registry in the Caller processing. The login method saves the authenticated data and makes it available to the commit method.

Listing 4 shows the implementation of the MyLoginModule.commit method

Listing 4. Implementation of MyLoginModule.commit method
  public boolean commit() throws LoginException {
    boolean ret = false;
    if (this._loginSuccess) {
      MyToken t = new MyToken(this._email, this._tokenId, this._username, 
this._tokenElement);
      if (this._properties == null) {
        System.out.println("WARNING: Can't return MyToken because 
properties parameter is null.");
      } else {
        this._properties.put
(com.ibm.wsspi.wssecurity.Constants.WSSECURITY_TOKEN_LOGININFO, t);
      }
      this._subject.getPrivateCredentials().add(t);
      ret = true;
    }
    return ret;
  }

The commit method then checks whether log-in is successful, and creates the security token MyToken and adds it to the Subject. This implementation also puts the instance of MyToken into the properties, so that MyTokenConsumer can get to the specific instance. This is useful when you have multiple instances of MyToken.

Implement the JAAS Callback Handler

The callback handler acts as a channel to pass data between MyTokenConsumer and MyLoginModule, leveraging the JAAS programming model. The data container for the data is the callback. The two callbacks used in this sample are:

  • MyEmailCallback for passing the email
  • PropertyCallback for passing the contextual data in the map

The implementation of MyCallbackHandler is very simple. Listing 5 shows the is the handle method:

Listing 5. Implementation of MyCallbackHandler method
  public void handle(Callback[] callbacks) throws IOException, 
UnsupportedCallbackException {
    for (int i = 0; i < callbacks.length; i++) {
      Callback c = callbacks[i];
      
      if (c instanceof MyEmailCallback) {
        ((MyEmailCallback) c).setEmail(this._email);
      } else if (c instanceof PropertyCallback) {
        ((PropertyCallback) c).setProperties(this._properties);
      } else {
        System.out.println("WARNING: unsupported callback " + 
c.getClass().getName());
      }
    }
  }

This implementation passes the data from MyTokenConsumer to MyLoginModule.

Implement the security token (WSSToken)

The MyToken security token is the Java representation of the custom token. It must extend the com.ibm.wsspi.wssecurity.auth.token.WSSToken abstract class. The MyToken object is the Java representation of the XML-based security token. In this example, it is the same for both the sender’s and the receiver’s side. It holds the email address and the mapped principal. The source code for the MyToken implementation is included in Part 1. The following are the key methods:

  • Constructor sets the qualified name (QName) of the token type using the setType() method. This is how the token is identified in the run-time. In this sample, the token type is {http://www.acme.com}MyToken. Note that this is the type used in Configure the Web services application to consume the custom token, when defining the custom token in the deployment descriptor and bindings.
  • getEmail returns the email address. This method is specific to the XML email token used in this sample.
  • getExipration returns the expiration time (in milliseconds). In this sample, there is no expiration for MyToken, so it's set to java.lang.Long.MAX_VALUE.
  • getPrincipal returns the principal name of the security token. In our sample, that's the user name of the email address. This is the identity name used by the Web services security run-time to create the WebSphere credential, which is described in Create the caller.
  • getUniqueID returns the unique ID that will be included as a part of the cache key used in the authenticated Subject. This is used by the receiver side. In our sample, it uses the hash code of MyToken. If the token does not want to affect the cache key, the implementation should return null.
  • isValid returns the validity of the token. In this example, the check is very simple; the token is valid if the email is present and not null.

Configure the Web services application to consume the custom token

Now that we've created the run-time components for consuming a custom token, the next step is to configure the Web services application to consume the XML-based security token by doing the following:

  1. Create the JAAS login configuration with MyLoginModule.
  2. Create a required security token of type {http://www.acme.com}MyToken in the Extension of the Web services provider.
  3. Create a caller of type {http://www.acme.com}MyToken in the Extension of the Web services provider.
  4. Create a token consumer for the MyTokenConsumer implementation in the Bindings of the Web services provider.

This following sections walk you through these configuration steps.

Note: Make sure that WebSphere global security is enabled and that the user registry contains a user called joe.smith.

Create the JAAS login configuration

In order for MyTokenConsumer to use MyLoginModule, you need to create a new JAAS log-in configuration using the WebSphere administrative console.

  1. In the administrative console, select Security => Secure administration, applications, and infrastructure, as shown in Figure 2.
    Figure 2. Select Secure administration, applications, and infrastructure
    Figure 2. Select Secure administration, applications, and         infrastructure
    Figure 2. Select Secure administration, applications, and infrastructure
  2. In the Secure administration, applications, and infrastructure dialog, select Java Authentication and Authorization Service, and then Application logins
  3. Click New to create a new application login configuration, as shown in Figure 3.
    Figure 3. Create new application login configuration
    Figure 3. Create new application log-in configuration
  4. Specify acme.mytoken in the Alias field and click OK, as shown in Figure 4.
    Figure 4. Configuration properties
    Figure 4. Configuration properties
    Figure 4. Configuration properties
  5. Select acme.mytoken in the list of application aliases, as shown in Figure 5.
    Figure 5. Select new alias
    Figure 5. Select new alias
    Figure 5. Select new alias
  6. Select JAAS login modules under Additional properties, as shown in Figure 6.
    Figure 6. Select JAAS login modules
    Figure 6. Select JAAS login modules
    Figure 6. Select JAAS login modules
  7. Click New, as shown in Figure 7.
    Figure 7. Click New to create a new login module
    Figure 7. Click New to create a new login module
    Figure 7. Click New to create a new login module
  8. In the Module class name field, type the class name of the login module, which is com.acme.security.MyLoginModule for this sample. Make sure that REQUIRED is selected for Authentication strategy, then click OK.
    Figure 8. Specify module properties
    Figure 8. Specify module properties
    Figure 8. Specify module properties
  9. Save the configuration by clicking the Save link at the top of the screen.

The new JAAS Login Configuration acme.mytoken is now available for use in the Token Consumer binding configuration.

Create the security token

To create the security token, complete the following steps:

  1. In the Project Explorer, open the Web services editor by double-clicking webservices.xml, as shown in Figure 9.
    Figure 9. Open the Web services editor
    Figure 9. Open the Web services editor
  2. Select the Extensions tab at the bottom of the screen, as shown in Figure 10.
    Figure 10. Select Extensions tab
    Figure 10. Select Extensions ta
    Figure 10. Select Extensions ta
  3. Expand Request Consumer Service Configuration Details, then expand Required Security Token, then click Add, as shown in Figure 11.
    Figure 11. Add required security token
    Figure 11. Add required security token
    Figure 11. Add required security token
  4. In the Required Security Token dialog, enter a name for the token type, a token URI, and a local name to match token type {http://www.acme.com}MyToken, as shown in Figure 12. Make make sure Required is selected for Usage type, and click OK.
    Figure 12. Specify security token information
    Figure 12. Specify security token information
    Figure 12. Specify security token information

The Web services security run-time will now ensure that a security token of type {http://www.acme.com}MyToken is sent in the SOAP message.

Create the caller

The next step is to create the caller configuration. The caller configuration defines which token type is used by the WebSphere security run-time to create the WebSphere credentials.

  1. Expand Request Consumer Service Configuration Details, then expand Caller Part, and click Add, as shown in Figure 13.
    Figure 13. Add caller
    Figure 13. Add caller
    Figure 13. Add caller
  2. In the Caller part dialog, specify a name, a token URI, and a local name to match token type {http://www.acme.com}MyToken, as shown in Figure 14.
    Figure 14. Specify caller information
    Figure 14. Specify caller information
    Figure 14. Specify caller information

The run-time for Web services security uses this token identity to create the security credential and principal for the WebSphere Application Server security run-time. The token identity must be in the configured user registry. The identity of this token can be used for J2EE role-based authorization checks or other security services in the application server.

Create the token consumer

Now you need to create a token consumer for MyTokenConsumer.

  1. Open the Web services editor as described earlier, and click the Binding Configuration tab at the bottom of the screen:
    Figure 15. Select Binding Configuration tab
    Figure 15. Select Binding Configuration tab
    Figure 15. Select Binding Configuration tab
  2. Expand Request Consumer Binding Configuration Details, then Token Consumer, and click Add, as shown in Figure 16.
    Figure 16. Create token consumer
    Figure 16. Create token consumer
    Figure 16. Create token consumer
  3. In the Token Consumer dialog, shown in Figure 17, specify the following information:
    • Enter a name in the Token consumer name field.
    • Specify the token consumer implementation class in the Token consumer class field. For this example, use com.acme.security.MyTokenConsumer.
    • For the Security token, select the name specified in the Security Token dialog (in this case, myToken).
    • Check Use value type.
    • Specify the token URI and local name to match token type {http://www.acme.com}MyToken.
    • Specify acme.mytoken for jaas.config name.
    Figure 17. Specify security token information
    Figure 17. Specify security token information
    Figure 17. Specify security token information

Deploy the application and run the client

Finally, you can deploy the application and run the client as described in Part 1, by completing the following steps:

  1. Deploy CustomTokenService.ear and start the application, by entering the following command:
    wsadmin>$AdminApp install "/tmp/CustomTokenService.ear" 
    	{-usedefaultbindings -appname CustomTokenService -verbose}
    wsadmin>$AdminConfig save
    wsadmin>set appMgr [$AdminControl queryNames cell=……,node=……,
    	type=ApplicationManager,process=server1,*]
    wsadmin>$AdminControl invoke $appMgr startApplication CustomTokenService
  2. Start TCPMON and set the listening port to 9082 by entering the following:
    java -cp $WAS_HOME/runtimes/com.ibm.ws.webservices.thinclient_6.1.0.jar 
    com.ibm.ws.webservices.engine.utils.tcpmon 9082 localhost 9080
  3. Export the client application from Rational Application Developer by entering the following command (assume the client application name is CustomTokenClient.ear):
    launchClient.sh CustomTokenClient.ear

The following message is captured by TCPMON when running the sample

Listing 6. Captured SOAP message from the sample
<soapenv:Envelope 
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Header>
    <wsse:Security 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd" 
soapenv:mustUnderstand="1">
      <a:MyToken xmlns:a="http://www.acme.com" 
xmlns:u="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-utility-1.0.xsd" 
u:Id="com.acme.security.MyTokenGenerator_1">
        <a:EMail>joe.smith@acme.com</a:EMail>
      </a:MyToken>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body>
    <p657:doEcho xmlns:p657="http://www.acme.org">
      <s>Custom Token Test</s>
    </p657:doEcho>
  </soapenv:Body>
</soapenv:Envelope>

In the SystemOut.log, check the following trace output to verify that the email XML token is consumed by the Web services provider and a WebSphere Caller Subject is created based on the email mapped identity and available by calling com.ibm.websphere.security.auth.WSSubject.getCallerSubject(). Listing 7 shows the output trace. The bold text is the output from the MyToken.toString method.

Listing 7. Output trace
[12/5/07 ……] 00000021 SystemOut     O Token created: 
com.acme.security.MyToken[tokenId=com.acme.security.MyTokenGenerator_1], 
element=[com.ibm.ws.webservices.engine.xmlsoap.SOAPElement --- 
{http://www.acme.com}MyToken], username=[joe.smith], 
email=[joe.smith@acme.com]
[12/5/07 ……] 00000021 SystemOut     O Subject: Subject:
	Principal: customRealm/joe.smith
	Public Credential: 
com.ibm.ws.security.auth.WSCredentialImpl@69866986
	Private Credential: 
com.acme.security.MyToken[tokenId=com.acme.security.MyTokenGenerator_1], 
element=[com.ibm.ws.webservices.engine.xmlsoap.SOAPElement --- 
{http://www.acme.com}MyToken], username=[joe.smith], email=[joe.smith@acme.com]
	Private Credential: 
com.ibm.ws.security.token.SingleSignonTokenImpl@26322632
	Private Credential: 
com.ibm.ws.security.token.AuthenticationTokenImpl@147e147e
	Private Credential: 
com.ibm.ws.security.token.AuthorizationTokenImpl@25582558

Summary

This concludes our two-part series on propagating and consuming custom XML-based security tokens using the OASIS Web Services Security SOAP Message Security 1.0 specification. Although the sample we used is very simple, it demonstrates all the necessary steps to enable a Web service client that you can use to propagate a custom token, and to enable a Web service provider to consume the custom token. You can apply these concepts to other token types, such as a binary security tokens.

In this series, we walked through the steps to implement a custom token using the SPI interfaces from the pluggable security token framework, as well as the configuration steps to enable propagation of that custom token in an application client and to enable a Web services provider to consume the custom token.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, SOA and web services
ArticleID=302206
ArticleTitle=Security for JAX-RPC Web services, Part 2: Consuming custom tokens
publish-date=04302008