April 16, 2012

SSO across Web Applications and Web Services - Part IV b

This blog describes how to implement the solution described in my previous blog which you must have read before.

I'd like to structure the blog into the following sections:


I've prepared this demo to run the different components in three tomcat instances. The reason is to get closer to a real scenario.

  1. tomcat-idp

    This tomcat instance hosts the security infrastructure components which are the IDP and the STS.

    HTTPS: 9443, HTTP: 9080

  2. tomcat-rp

    This tomcat instance hosts the Web Application.

    HTTPS: 8443, HTTP: 8080

  3. tomcat-svc

    This tomcat instance hosts the Web Services provider which is usually not hosted on the same instance as the frontend.

    HTTPS: 10443, HTTP: 10080

All three tomcat instances use the same certificate tomcatkeystore.jksfor HTTPS which can be downloaded here.

The Tomcat HTTPS connector is configured in conf/server.xml. Deploy the tomcatkeystore.jks of the example project to the Tomcat root directory if the Connector is configured as illustrated:
  <Connector port="10443" protocol="HTTP/1.1"
    SSLEnabled="true"
    maxThreads="150" scheme="https" secure="true"
    keystoreFile="tomcatKeystore.jks"
    keystorePass="tompass" sslProtocol="TLS"
  />

Extend the STS...

We use the same STS instance prepared for the Web SSO. The initial set up of this STS is described in my first blog. You can find the updated sources here.

The previous STS supported only one policy which says the communication with the STS must be secured on the transport level using HTTPS (TransportBinding) and UsernameToken is used as a "SupportingToken".

We have to configure a new policy which supports the TransportBinding but doesn't enforce a UsernameToken (SupportingToken assertion is missing). The new policy is added to the wsdl file ws-trust-1.4-service.wsdl.

Right now, everybody who gets into the possession of a signed SAML token could request a new security token from the STS. This risk is mitigated using HTTPS (You could authenticate the requestor also who requests a token on behalf of someone else).


The new endpoint is configured in WEB-INF/cxf-transport.xml as illustrated here:

<jaxws:endpoint id="transportSTS2"
   implementor="#transportSTSProviderBean"
   address="/STSServiceTransport"
   wsdlLocation="/WEB-INF/wsdl/ws-trust-1.4-service.wsdl"
   xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
   serviceName="ns1:SecurityTokenService"
   endpointName="ns1:Transport_Port">
</jaxws:endpoint>
You can see all updates here.


Protect the Web Services provider...


The Web Service provider is a new component. You can find the sources of this example here. It supports a basic Web Services operation called "greetMe" with no input parameters but one output parameter. The response contains the authenticated user id which is retrieved from the JAX-WS WebServiceContext. In this use case, the authenticated user id must be the same as the one used by the browser user when login to the Web Application.

The following code snippet shows the implementation of the operation greetMe:

@Resource
    WebServiceContext context = null;

    public String greetMe() {
        LOG.info("Executing operation greetMe");
        System.out.println("Executing operation greetMe");
        if (context == null) {
           return "Unknown user";
        }
        else {
           Principal p = context.getUserPrincipal();
           if (p == null) {
             return "Principal null";
           }
           return p.getName();
        }
    }

The WSDL of the service provider contains a WS-SecurityPolicy which defines that:
  • HTTPS must be used
  • a SAML token must be sent issued by an STS
  • the SAML token is a SupportingToken
<sp:TransportBinding
 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
 <wsp:Policy>
  <sp:TransportToken>
   <wsp:Policy>
    <sp:HttpsToken RequireClientCertificate="false" />
   </wsp:Policy>
  </sp:TransportToken>
  ...
 </wsp:Policy>
</sp:TransportBinding>
<sp:SupportingTokens
 xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
 <wsp:Policy>
  <sp:IssuedToken
   sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
   <sp:RequestSecurityTokenTemplate>
    <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType>
    <t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</t:KeyType>
   </sp:RequestSecurityTokenTemplate>
   ...
  </sp:IssuedToken>
 </wsp:Policy>
</sp:SupportingTokens>

The service provider trusts an STS which means that it accepts every SAML token which has been signed/issued by its trusted STS. This trust is established based on the certificate (stsstore.jks) which must be configured in the service provider in the JAX-WS property ws-security.signature.properties.

<jaxws:endpoint id="GreeterService"
   implementor="org.apache.cxf.fediz.examples.service.GreeterImpl"
   wsdlLocation="WEB-INF/wsdl/hello_world.wsdl"
   serviceName="svc:GreeterService"
   xmlns:svc="http://apache.org/hello_world_soap_http"
   address="/GreeterService">
   <jaxws:properties>
     <entry key="ws-security.signature.properties" value="stsKeystore.properties" />
   </jaxws:properties>
</jaxws:endpoint>

You must ensure to add the Maven dependencies for security and policy because the policy engine is not initialized otherwise.
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-ws-policy</artifactId>
  <version>${cxf.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-ws-security</artifactId>
  <version>${cxf.version}</version>
</dependency>

The demo is prepared to deploy the Web Service to Tomcat instance tomcat-svc using Maven.

You don't have to change the tomcat URL if you have set up a Tomcat instance with HTTP port 10080 and HTTPS port 10443. Otherwise you must update the tomcat plugin in the POM:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>tomcat-maven-plugin</artifactId>
  <version>1.1</version>
  <configuration>
    <server>myTomcat</server>
    <url>http://localhost:10080/manager</url>
    <path>/${project.build.finalName}</path>
  </configuration>
</plugin>

Add the server with username and password to your Maven settings.xml
Ensure that this user has the role "manager-script" in Tomcat as described here

Run...

mvn clean install tomcat:redeploy
I recommend to use redeploy as deploy works the first time only

You can download the published WSDL with the following url:
https://localhost:10443/fedizservice/GreeterService?wsdl


Integrate the Web SSO and Web Services stack...

The Web Application fediz-tomcat-example has been extended and slightly changed. You can find the new version here. A key change is the URL used to access the FederationServlet.
old: https://localhost:8443/fedizhelloworld/secureservlet/fed
new: https://localhost:8443/fedizhelloworld/secure/fedservlet

A simple JSP page has been added which shows the authenticated user and provides a button to call the Web Service. The Web Services is protected by the WS-SecurityPolicy which enforces a SAML token issued by its trusted STS. If the call was successfull a similar page is shown as for the federation servlet but it shows the response of the Web Services provider. The response contains the authenticated username which must be the same as the one for the authenticated user in the Web Application.

The following code snippet shows the code used to call the service provider securely. The JAX-WS proxy is instantiated by Spring and thus just read from the ApplicationContext.

Greeter service = (Greeter)ApplicationContextProvider.getContext().getBean("HelloServiceClient");
String reply = service.greetMe();
out.println("<br><b>Greeter Service Response: " + reply + "</b><p>");
There is no security related code required.

The spring configuration beans.xml contains the bean configuration for the service consumer:

<jaxws:client id="HelloServiceClient" serviceName="svc:GreeterService"
  xmlns:svc="http://apache.org/hello_world_soap_http"
  serviceClass="org.apache.hello_world_soap_http.Greeter"
  address="https://localhost:10443/fedizservice/GreeterService"
  wsdlLocation="WEB-INF/wsdl/hello_world.wsdl">
  <jaxws:properties>
    <entry key="ws-security.sts.client">
      <bean class="org.apache.cxf.ws.security.trust.STSClient">
        <constructor-arg ref="cxf" />
        <property name="wsdlLocation" value="https://localhost:9443/fedizidpsts/STSServiceTransport?wsdl" />
        <property name="serviceName"
          value="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}SecurityTokenService" />
        <property name="endpointName"
          value="{http://docs.oasis-open.org/ws-sx/ws-trust/200512/}Transport_Port" />
        <property name="onBehalfOf" ref="delegationCallbackHandler" />
        <property name="enableAppliesTo" value="true" />
      </bean>
    </entry>
    <entry key="ws-security.cache.issued.token.in.endpoint" value="false" />
  </jaxws:properties>
</jaxws:client>

<bean id="delegationCallbackHandler" class="org.apache.cxf.fediz.example.ThreadLocalCallbackHandler" />

The Web Application must have access to the WSDL which includes the WS-SecurityPolicy assertion. Either you have deployed this WSDL within your Web Application (see configuration wsdlLocation attribute) or you download it from the service provider at runtime (see configuration for wsdlLocation of the ws-security.sts.client). The SecurityPolicy contains an IssuedToken assertion which indicates CXF to call the STS to request a token. The STS configuration is shown above where the STSClient bean is configured at entry ws-security.sts.client.

The information in the SecurityPolicy element RequestSecurityTokenTemplate are added to the SecondaryParameters element in the request to the STS. The service consumer doesn't care about its content. Our example looks like this:

<sp:RequestSecurityTokenTemplate>
  <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType>
  <t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</t:KeyType>
</sp:RequestSecurityTokenTemplate>

You can find more information about the correlation of STS parameters and SAML tokens here.

If you have deployed the service provider to a Tomcat instance with another port than 10443 you must update the attribute address of the bean jaxws:client.

The key thing to bridge Web SSO and Web Services stack CXF is the property onbehalfof in the STSClient bean configuration. This property references a Java CallbackHandler implementation which provides the on-behalf-of token as a DOM element. More information about the CallbackHandler for onbehalfof can be found here.
CXF ships some out-of-the-box CallbackHandler implementations. This demo provides a custom CallbackHandler implementation called ThreadLocalCallbackHandler which provides the SAML token which has been issued by the IDP/STS during the Web login. The following additional classes are required:

  • SecurityTokenThreadLocal

    This is the thread local which contains the SAML token as a DOM element. The ThreadLocalCallbackHandler gets the SAML token from this class.

  • FederationFilter

    This servlet filter reads the SAML token from the session and adds it to SecurityTokenThreadLocal and cleans it after request processing finished

You must also ensure to add the Maven dependencies for security and policy because the policy engine is not initialized otherwise.
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-ws-policy</artifactId>
  <version>${cxf.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-ws-security</artifactId>
  <version>${cxf.version}</version>
</dependency>

You can deploy this demo with Maven to Tomcat instance tomcat-rp in the same way as for the service provider described above.

You got a bunch of information in the last two blogs and I'd say a great example to illustrate how to SSO enable your Web Applications and Web Services with only a few open source components.

Update: This example is part of the Fediz distribution.

6 comments:

  1. very interesting..appreciate u r work:-)

    ReplyDelete
  2. hello Oliver

    we got this working with the latest fediz1.0 release. My question is:
    Is there a way to avoid getting the SAML tokenB (ActAs/onBehalfOf) when the federationservlet makes a WS-call to the test webservice? How do I forward the same tokenA got by the browser to the web service(for performance reasons)?
    I understand that SAML2 spec introduced this ActAs behaviour; however i would want to disable this and use a single token throughout my flow( until the token expires).

    ReplyDelete
    Replies
    1. The SAML token is stored by fediz in the http session (see FederationFilter) or use the thread local storage. You can set the SAML token element in the proxy but you must also update the WS-SecurityPolicy to not require an "IssuedToken" but instead a "SamlToken".

      FYI, CXF will request a token once for its lifetime and not request a new token for each outgoing web service call.

      Delete
  3. Thanks for this article & appreciate your work !.

    We have requirements to sign (both headers & body parts) and encrypt (body part) of the message.
    As part of WEB SSO how to address body part encryption (SAML token encryption) and also while calling the web service along with ActAs token request again the requests & response are required to follow policy to do sign and encrypt.

    Could you please provide pointers on how to go about implementing this?.

    ReplyDelete
  4. Thanks. good article & informative. Appreciated.
    We have requirements to sign (header +body) & encrypt (body) parts of the message.

    As part of WEB-SSO, The RP app has to decrypt the received encrypted SAML token and while calling the web service the STS authorize token issue request (along with "ActAs" SAML token) has to have sign & encrypted data in the message follow with the actual service call. The service call will be again signed & encrypted.
    Could you please provide pointers on how to go about implementing this?

    ReplyDelete
    Replies
    1. It's planned to support encrypted tokens in fediz release 1.1
      https://issues.apache.org/jira/browse/FEDIZ-2

      When your web application calls another web service you must request a new token from the STS to sign and encrypt the outgoing soap message.

      I recommend to have a look at Colm's blog:
      http://coheigea.blogspot.ch/2011/12/ws-securitypolicy-examples-in-apache.html

      Delete