Oct 20, 2009

How to use SAML 2.0 Token Profile Support in Rampart 1.5

From 1.5 release onwards Apache Rampart supports SAML 2.0 Token Profile. With this new feature, it allows web service consumers to obtain SAML 2.0 tokens from Security Token Services(STS) and use those tokens to consume other services which impose the presence of a SAML 2.0 token in a SOAP request. In this post, I am explaining how to use a SAML 2.0 token using Apache Rampart in a WS Trust scenario.

1. As the first step you need to set up Apache Axis2 + Rampart. You can download Axis2 1.5 from here and Rampart 1.5 from here.(Since Rampart 1.5 is not released yet, I have hosted the binaries built from the 1.5 branch) I am using an Axis2 deployment on Apache Tomcat to host services in this post.
  • You can simply download Axis2 webapp and deploy it in Apache Tomcat. (I am referring the tomcat installation directory as TOMCAT_HOME from here onwards)
  • Copy the set of jars inside the lib directory of Rampart binary distribution into $TOMCAT_HOME/webapps/axis2/WEB-INF/lib and copy the rampart and rahas module archives(.mar) files into $TOMCAT_HOME/webapps/axis2/WEB-INF/modules directory. Rampart makes use of WSS4J for SAML token validation. Because WSS4J release with the SAML 2.0 token validation support is yet to be released, I am using a custom WSS4J implementation in this scenario. Please download wss4j-1.5.7.wso2v2.jar from here and replace the wss4j-1.5.8.jar which is shipped with Rampart.
  • To use SAML 2.0 support, it is required to endorse the default JAXP implementation of the JDK with Apache Xerces and Xalan. You can find more information on how to endorse the JDK in the README file of Rampart binary distribution. Since Tomcat uses its own endorsed directory, it is required to endorse the Tomcat deployment. You can copy the same set of jar files which is used to endorse the JDK to $TOMCAT_HOME/endorsed directory. For convenience, I have hosted the necessary endorsing jars here. You can download this set of jars and copy them to $JAVA_HOME/jre/lib/endorsed directory.

2. As the second step you need to set up the STS and the relying party service. I am using the SAML 1.1 sample shipped with Apache Rampart with some modifications to make it use SAML 2.0. We are using a service archive which contains both relying party service and configurations for STS. STS is implemented inside Rampart and it is sufficient to provide only the configuration. You can download the this service archive named "samlple05.aar" from here. This service archive will contain a service group where STS and Sample05 are the members of that service group.
  • It is possible to configure STS according to the user requirements. STS configuration of this sample can be found in services.xml file inside the META-INF directory of the extracted service archive. These configurations are available in the <parameter name="saml-issuer-config"> element. Lets go through some of the important parameters of this configuration.



    • issuerName - This is used to identify the issuer, this could be the end point of the issuer or any other identifier used by the relying party services.
    • issuerKeyAlias - This is the alias of the certificate that STS will be using for signing SAML Assertions.
    • issuerKeyPassword - Private key password of the certificate of the STS
    • cryptoProperties - The child elements of this configuration element is used to identify the keystore which is used by the STS. Location of the keystore, keystore type and keystore password are specifed as child elements of this parameter.
    • timeToLive - Validity period of a SAML assertion(mentioned in seconds)
    • trusted-services - Under this parameter, you can specify the EPRs of the trusted relying party services for which users are obtaining SAML tokens. It is possible to specify a wildcard character, so that STS trusts any relying party service. In this sample, we have used a wildcard character. But in real world scenarios, it is recommended not to use this and mention the trusted services specifically.




  • STS is a web service, hence it is possible to publish its requirement as a policy. Please note that, it is better if STS can express its authentication requirements for the user in its policy. In this case, we are using a security policy which contains a Asymmetric Binding. So the X.509 certificate of the client is used to authenticate him.
  • Then it contains the policy and configuration required for the relying party service (sample05) which has a 'echo' operation. The security policy of the relying party service imposes the presence of a SAML 2.0 token in the SAML request.


    
        
            
                
https://kirillgdev04/Security_Federation_SecurityTokenService_Indigo/Symmetric.svc/Scenario_1_IssuedTokenOverTransport_UsernameOverTransport
urn:oasis:names:tc:SAML:2.0:assertion http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey 256

This policy assertion establishes the requirement for a SAML 2.0 assertion which uses a symmetric key with a length of 256 bit for SAML subject confirmation. These requirements are specified in the RequestSecurityTemplate policy assertion.


  • Then lets deploy this service archive in Axis2. You can simply copy sample05.aar file into $TOMCAT_HOME/webapps/axis2/WEB-INF/services/ directory and restart Tomcat if you haven't enable hot deployment.

3. Now lets look at the client code.
package org.wso2.sts;                                                                            

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.soap.SOAP12Constants;
import org.apache.axis2.addressing.AddressingConstants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.rahas.RahasConstants;
import org.apache.rahas.Token;
import org.apache.rahas.TokenStorage;
import org.apache.rahas.TrustException;
import org.apache.rahas.TrustUtil;
import org.apache.rahas.client.STSClient;
import org.apache.rampart.RampartMessageData;
import org.apache.ws.secpolicy.SP11Constants;
import org.apache.ws.secpolicy.SPConstants;
import org.opensaml.XML;

import javax.xml.namespace.QName;

public class Client {

  public static void main(String[] args) throws Exception {

     //TODO : replace with the local paths in your machine
     String epr = "http://localhost:8081/axis2/services/sample05";
     String repo = "/path/to/repo";
     String servicePolicy = "/path/to/service-policy.xml";
     String stsPolicy = "/path/to/sts-policy.xml";

     ConfigurationContext ctx =      configurationContextFactory.createConfigurationContextFromFileSystem(repo, null);

     STSClient stsClient = new STSClient(ctx);

     stsClient.setRstTemplate(getRSTTemplate());
     stsClient.setVersion(RahasConstants.VERSION_05_12);
     String action = TrustUtil.getActionValue(RahasConstants.VERSION_05_02, RahasConstants.RST_ACTION_ISSUE);
     stsClient.setAction(action);

      //Obtain the token
     Token responseToken = stsClient.requestSecurityToken(loadPolicy(servicePolicy),
      "http://localhost:8081/axis2/services/STS", loadPolicy(stsPolicy), epr);

     System.out.println("\n------------------------------ Requested Token   ---------------------------------------\n");
     System.out.println(responseToken.getToken().toString());

     TokenStorage store = TrustUtil.getTokenStore(ctx);
     store.add(responseToken);

     //Call the relying party service
     ServiceClient client = new ServiceClient(ctx, null);

     Options options = new Options();
     options.setAction("urn:echo");
     options.setTo(new EndpointReference(epr));
     options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy(servicePolicy));
     options.setProperty(RampartMessageData.KEY_CUSTOM_ISSUED_TOKEN, responseToken.getId());
     client.setOptions(options);

     client.engageModule("addressing");
     client.engageModule("rampart");

     OMElement response = client.sendReceive(getPayload("Hello world1"));
     System.out.println("Response  : " + response);

  }

  private static Policy loadPolicy(String xmlPath) throws Exception {
    StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
    return PolicyEngine.getPolicy(builder.getDocumentElement());
  }

  private static OMElement getPayload(String value) {
    OMFactory factory = OMAbstractFactory.getOMFactory();
    OMNamespace ns = factory.createOMNamespace("http://sample05.policy.samples.rampart.apache.org", "ns1");
    OMElement elem = factory.createOMElement("echo", ns);
    OMElement childElem = factory.createOMElement("param0", null);
    childElem.setText(value);
    elem.addChild(childElem);

    return elem;

  }

  private static OMElement getRSTTemplate() throws Exception {
    OMFactory fac = OMAbstractFactory.getOMFactory();
    OMElement elem = fac.createOMElement(SP11Constants.REQUEST_SECURITY_TOKEN_TEMPLATE);
    TrustUtil.createTokenTypeElement(RahasConstants.VERSION_05_12,  elem).setText(RahasConstants.TOK_TYPE_SAML_20);
    TrustUtil.createKeyTypeElement(RahasConstants.VERSION_05_12, elem,  RahasConstants.KEY_TYPE_SYMM_KEY);
    TrustUtil.createKeySizeElement(RahasConstants.VERSION_05_12, elem, 256);

    return elem;
  }
}

Above listing depicts the complete version of the Client code. In order to make this work, you need to make some changes in the paths to repos, policies etc. Modify the repo, epr, servicePolicy and stsPolicy accordingly. I am explaining some of the important code segments of the above code for the sake of completion.
  • After instantiating the STSClient object, we set its RSTTemplate. In this code, getRSTTemplate() method is used to create the RSTTemplate. The SAML version, key type and the key size are set to the RST(Request Security Token) inside this method. Following code snipped sets the SAML version, Key type and key length in RST.

TrustUtil.createTokenTypeElement(RahasConstants.VERSION_05_12, elem).setText(RahasConstants.TOK_TYPE_SAML_20);
TrustUtil.createKeyTypeElement(RahasConstants.VERSION_05_12, elem, RahasConstants.KEY_TYPE_SYMM_KEY);
TrustUtil.createKeySizeElement(RahasConstants.VERSION_05_12, elem, 256);

  • Then we set the RST action. In this scenario, we are requesting a token from the STS. So the corresponding trust action is ISSUE.

String action = TrustUtil.getActionValue(RahasConstants.VERSION_05_02, RahasConstants.RST_ACTION_ISSUE);
stsClient.setAction(action);

  • Then the RST is sent to the STS. Here, we are passing the EPR of the RP service as a parameter. This is going to be checked against the set of trusted services we specified in the sts-configuration.

Token responseToken = stsClient.requestSecurityToken(loadPolicy(servicePolicy), "http://localhost:8081/axis2/services/STS", loadPolicy(stsPolicy), epr);

  • After obtaining the token, we are storing it in the trust store and then sending it to the RP service.

You can download the source code, policy files and client site key store from here. Please note that you have to change the rampart-config parameters in both policy files to reflect your local settings.

In this sample, we are using a password callback handler to load the passwords of the private keys. In the client's end, we are using the following password callback handler.
package org.wso2.sts;

import org.apache.ws.security.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class PWCBHandler  implements CallbackHandler{
      public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
          for (int i = 0; i < callbacks.length; i++) {
              WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
              String id = pwcb.getIdentifer();
              if("client".equals(id)) {
                 pwcb.setPassword("apache");
              } else if("service".equals(id)) {
                pwcb.setPassword("apache");
              }
         }
    }
}

To get the client code up and running you need to add certain set of jars to your classpath. Most straight forward approach is adding the Axis2 'lib' into your classpath. It contains all the jars required to compile and run this sample.

Following listing depicts the RST sent from Client to STS.

    http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue
    
        
            http://localhost:8081/axis2/services/sample05
        
    
    
        2009-10-20T13:15:51.739Z
        2009-10-20T13:20:51.739Z
    
    http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0
    http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey
    256
    
        
            g5WVRpUl7bKno8LYFC9JUGLpe1NZpkZ/
        
    
    http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1

In this RST, RequestType is set to "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue". We have passed EPR of the RP service as a parameter when requesting the token. If you carefully observe  AppliesTo element, you will note the EPR we have passed has been set as the value of this element. Similarly the token type, key type and the key size are also appearing in the RST as we have set them in the RSTTemplate. The "Entopy" element is used pass a binary secret which is used to derive keys. I am not going into further details about key derivation is WS Trust. Plese refer to the WS Trust specification for further details.

Following is the resulting RSTR(Request Security Token Response) returned by the STS.

    
        http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0
        256
        
            
                
            
        
        
            
                
            
        
        
            
                http://localhost:8081/axis2/services/sample05
            
        
        
            2009-10-20T15:13:26.138Z
            2009-10-20T15:13:56.138Z
        
        
            
                SAMPLE_STS
                    ...
               
        
        
            http://docs.oasis-open.org/ws-sx/ws-trust/200512/CK/PSHA1
        
        
            
                r/JgXgGMFb4afwRQpggqky8q4TQm7pdXm8RQq9IgCzI=
            
        
    

SAML assertion contained in the RSTR is removed for brevity.

In this post, we have looked about the STS Configuration of Apache Rampart, how to obtain a SAML 2.0 Token, and how to use it to consume a web service. If you faced any issues, do not hesitate to post them here.

10 comments:

Francesco Stampacchia said...

Thilina thanks,

that was really useful!
I was looking forward to a SAML 2.0 consuming guide on Rampart 1.5.

Just one thing, I didn't catch quite well where it is decribed how the service consumes the SAML Assertion. Could you please underline me the point or how does it do it?

Thanks

lia said...

Hi, I am new in java, now I am trying to realize a demo for the rampart 1.5. I did everything following your article. But I get errors, the STS response have an error: java.lang.NoClassDefFoundError: org/apache/commons/lang/StringUtils. So if possible, can I get your e-mail. Thank you!

shawn said...

Hello, I have enjoyed this article very much. One problem, the links for downloading appear to be broken. Is there any way we can get these items from somewhere else?

Thanks

shawn said...

Thilina, can you repost the source and other artfacts to a different web server? I'd like to try this out but can't reach the website.

Thilina Mahesh Buddhika said...

Hi Shawn,

I am in the process of pushing the resources to a new location.

It may take couple of days. As an alternative you can start trying out this by your self using the code snippets I have shared in this post. If you encounter any issue, please post them to
rampart-dev@ws.apache.org.
(You can subscribe to it via http://ws.apache.org/rampart/mail-lists.html)

Cheers.

/thilina

Suresh Achary said...

Thilina, can you repost the source and other artfacts to a different web server? I'd like to try this out but can't reach the website. At least to a public ftp site like turboupload or any other site?

Rob said...

I would be interested in the source code as well. Any progress in relocating it?

duschata said...

Hi Thilina, nice article! But unfortunately the link to the service05.aar is still broken. Can you upload it to another location or attach it to tom(_at_)toms-toy.de ?

Kind regards
Tom

duschata said...

Hi Thilina,
Great article! But unfortunately the link to service05.aar is still broken. Can you republish it or send an attachment to my email tom(_at_)toms-toy.de ?

Kind regards

Tom

murali said...

Hi Thilina,

When I run the client code I got the following exception:

Exception in thread “main” org.apache.axis2.AxisFault: Missing wsse:Security header in request
at org.apache.rampart.handler.RampartReceiver.setFaultCodeAndThrowAxisFault(RampartReceiver.java:180)
at org.apache.rampart.handler.RampartReceiver.invoke(RampartReceiver.java:99)
at org.apache.axis2.engine.Phase.invokeHandler(Phase.java:340)
at org.apache.axis2.engine.Phase.invoke(Phase.java:313)
at org.apache.axis2.engine.AxisEngine.invoke(AxisEngine.java:262)
at org.apache.axis2.engine.AxisEngine.receive(AxisEngine.java:168)
at org.apache.axis2.description.OutInAxisOperationClient.handleResponse(OutInAxisOperation.java:364)
at org.apache.axis2.description.OutInAxisOperationClient.send(OutInAxisOperation.java:421)
at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(OutInAxisOperation.java:229)
at org.apache.axis2.client.OperationClient.execute(OperationClient.java:165)
at org.apache.axis2.client.ServiceClient.sendReceive(ServiceClient.java:555)
at org.apache.axis2.client.ServiceClient.sendReceive(ServiceClient.java:531)
at client.Client.main(Client.java:79)
Caused by: org.apache.rampart.RampartException: Missing wsse:Security header in request
at org.apache.rampart.RampartEngine.process(RampartEngine.java:114)
at org.apache.rampart.handler.RampartReceiver.invoke(RampartReceiver.java:92)
… 11 more

May I know what is the problem in this application.

Thanks

Post a Comment