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.

April 12, 2012

SSO across Web Applications and Web Services - Part IV a

As promised in my first blog I talk now about Part IV where an Single Sign On (SSO) enabled Web Application integrates with Web Services in the back office. I've splitted this topic into two blogs. This one describes the design of the solution and the next one describes how to implement it.

This is a list of requirements for this application:

  • Web Application must be SSO enabled using WS-Federation
  • Role Based Access Control (RBAC) required for Web Application
  • users are managed in an LDAP directory
  • security solution integrated without programming
  • industry standards should be used (WS-SecurityPolicy, Servlet- and JAX-WS Security API)
  • Web Services are secured with SAML 2.0 to authenticate the browser user
  • Web Services trust a WS-Trust Security Token Service (STS)
  • SAML token for authentication only (SupportingToken, WS-SecurityPolicy)
  • RBAC might be required for Web services in a future release
  • Confidentiality and Integrity on transport level (HTTPS)
  • prevent man-in-the-middle attack

You might think this are a bunch of very sophisticated requirements and will cost a lot of money to implement. The answer is no. In this blog I'll introduce the architecture and design and in the next blog how to realize that with Apache CXF and Tomcat.

The following diagram illustrate the overall architecture:


The key components are the WS-Trust Security Token Service (STS) and the Identity Provider (IDP) which is part of the WS-Federation Passive Requestor Profile. If you are familiar with my previous blogs about Web SSO...

... there are no new infrastructure components required to integrate SSO with Web Services. Because the STS is used for both Web SSO and Web Services Security.

The sequence of steps can be splitted into two parts the Web login and the security for the Web Services.
The Web login starts when the user access the Web Application the first time. The Web Application redirects the browser to the IDP for authentication. The IDP transforms and delegates the request to the STS. The STS validates the user against the LDAP directory and issues a signed SAML token. The browser posts the SAML token to the Web Application which validates the token and creates the security context. This part is described in more details in the above mentioned blogs.

Role information can be accessed using standard security constraints in web.xml or using the Java Servlet API without worrying about the underlying SAML security token or accessing the LDAP directory explicitly.

The second part is the security for Web Services when the user does a step in the application which requires a call to a Web Services. The Web Services stack processes the WS-SecurityPolicy of the service provider where it is described to send a token issued by the STS. The Web Services stack sends an Issue request to the STS on behalf of the browser user. The STS validates the incoming credentials and issues the requested signed SAML token for the target Web Service. The Web Services stack caches the token and attachs it to the outgoing Web Services call. The service provider trusts the STS and can therefore validate the signed SAML token on its own.

I'd like to describe the second request to the STS in more details where the Web Services stack requests a token on behalf of the browser user. You must be in the possession of the credentials (ex. password) to request a token for the browser user - otherwise, anybody could request a token for someone else without knowing the passwort using a took like SOAPUI. The Web Application itself doesn't know the password because the authentication is externalized to the IDP. Even if the authentication had been done by the Web Application the password wouldn't have been cached by the application server for security reasons. OK, you don't possess the password but you got the signed SAML token which you can only get if you provided the credentials to the STS initially. This token must be protected on the wire when exchanged between browser and IDP, the browser and the Web Application and the Web Application and the STS. The option with the best performance is SSL/TLS.
For further confidentiality protection you could encrypt the token with the certificate of the application thus only the application is able to process the token. This is a supported feature of the CXF STS.

The SAML token is cached by the application server and is provided to the Web Services stack to request a new token when required. This use case is covered by the WS-Trust specification with the OnBehalfOf element (or ActAs) in the request to the STS. The STS validates the SAML token in the OnBehalfOf element and issues a new token based on the requirements of the Web Services which are described in its WS-SecurityPolicy document and send to the STS in the SecondaryParameters element.

There are some service calls before a request is sent to the service provider. The request to the STS is the most expensive as it might require to access some ID stores to read user attributes, roles (so called claims) to add it to the SAML token. Usually, a security token has some sort of lifetime either described in the security token itself (SAML condition) or some out-of-band agreement and known by the STS only. The WS-Trust specifiction defines the Lifetime element in the response to tell the token requestor the lifetime in a security token agnostic way. Thus, the Web Services stack just have to cache the returned security token (XML element) and the LifeTime element to prevent sending an issue request for every outgoing Web Service call.

Going through the requirements above... the solution above covers all of them.

How to implement this solution is described here