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.

Wednesday 30 March 2016

CXF JAX-RS Security: authentication with SAML Token using SecurityTokenService

Introduction

Authentication with SAML Token  is widely used not only in SOAP world, but also for the REST services. SAML provides the number of benefits like:
  • standardization (SAML is widely accepted standard)
  • security (user credentials never leave the firewall boundary)
  • Single Sign On option (SAML token can be used to access multiple applications with a credentials entered only once)
  • extensibility (using custom assertion attributes)
Apache CXF framework supports SAML authentication for JAX-RS out the box. There are two main approaches to issue SAML token in JAX-RS applications:
  1. JAX-RS client issues SAML token itself (using interceptor) and sends it to the JAX-RS service.
  2. JAX-RS client delegates creation of SAML token to SecurityTokeService (STS) and sends the received token to JAX-RS service.
The first approach is briefly described in CXF Wiki and illustrated in this system test.
Approach works in many cases, however it has some drawbacks:
  • In case of using multiple clients, the server have to trust all SAML issuer certificates
  • Client have to know a lot of details about issuing SAML: versions, subject, confirmation method, private keys, certificates
The second approach helps to resolve these drawbacks using central SecurityTokenService (STS) to issue the SAML tokens. In this case JAX-RS client communicates with STS using WS-Trust protocol and delegate creation of SAML Token to STS. This post describes mainly the STS based approach.

Communication

The communication between actors is depicted bellow:



  1. JAX-RS client sends request to STS using WS-Trust protocol. In order to authenticate the end user, this request contains Proof of Possession (PoP) (UsernameToken, X509 with signature, Kerberos, etc)
  2. STS verifies user credentials (normally against Identity Management System)
  3. If verification is successful, STS issues SAML token and sends it back to the JAX-RS client
  4. JAX-RS client injects SAML Token into HTTP request (using HTTP Authorization header, Form values or enveloped)
  5. JAX-RS Service extracts SAML token, verifies STS certificate, signature and expire date. Optionally JAX-RS service can delegate validation of SAML token to STS.
  6. If verification was successful, STS Service processes request and sends response

Configuration and source code

The source code of JAX-RS client, JAX-RS service and STS with running instructions is available on the GitHub
Let  go through all components step by step:

Security Token Service

 STS code was build on the base of  Glen Mazza's Tutorial and contains following parts:
  1. CXF configuration (cxf-servlet.xml) exposing two endpoints for UsernameToken and X509 based authentications
  2. Keystore stskeystore.jks and stsKeystore.properties containing STS public/private key paar and trusted entry with client certificate
  3. PasswordCallbackHandler providing password for STS private key
Note:
  • stskeystore.jks must contains either client certificate itself or client CA certificate as trusted entry to be able to verify validity of client certificate (in case of X509 authentication)
  • x509Endpoints bean in cxf-servlet.xml contains the list of JAX-RS Service endpoints for which SAML will be issued. Currently STS configured to accept any service endpoint.
STS can be either deployed to tomcat as war or started used tomcat plugin (start mvn tomcat7:run). X509 authentication doesn't requires HTTPS.

JAX-RS Client

In CXF versions < 3.1.2 it was required to configure complete STSClient in order to communicate with SecurityTokenService. The change request #CXF-6267 helps to make this configuration a bit easier.
It is necessary to instantiate SamlHeaderOutInterceptor and STSTokenOutInterceptor with two constructor arguments: STS endpoint and authentication parameters:

 <bean id="samlHeadOutInterceptor" class="org.apache.cxf.rs.security.saml.SamlHeaderOutInterceptor" />

<bean id="issuedTokenInterceptor"
    class="org.apache.cxf.ws.security.policy.interceptors.STSTokenOutInterceptor">
    <constructor-arg name="authParams" ref="authParams" />
    <constructor-arg name="stsWsdlLocation"
        value="http://localhost:8080/sts/STS/X509?wsdl" />
    <constructor-arg name="bus" ref="cxf" />
</bean>

<bean id="authParams"
    class="org.apache.cxf.ws.security.policy.interceptors.STSTokenOutInterceptor$AuthParams">
    <constructor-arg name="authMode" value="X509" />
    <constructor-arg name="userName" value="alice" />
    <constructor-arg name="alias" value="myclientkey" />
    <constructor-arg name="callbackHandler"
        value="demo.jaxrs.saml.client.ClientCallbackHandler" />
    <constructor-arg name="keystoreProperties" value="clientKeystore.properties" />
</bean>

The authentication parameters bean specifies:
  1. Authentication mode (X509 or TRANSPORT, I use X509 in this post)
  2. User name
  3. Keystore alias
  4. Keystore password callback handler
  5. Keystore properties
The SamlHeaderOutInterceptor is required to set received SAML Token into authorization header.

After that, JAX-RS client needs to configure these two interceptors:   

<jaxrs:client id="bookStoreProxy" address="https://localhost:9000"
    serviceClass="demo.jaxrs.saml.common.BookStore">
    <jaxrs:providers>
        ...
    </jaxrs:providers>
    <jaxrs:outInterceptors>
        <ref bean="issuedTokenInterceptor" />
        <ref bean="samlHeadOutInterceptor" />
    </jaxrs:outInterceptors>
</jaxrs:client>


That's all.
In case of X509 based authentication client sends to STS request, containing user certificate and signed with user private key. STS validates the certificate and checks the signature. If both are valid,  Proof Of Possession (PoP) is successful and STS issues SAML Token for this user. SamlHeaderOutInterceptor inserts SAML received from STS into HTTP request Authorization header.
Client keystore must contain following entries:
  1.  User certificate and corresponded private key to sign STS request and establish SSL connection with JAX-RS service (myclientkey alias)
  2. STS certificate or it's CA certificate  as trusted entry to validate trust relationship with STS server (mystskey alias)
  3. JAX-RS service certificate as trusted entry to establish SSL connection with JAX-RS service (myservicekey alias), see Client SSL Configuration for details.

Client SSL Configuration

In order to verify SAML token, the JAX-RS service should not only check SAML validity and signature itself, but also ensure that request containing SAML was send by trusted client. SAML Subject Confirmation methods address this problem. By default our JAX-RS client will use Holder Of Key (HoK) Subject Confirmation method. This means that the client must sign the request or part of the request with the private key corresponding to client certificate inside the SAML token. CXF provides two ways to achive this:
  1. Sign XML payload (works only for XML media types)
  2. Use client private and public keys in SSL connection
In this post I use the second method as the most flexible and independent on media type. Therefore it is necessary to configure SSL connection between JAX-RS client and JAX-RS service using appropriate client keys:

<http:conduit name="https://localhost:9000.*">
    <http:client ConnectionTimeout="3000000" ReceiveTimeout="3000000" />
    <http:tlsClientParameters>
        <sec:keyManagers keyPassword="ckpass">
            <sec:keyStore file="src/main/resources/clientstore.jks"
                password="cspass" type="JKS" />
        </sec:keyManagers>
        <sec:trustManagers>
            <sec:keyStore file="src/main/resources/clientstore.jks"
                password="cspass" type="JKS" />
        </sec:trustManagers>
    </http:tlsClientParameters>
</http:conduit>

 I used the same clientstore.jks for SSL communication as in STSTokenOutInterceptor. Now JAX-RS service will be able to compare SSL client certificate and certificate from SAML token and ensure that the client owns private key corresponds to this certificate.

JAX-RS Service

Configuration of JAX-RS service is quite simple:

<bean id="bookStore" class="demo.jaxrs.saml.server.BookStoreImpl" />

<bean id="samlValidator" class="org.apache.cxf.rs.security.saml.SamlHeaderInHandler">
    <property name="validator">
        <bean class="org.apache.wss4j.dom.validate.SamlAssertionValidator" />
    </property>
</bean>

<jaxrs:server id="bookStoreServer" address="https://localhost:9000/">
    <jaxrs:serviceBeans>
        <ref bean="bookStore" />
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <ref bean="samlValidator" />
        ...
    </jaxrs:providers>
    <jaxrs:properties>
        <entry key="ws-security.signature.properties" value="serviceKeystore.properties" />
    </jaxrs:properties>
</jaxrs:server>


I configured default STSTokenValidator and specifies serviceKeystore.properties as signature properties.
Service keystore must contain following entries:
  1.  Service certificate and corresponded private key to establish SSL connection with JAX_RS client (myservicekey alias)
  2. STS certificate or it's CA certificate  as trusted entry to validate trust relationship with STS server (mystskey alias)
  3. JAX-RS client certificate as trusted entry to establish SSL connection with JAX-RS client (myclientkey alias), see Client SSL Configuration for details.
Of course, JAX-RS service need SSL configuration as well:

<httpj:engine-factory bus="cxf">
    <httpj:engine port="9000">
        <httpj:tlsServerParameters>
            <sec:keyManagers keyPassword="skpass">
                <sec:keyStore file="src/main/resources/servicestore.jks"
                    password="sspass" type="JKS" />
            </sec:keyManagers>
            <sec:trustManagers>
                <sec:keyStore file="src/main/resources/servicestore.jks"
                    password="sspass" type="JKS" />
            </sec:trustManagers>
            <sec:clientAuthentication required="true" />
        </httpj:tlsServerParameters>
    </httpj:engine>
</httpj:engine-factory>


The JAX-RS service will receive request with SAML token, extract the token, validate expire day, conditions, signature, verifies trust relationship with STS server and Holder Of Key Subject Confirmation using SSL client certificate. If all validations are successful - service operation will be called, otherwise client will receive 401 Unauthorized response.