Wednesday 1 June 2016

CXF JAX-RS Security: SAML Token validation with SecurityTokenService

Introduction

In the previous blog CXF JAX-RS Security: authentication with SAML Token using SecurityTokenService we have seen how to configure JAX-RS Client to obtain SAML from STS and send it to the JAX-RS service in the REST request. The token was validated locally by the JAX-RS service.

This approach works perfectly, but in some scenarios such validation is inconvenient or even impossible. Imagine that we have complex distributed system with multiple domains. Every domain has own STS. There is a trust relationship between all Security Token Services, but services trust only STS from own domain:


Assume that the domain1 client calls domain2 service. SAML token in client request is signed by domain1 STS. Domain2 service hasn't trust relationship with domain1 STS and cannot validate SAML token, received from client, locally.
What is the solution? Domain2 service delegates SAML validation to own STS service, which has trust relationship with domain1 STS.

JAX-RS Client

The client configuration will be the same as in previous blog: no any changes in spring configuration nor in keystore are necessary.

JAX-RS Service

Service configuration and keystore have to be updated for the communication with STS service in order to validate SAML token:
    ...
    <bean id="stsClient" class="org.apache.cxf.ws.security.trust.STSClient">
        <constructor-arg ref="cxf" />
        <property name="wsdlLocation" value="http://localhost:8080/sts/STS/X509?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/}X509_Port" />
        <property name="properties">
            <map>
                <entry key="ws-security.callback-handler"  value="demo.jaxrs.saml.server.ServiceCallbackHandler" />
                <entry key="ws-security.sts.token.username" value="myservicekey" />
                <entry key="security.signature.properties" value="serviceKeystore.properties" />       
                <entry key="ws-security.sts.token.properties" value="serviceKeystore.properties" />
            </map>
        </property>
    </bean>

    <bean id="samlValidator"
        class="org.apache.cxf.rs.security.saml.SamlHeaderInHandler">
        <property name="validator">
            <bean class="org.apache.cxf.ws.security.trust.STSTokenValidator">
                 <constructor-arg value="true" />
                 <property name="stsClient" ref="stsClient"/>
            </bean>
        </property>
    </bean>
    ... 

The STSClient bean is declared and configured with appropriate STS WSDL location, service name, endpoint name and security properties. This client is injected into samlValidator bean to enable remote SAML validation by STS. Note: by default, STSTokenValidator at first tries to verify SAML signature locally and only if local keystore doesn't contain STS certificate as trusted entry, delegates validation to STS service. It is possible to change this behavior using STSTokenValidator constructor argument alwaysValidateToSts. If this argument is set to true, CXF skip local validation and immediately delegates validation to STS server.

The service keystore must contain STS certificate as trusted entry to enable secure communication with STS.

STS Service

Single change in STS Service is additional trust entry in keystore - it is JAX-RS service certificate. It is necessary to enable secure communication between STS and JAX-RS service. 

Example and source code

Updated sources are located on the GitHub. JAX-RS Service configuration is stored into separate configuration file (server-context-sts-validation.xml). Therefore example code can be run with STS validation or without it depending on used spring configuration.
Of course, the same functionality can be implemented as well programmatically, without Spring.