Friday, October 21, 2011

Apache CXF STS documentation - part II

In part I of the series of posts on the new Apache CXF STS implementation, I talked about what a Security Token Service can do, as well as the STS provider framework in CXF since the 2.4.0 release. In this part, I will leave the STS implementation to one side for the moment, and instead focus on how a client interacts with the STS in CXF.

A simple example of how a CXF client can obtain a security token from the STS is shown in the "basic" STS system test "IssueUnitTest". This test starts an instance of the new CXF STS and obtains a number of different security tokens, all done completely programmatically, i.e. with no spring configuration. The STS instance that is used for the test-cases is configured with a number of different endpoints that use different security bindings (defined in the wsdl of the STS). For the purposes of this test, the Transport binding is used:

<wsp:Policy wsu:Id="Transport_policy">
      <wsp:ExactlyOne>
         <wsp:All>
            <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>
                  <sp:AlgorithmSuite>
                     <wsp:Policy>
                        <sp:Basic128 />
                     </wsp:Policy>
                  </sp:AlgorithmSuite>
                  <sp:Layout>
                     <wsp:Policy>
                        <sp:Lax />
                     </wsp:Policy>
                  </sp:Layout>
                  <sp:IncludeTimestamp />
               </wsp:Policy>
            </sp:TransportBinding>
            <sp:SignedSupportingTokens
               xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
               <wsp:Policy>
                  <sp:UsernameToken
                     sp:IncludeToken=".../AlwaysToRecipient">
                     <wsp:Policy>
                        <sp:WssUsernameToken10 />
                     </wsp:Policy>
                  </sp:UsernameToken>
               </wsp:Policy>
            </sp:SignedSupportingTokens>
            ...
         </wsp:All>
      </wsp:ExactlyOne>
   </wsp:Policy>

In other words, this security policy requires that a one-way TLS connection must be used to communicate with the STS, and that authentication is performed via a Username Token in the SOAP header.

The object that communicates with an STS in CXF is the STSClient. Typically, the user constructs an STSClient instance (normally via Spring), sets it with certain properties such as the WSDL location of the STS, what service/port to use, various crypto properties, etc, and then stores this object on the message context using the SecurityConstants tag "ws-security.sts.client". This object is then controlled by the IssuedTokenInterceptorProvider in the ws-security runtime in CXF. This interceptor provider is triggered by the "IssuedToken" policy assertion, which is typically in the WSDL of the service provider. This policy assertion informs the client that it must obtain a particular security token from an STS and include it in the service request. The IssuedTokenInterceptorProvider takes care of using the STSClient to get a Security Token from the STS, and handles how long the security token should be cached, etc.

An example of a simple IssuedToken policy that might appear in the WSDL of a service provider is as follows:

<sp:IssuedToken sp: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>

This policy states that the client should include a SAML 2.0 Assertion of subject confirmation method "Bearer" in the request. The client must know how to communicate with an STS to obtain such a token. This is done by providing the STSClient object with the appropriate information.

We will come back to the IssuedTokenInterceptorProvider at a later date. The IssueUnitTest referred to above uses the STSClient programmatically to obtain a security token. Let's look at the "requestSecurityToken" method called by the tests. An STSClient is instantiated via the CXF bus, and the WSDL location of the STS, plus service and port names are configured:

STSClient stsClient = new STSClient(bus);
stsClient.setWsdlLocation("https://.../SecurityTokenService/Transport?wsdl");
stsClient.setServiceName("{...}SecurityTokenService");
stsClient.setEndpointName("{...}Transport_Port");

A map is then populated with various properties and set on the STSClient. It is keyed with a different number of SecurityConstants tags. A username is supplied for use as the "user" in the UsernameToken. A CallbackHandler class is supplied to get the password to use in the UsernameToken. Compliance of the Basic Security Profile 1.1 is turned off, this is to prevent CXF throwing an exception when receiving a non-spec compliant response from a non-CXF STS:

Map<String, Object> properties = new HashMap<String, Object>();
stsClient.setProperties(properties);
properties.put(SecurityConstants.USERNAME, "alice");
properties.put(
        SecurityConstants.CALLBACK_HANDLER,
        "org.apache.cxf.systest.sts.common.CommonCallbackHandler"
);
properties.put(SecurityConstants.IS_BSP_COMPLIANT, "false");
      
If the KeyType is a "PublicKey", then an X.509 Certificate is presented to the STS in the request to embed in the generated SAML Assertion. The X.509 Certificate is obtained from the keystore defined in "clientKeystore.properties", with the alias "myclientkey". Finally, the "useCertificateForConfirmationKeyInfo" property of the STSClient means that the entire certificate is to be included in the request, instead of a KeyValue (which is the default):

if (PUBLIC_KEY_KEYTYPE.equals(keyType)) {
        properties.put(SecurityConstants.STS_TOKEN_USERNAME, "myclientkey");
        properties.put(SecurityConstants.STS_TOKEN_PROPERTIES, "clientKeystore.properties");
        stsClient.setUseCertificateForConfirmationKeyInfo(true);
}

Finally, the token type is set on the STSClient (the type of token that is being requested), as well as the KeyType (specific to a SAML Assertion), and a security token is requested, passing the endpoint address which is sent to the STS in the "AppliesTo" element:

        stsClient.setTokenType(tokenType);
        stsClient.setKeyType(keyType);
        return stsClient.requestSecurityToken(endpointAddress);

The returned SecurityToken object contains the received token as a DOM element, the ID of the received token, any reference elements that were returned - which show how to reference the token, any secret associated with the token, and the lifetime of the token.

1 comment:

  1. Hi Colm,

    I'm wondering if it is possible to use the CXF STSClient to exchange a token issued by a trusted third party for a token issued by the CXF STS?

    If you could offer some direction or code on how to accomplish this I would appreciate it.

    ReplyDelete