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.