Version 9

    In this document we show how to use PicketLink STS to validate SAML assertions and authenticate WS clients.

    Required software: JDK 6, PicketLink 1.0.3 or superior and PicketLink Trust JBossWS 1.0.0.CR3 or superior.


    Process Overview

    The following picture illustrates the process of using SAML assertions to authenticate clients of EJB applications:

    saml-sts-module.png

    The client must first obtain the SAML assertion from PicketLink STS by sending a WS-Trust request to the token service. This process usually involves authentication of the client. After obtaining the SAML assertion from the STS, the client includes the assertion in the context of the WS request before invoking any operation. The client side WS handler will take care of including the assertion in the SOAP payload. Upon receiving the invocation, the server side WS handler extracts the assertion and validates it by sending a WS-Trust validate message to the  STS. If the assertion is considered valid by the STS (and the proof of  possession token has been verified if needed), the client is  authenticated.

     

    On JBoss, the SAML assertion validation process is handled by the SAML2STSLoginModule. It reads properties from a configurable file (specified by the configFile option) and establishes communication with the STS based on these  properties. We will see how a configuration file looks like later on. If  the assertion is valid, a Principal is created using the assertion subject name and if the assertion contains roles, these roles are also extracted and associated with the caller's Subject.


    WS Integration Example

    In this section we present a sample WS application that authenticates  clients by validating their SAML assertions with PicketLink STS. The  deployments for both the WS application and the STS can be found  attached in this document.


    WS Sample App

    Our WS application consists of a simple EJB3 stateless session bean. The session interface can be seen bellow:

     

    package webservice.test;
    
    import javax.ejb.Remote;
    import javax.jws.WebService;
    
    @Remote
    @WebService
    public interface WSTest {
    
        public void echo(String echo);
    }

    And this is the implementation class:

     

    package webservice.test;
    
    import javax.annotation.Resource;
    import javax.annotation.security.RolesAllowed;
    import javax.ejb.Stateless;
    import javax.jws.WebMethod;
    import javax.jws.WebService;
    import javax.xml.ws.WebServiceContext;
    
    import org.jboss.ejb3.annotation.SecurityDomain;
    import org.jboss.ws.annotation.EndpointConfig;
    
    @Stateless
    @WebService
    @EndpointConfig(configName = "SAML WSSecurity Endpoint")
    @SecurityDomain("sts")
    @RolesAllowed("testRole")
    public class WSTestBean implements WSTest {
        
        @Resource
        WebServiceContext wsCtx;
    
        @WebMethod
        public void echo(String echo) {
            System.out.println("WSTest: " + echo);
            System.out.println("Principal: " + wsCtx.getUserPrincipal());
            System.out.println("Principal.getName(): " + wsCtx.getUserPrincipal().getName());
            System.out.println("isUserInRole('testRole'): " + wsCtx.isUserInRole("testRole"));
        }
    }
    

    The session requires authentication using the sts security domain and just prints whatever string the client sent and also the Principal on the server side.

     

    This sts security domain is deployed along with the example wstest.jar file attached here but here is:

     

    <?xml version="1.0" encoding="UTF-8"?>
    
    <deployment xmlns="urn:jboss:bean-deployer:2.0">
    
       <!-- ejb3 test application-policy definition -->
       <application-policy xmlns="urn:jboss:security-beans:1.0" name="sts">
          <authentication>
             <login-module code="org.picketlink.identity.federation.bindings.jboss.auth.SAML2STSLoginModule" flag="required">
                <module-option name="configFile">sts-config.properties</module-option>
                <module-option name="password-stacking">useFirstPass</module-option>
             </login-module>
             <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
                <module-option name="usersProperties">sts-users.properties</module-option>
                <module-option name="rolesProperties">sts-roles.properties</module-option>
                <module-option name="password-stacking">useFirstPass</module-option>
             </login-module>
          </authentication>
       </application-policy>
    
    </deployment>
    

    The policy defines two login modules: SAML2STSLoginModule and UsersRolesLoginModule. The former will be responsible for validating the assertion with the STS in order to authenticate the client and the latter is responsible for adding roles. To validate the SAML assertions the login module needs information about the STS, like its endpoint URL, service name, port name, etc. This information is supplied by the sts-config.properties file:

     

    serviceName=PicketLinkSTS
    portName=PicketLinkSTSPort
    endpointAddress=http://localhost:8080/picketlink-sts-1.0.0/PicketLinkSTS
    username=JBoss
    password=JBoss
    

    The last two properties specify the username and password that will be  used to authenticate the JBoss server to the STS when the WS-Trust  validate message is dispatched. In other words, SAML2STSLoginModule needs to authenticate to the STS when validating the SAML assertions  and these properties specify the username and password that will be used  for that.

    The deployment wstest.jar also contains the files to map roles to the Subject. The file sts-users.properies is just an empty file as authentication is handled by PicketLink's login module. sts-roles.properties looks like this:

     

    UserA=testRole
    

     

    Notice also that the WS uses a custom endpoint (SAML WSSecurity Endpoint). This endpoint definition must be included in .../deployers/jbossws.deployer/META-INF/standard-jaxws-endpoint-config.xml:

     

    <endpoint-config>
        <config-name>SAML WSSecurity Endpoint</config-name>
        <post-handler-chains>
          <javaee:handler-chain>
            <javaee:protocol-bindings>##SOAP11_HTTP ##SOAP11_HTTP_MTOM</javaee:protocol-bindings>
            <javaee:handler>
              <javaee:handler-name>SAML WSSecurity Handler</javaee:handler-name>
              <javaee:handler-class>org.picketlink.trust.jbossws.handler.SAML2Handler</javaee:handler-class>
            </javaee:handler>
            <javaee:handler>
              <javaee:handler-name>Recording Handler</javaee:handler-name>
              <javaee:handler-class>org.jboss.wsf.framework.invocation.RecordingServerHandler</javaee:handler-class>
            </javaee:handler>
          </javaee:handler-chain>
        </post-handler-chains>
      </endpoint-config>
    

     

    For this endpoint to work, the library picketlink-trust-jbossws-1.0.0.CR3.jar must be included in .../deployers/jbossws.deployer/. This library is also attached here at the end of the article.

    PicketLink STS

    Our PicketLink STS application is a tweaked version of the picketink-sts.war  file that is available in the PicketLink project downloads page. More  specifically, we created a new security domain for the STS in jboss-web.xml, included an application policy for the new domain that uses the UsersRolesLoginModule to authenticate STS clients, included the users and roles properties files,  and changed the required role in web.xml to STSClient.

     

    This is the content of the STS web.xml:

     

    <?xml version="1.0"?>
    <!DOCTYPE web-app PUBLIC
       -//Sun Microsystems, Inc.//DTD Web Application 2.3//EN
       http://java.sun.com/dtd/web-app_2_3.dtd>

    <web-app>
       <servlet>
         <servlet-name>PicketLinkSTS</servlet-name>
         <servlet-class>org.picketlink.identity.federation.core.wstrust.PicketLinkSTS</servlet-class>
       </servlet>
       <servlet-mapping>
          <servlet-name>PicketLinkSTS</servlet-name>
          <url-pattern>/*</url-pattern>
       </servlet-mapping>

      <security-constraint>
         <web-resource-collection>
           <web-resource-name>TokenService</web-resource-name>
           <url-pattern>/*</url-pattern>
           <http-method>GET</http-method>
           <http-method>POST</http-method>
         </web-resource-collection>
         <auth-constraint>
           <role-name>STSClient</role-name>
         </auth-constraint>
       </security-constraint>

       <login-config>
          <auth-method>BASIC</auth-method>
          <realm-name>PicketLinkSTSRealm</realm-name>
       </login-config>

       <security-role>
          <role-name>STSClient</role-name>
       </security-role>

    </web-app>

     

    STS callers must all have the STSClient role in order to send a WS-Trust request to the STS.

     

    The STS security domain is specified by the jboss-web.xml file:

     

    <?xml version="1.0" encoding="UTF-8"?>

    <jboss-web>
      <security-domain>java:/jaas/sts-domain</security-domain>
    </jboss-web>

     

    The application policy for the sts-domain is defined in the sts-jboss-beans.xml file:

     

    <?xml version="1.0" encoding="UTF-8"?>

    <deployment xmlns="urn:jboss:bean-deployer:2.0">

       <!-- ejb3 test application-policy definition -->
       <application-policy xmlns="urn:jboss:security-beans:1.0" name="sts-domain">
          <authentication>
             <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
                <module-option name="usersProperties">sts-users.properties</module-option>
                <module-option name="rolesProperties">sts-roles.properties</module-option>
             </login-module>
          </authentication>
       </application-policy>

    </deployment>

     

    The sts-users.properties specify the username/passwords of the STS callers:

     

    JBoss=JBoss
    UserA=PassA
    UserB=PassB
    UserC=PassC

     

    The sts-roles.properties specify the roles of the STS callers:

     

    JBoss=STSClient
    UserA=STSClient
    UserB=STSClient
    UserC=STSClient

     

    Notice that the JBoss user represents the JBoss server during the SAML validation process. All other users are the clients of the WS sample  application - they send a message to the STS to acquire a SAML assertion before calling the methods on the WS application.


    Client Application

    STSWSClient just acquires a SAML assertion from PicketLink STS and invokes the echo method of the WS:

     

    package webservice.test;
    
    import java.net.URL;
    
    import org.w3c.dom.Element;
    
    import javax.xml.namespace.QName;
    import javax.xml.ws.BindingProvider;
    import javax.xml.ws.Service;
    
    import org.jboss.ws.core.StubExt;
    import org.picketlink.identity.federation.api.wstrust.WSTrustClient.SecurityInfo;
    import org.picketlink.identity.federation.api.wstrust.WSTrustClient;
    import org.picketlink.identity.federation.core.wstrust.WSTrustException;
    import org.picketlink.identity.federation.core.wstrust.plugins.saml.SAMLUtil;
    import org.picketlink.trust.jbossws.SAML2Constants;
    
    public class STSWSClient {
    
        private static String username = "UserA";
        private static String password = "PassA";
        
        public static void main(String[] args) throws Exception {
            WSTrustClient client = new WSTrustClient("PicketLinkSTS", "PicketLinkSTSPort",
                    "http://localhost:8080/picketlink-sts-1.0.0/PicketLinkSTS",
                    new SecurityInfo(username, password));
            Element assertion = null;
            try {
                System.out.println("Invoking token service to get SAML assertion for " + username);
                assertion = client.issueToken(SAMLUtil.SAML2_TOKEN_TYPE);
                System.out.println("SAML assertion for " + username + " successfully obtained!");
            } catch (WSTrustException wse) {
                System.out.println("Unable to issue assertion: " + wse.getMessage());
                wse.printStackTrace();
                System.exit(1);
            }
    
            URL wsdl = new URL("http://127.0.0.1:8080/wstest/WSTestBean?wsdl");
            QName serviceName = new QName("http://test.webservice/", "WSTestBeanService");
            Service service = Service.create(wsdl, serviceName);
            WSTest port = service.getPort(new QName("http://test.webservice/", "WSTestBeanPort"), WSTest.class);
    
            ((StubExt) port).setConfigName("SAML WSSecurity Client");
            ((BindingProvider) port).getRequestContext().put(SAML2Constants.SAML2_ASSERTION_PROPERTY, assertion);
    
            port.echo("Test");
        }
    
    }
    

    As we can see, the assertion is first obtained using the WSTrustClient API. Notice the client then adds the SAML assertion in the SAML2Constants.SAML2_ASSERTION_PROPERTY ("org.picketlink.trust.saml.assertion") property in the request context before invoking the echo method.

     

    The client also uses a custom endpoint configuration (SAML WSSecurity Client) that must be included in the client's META-INF/standard-jaxws-client-config.xml:

     

    <?xml version="1.0" encoding="UTF-8"?>
    
    <jaxws-config xmlns="urn:jboss:jaxws-config:2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
      xsi:schemaLocation="urn:jboss:jaxws-config:2.0 schema/jaxws-config_2_0.xsd">
    
    ...
    
      <client-config>
        <config-name>SAML WSSecurity Client</config-name>
        <post-handler-chains>
          <javaee:handler-chain>
            <javaee:protocol-bindings>##SOAP11_HTTP ##SOAP11_HTTP_MTOM</javaee:protocol-bindings>
            <javaee:handler>
              <javaee:handler-name>SAML WSSecurity Client Handler</javaee:handler-name>
              <javaee:handler-class>org.picketlink.trust.jbossws.handler.SAML2Handler</javaee:handler-class>
            </javaee:handler>
          </javaee:handler-chain>
        </post-handler-chains>
      </client-config>
    
    </jaxws-config>
    

    Deploying and Running the EJB3 Sample Application on JBoss AS5

    In  order to get the sample application running you must first install the  PicketLink jar files on JBoss. This is accomplished by copying picketlink-fed-2.0.0-SNAPSHOT.jar and picketlink-bindings-jboss-2.0.0-SNAPSHOT.jar (both attached in this document) files to the $JBOSS_HOME/server/<config>/lib folder. After installing the required PicketLink libs you must copy the wstest.jar and picketlink-sts-1.0.0.war to $JBOSS_HOME/server/<config>/deploy.

     

    After  copying the required PicketLink jars and deploying the sample  application and the STS war, start your JBoss instance. If everything  is ok, you should see something like the following in the log:

     

    16:36:30,062 INFO  [JBossASKernel] Created KernelDeployment for: wstest.jar
    16:36:30,069 INFO  [JBossASKernel] installing bean: jboss.j2ee:jar=wstest.jar,name=WSTestBean,service=EJB3
    16:36:30,069 INFO  [JBossASKernel]   with dependencies:
    16:36:30,069 INFO  [JBossASKernel]   and demands:
    16:36:30,069 INFO  [JBossASKernel]      jboss.ejb:service=EJBTimerService
    16:36:30,070 INFO  [JBossASKernel]   and supplies:
    16:36:30,070 INFO  [JBossASKernel]      Class:webservice.test.WSTest
    16:36:30,070 INFO  [JBossASKernel]      jndi:WSTestBean/remote
    16:36:30,070 INFO  [JBossASKernel]      jndi:WSTestBean/remote-webservice.test.WSTest
    16:36:30,070 INFO  [JBossASKernel] Added bean(jboss.j2ee:jar=wstest.jar,name=WSTestBean,service=EJB3) to KernelDeployment of: wstest.jar
    16:36:30,214 INFO  [SessionSpecContainer] Starting jboss.j2ee:jar=wstest.jar,name=WSTestBean,service=EJB3
    16:36:30,227 INFO  [EJBContainer] STARTED EJB: webservice.test.WSTestBean ejbName: WSTestBean
    16:36:30,354 INFO  [JndiSessionRegistrarBase] Binding the following Entries in Global JNDI:
    
            WSTestBean/remote - EJB3.x Default Remote Business Interface
            WSTestBean/remote-webservice.test.WSTest - EJB3.x Remote Business Interface
    
    16:36:30,687 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=wstest,endpoint=WSTestBean
    16:36:31,924 INFO  [WSDLFilePublisher] WSDL published to: file:/opt/jboss-eap-5.0/jboss-as/server/wstest/data/wsdl/wstest.jar/WSTestBeanService7960307186338997650.wsdl
    16:36:32,048 INFO  [TomcatDeployment] deploy, ctxPath=/wstest
    16:36:32,102 WARNING [config] Unable to process deployment descriptor for context '/wstest'
    16:36:32,136 INFO  [config] Initializing Mojarra (1.2_13-b01-FCS) for context '/wstest'
    16:36:35,856 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=picketlink-sts-1.0.0,endpoint=PicketLinkSTS
    16:36:35,936 INFO  [TomcatDeployment] deploy, ctxPath=/picketlink-sts-1.0.0
    16:36:36,120 INFO  [WSDLFilePublisher] WSDL published to: file:/opt/jboss-eap-5.0/jboss-as/server/wstest/data/wsdl/picketlink-sts-1.0.0.war/PicketLinkSTS.wsdl
    

    To run the client application some JBoss' libraries must be included in the classpath:

     

    java -Djava.endorsed.dirs=/opt/jboss-eap-5.0/jboss-as/lib/endorsed -classpath /opt/jboss-eap-5.0/jboss-as/client/jbossall-client.jar:/opt/jboss-eap-5.0/jboss-as/client/jbossws-native-core.jar:/opt/jboss-eap-5.0/jboss-as/client/jbossws-spi.jar:/opt/jboss-eap-5.0/jboss-as/client/jbossws-common.jar:/opt/jboss-eap-5.0/jboss-as/client/jbossws-native-jaxrpc.jar:/opt/jboss-eap-5.0/jboss-as/client/mail.jar:../lib/picketlink-bindings-jboss-2.0.0-SNAPSHOT.jar:../lib/picketlink-fed-2.0.0-SNAPSHOT.jar:../lib/picketlink-trust-jbossws-1.0.0.CR3.jar:/opt/jboss-eap-5.0/jboss-as/client/jboss-xml-binding.jar:/opt/jboss-eap-5.0/jboss-as/client/jaxb-impl.jar:/opt/jboss-eap-5.0/jboss-as/client/wsdl4j.jar:../lib/wstest.jar:. webservice.test.STSWSClient
    

    If everything has been configured and deployed correctly, you should see this output in the client's side:

     

    Invoking token service to get SAML assertion for UserA
    SAML assertion for UserA successfully obtained!
    

    And this in the server's side:

     

    15:56:19,040 INFO  [PicketLinkSTS] Loading STS configuration
    15:56:19,145 INFO  [PicketLinkSTS] picketlink-sts.xml configuration file loaded
    15:56:21,648 INFO  [STDOUT] WSTest: Test
    15:56:21,658 INFO  [STDOUT] Principal: org.picketlink.identity.federation.bindings.jboss.subject.PicketLinkPrincipal@4e39dd5
    15:56:21,658 INFO  [STDOUT] Principal.getName(): UserA
    15:56:21,669 INFO  [STDOUT] isUserInRole('testRole'): true
    

    As we can see, the echo method was invoked and the Principal is an instance of PicketLinkPrincipal (meaning the authentication was successful). Invocation of the web service was successful and also WebServiceContext.isUserInRole("testRole") returns true meaning authorization was also successful.

     

    Special thanks to David Boeren for helping out with the WS client part.