Friday, November 8, 2013

Apache CXF STS client configuration options

Apache CXF provides a Security Token Service (STS), which can issue (as well as validate, renew + cancel) security tokens using the WS-Trust protocol. A common SOAP security scenario is where a service provider requires that a client must authenticate itself to the service, by geting a token from an STS and including it in the service request. In this article, we will explore different ways of configuring the client with details of how to communicate with the STS, as well as how the service provider can provide these details to the client.

1) IssuedToken policies

The service provider can require that a client retrieve a security token from an STS by specifying a WS-SecurityPolicy IssuedToken policy (for example, in the WSDL of the service). The following IssuedToken policy fragment (see here for a concrete example) tells the client that it must include a SAML 1.1 token in the request. In addition, the token must include the client's PublicKey, the corresponding private key of which must be used to secure the request in some way (SOAP Message Signature, TLS client authentication):

<sp:IssuedToken
    sp:IncludeToken=".../IncludeToken/AlwaysToRecipient">
    <sp:RequestSecurityTokenTemplate>
        <t:TokenType>http://.../oasis-wss-saml-token-profile-1.1#SAMLV1.1</t:TokenType>
        <t:KeyType>http://.../ws-trust/200512/PublicKey</t:KeyType>
    </sp:RequestSecurityTokenTemplate>
</sp:IssuedToken>

2) STSClient configuration

So the CXF client will know that it must get a SAML 1.1 token from an STS when it sees the above policy, and that it must present the STS with a X.509 Certificate, so that the STS can embed it in the issued token. How does it know how to contact the STS? Typically, this is defined in an STSClient bean. The following configuration (see here for a concrete example), specifies the WSDL location of the STS, as well as the Service + Port QNames to use, as well as some additional security configuration:

<bean id="stsClient" class="org.apache.cxf.ws.security.trust.STSClient">
       <property name="wsdlLocation"
                 value="https://localhost:8443/SecurityTokenService/Transport?wsdl"/>
       <property name="serviceName"
                 value="{http://.../ws-trust/200512/}SecurityTokenService"/>
       <property name="endpointName"
                 value="{http://.../ws-trust/200512/}Transport_Port"/>
       <property name="properties">
           <map>
               <entry key="ws-security.username" value="alice"/>
               <entry key="ws-security.callback-handler"
                      value="org.apache.cxf.systest.sts.common.CommonCallbackHandler"/>
               <entry key="ws-security.sts.token.username" value="myclientkey"/>
               <entry key="ws-security.sts.token.properties" value="clientKeystore.properties"/>
               <entry key="ws-security.sts.token.usecert" value="true"/>
           </map>
        </property>
</bean>

While this type of configuration works well, it has a few drawbacks:
  • The client must have the WSDL (location) of the STS (as well as service + port QNames). 
  • The service can't communicate to the client which STS address to use (as well as service + port QNames).
Apache CXF provides two ways of addressing the limitations given above, that will be described in the next two points.

3) Communicating the STS address + service/port QNames to the client

From Apache CXF 2.7.8 it is possible for the service to communicate the STS address and service/port QNames to the client in a simple way (as opposed to using WS-MEX, as will be covered in the next section). This is illustrated by the IssuerTest.testSAML1Issuer in the CXF source. The IssuedToken policy of the service provider, has an additional child policy:

<sp:Issuer>
    <wsaw:Address>https://.../SecurityTokenService/Transport</wsaw:Address>
    <wsaw:Metadata xmlns:wst="http://.../ws-trust/200512/">
        <wsam:ServiceName EndpointName="Transport_Port">
            wst:SecurityTokenService
        </wsam:ServiceName>
    </wsaw:Metadata>
</sp:Issuer>

The "Address" Element communicates the STS address, and the "ServiceName" Element communicates the Service + Endpoint QNames to the client. With this configuration in the WSDL, the client configuration is greatly simplified. Instead of specifying the WSDL Location + Service/Port QNames, the client now only has to specify the security policy to be used in communicating with the STS. It also must set the security configuration tag "ws-security.sts.disable-wsmex-call-using-epr-address" to "true", to avoid using WS-MEX.

An obvious disadvantage to this method is that it still requires the client to have access to the security policy of the STS. However, it at least is a simple way of avoiding hardcoding service host + port numbers in client configuration. A more sophisticated method is to use WS-MEX, as will be described in the next section.

4) Using WS-MetadataExchange (WS-MEX)

The best way for the service to communicate details of the STS to the client is via WS-MetadataExchange (WS-MEX). This is illustrated by the IssuerTest.testSAML2MEX in the CXF source. The IssuedToken policy of the service provider has an Issuer policy that looks like:

<sp:Issuer>
    <wsaw:Address>https://.../SecurityTokenService/Transport</wsaw:Address>
    <wsaw:Metadata>
        <wsx:Metadata>
            <wsx:MetadataSection>
                <wsx:MetadataReference>
                    <wsaw:Address>https://.../SecurityTokenService/Transport/mex</wsaw:Address>
                </wsx:MetadataReference>
            </wsx:MetadataSection>
        </wsx:Metadata>
    </wsaw:Metadata>
</sp:Issuer>

The client uses the Metadata address to obtain the WSDL of the STS. A CXF Endpoint supports WS-MetadataExchange via a "/mex" suffix, when "cxf-rt-ws-mex" is on the classpath. Note that the client configuration does not need to define an STSClient Object at all any more, only to provide some security configuration for the request to the STS. The client first makes a call to the STS to get the WSDL that looks like:

<soap:Envelope>
    <soap:Header>
        <Action xmlns=".../addressing">http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</Action>
       <MessageID xmlns=".../addressing">urn:uuid:1db606be-695b-46d4-8759-fe9d41746b42</MessageID>
       <To xmlns=".../addressing">https://.../SecurityTokenService/Transport/mex</To>
       <ReplyTo xmlns=".../addressing"><Address>.../addressing/anonymous</Address></ReplyTo>
   </soap:Header>
   <soap:Body/>
</soap:Envelope>

The STS responds with the WSDL embedded in the SOAP Body under a "Metadata" Element. The client then matches the Address defined in the Issuer policy with an endpoint address in the WSDL and uses the corresponding Service/Endpoint QName to invoke on the STS. Alternatively, the service could specify explicit QNames as per the previous example.


No comments:

Post a Comment