Friday, April 20, 2012

Renewing SAML Tokens in the Apache CXF STS

Apache CXF 2.6.0 sees a number of improvements to the functionality of the SecurityTokenService (STS). These include relatively minor tasks such as supporting SymmetricKeys for Entropy and BinarySecret Elements, supporting SecurityTokenReferences in UseKey Elements, and supporting KeyInfo/KeyValue Elements in UseKey Elements. However, the STS also includes two major new features. The first of these new features, the ability to renew (SAML) tokens, is covered in this blog entry.

1) The TokenRenewer interface

Security tokens are renewed in the STS via the TokenRenewer interface. It has the following methods:
  • void setVerifyProofOfPossession(boolean verifyProofOfPossession) - A boolean switch to enable or disable the proof of possession requirement.
  • void setAllowRenewalAfterExpiry(boolean allowRenewalAfterExpiry) - A switch to enable or disable the ability to renew tokens after they have expired.
  • boolean canHandleToken(ReceivedToken renewTarget) - Whether this TokenRenewer implementation can renew the given token.
  • boolean canHandleToken(ReceivedToken renewTarget, String realm) - Whether this TokenRenewer implementation can renew the given token in the given realm.
  • TokenRenewerResponse renewToken(TokenRenewerParameters tokenParameters) - Renew the token using the given parameters
A client can request that the STS renew a security token by invoking the "renew" operation and supplying a token under the "RenewTarget" Element. Assuming that the client request is authenticated and well-formed, the STS will first iterate through a list of TokenValidator implementations to see if they can "handle" the received token. If they can, then the implementation is used to validate the received security token. If no TokenValidator is found that can handle the RenewTarget that was received, then an exception is thrown. Note that this means that for token renewal, it is necessary to configure both a TokenValidator and TokenRenewer implementation that can handle the given token.

After the successful validation of a token, the state of the token is checked. If the state is not valid or expired, then an exception is thrown. The STS then iterates through the configured list of TokenRenewer implementations to see which can renew the given (validated) token. The token is then renewed and returned to the client.

The TokenRenewerParameters class is nothing more than a collection of configuration properties to use in renewing the token, which are populated by the STS operations using information collated from the request, or static configuration, etc. The TokenRenewerResponse class holds the results from the (successful) token renewal, including the DOM representation of the renewed token, the token Id, the new lifetime of the renewed token, and references to the renewed token. 

2) The SAMLTokenRenewer

The SAMLTokenRenewer can renew valid or expired SAML 1.1 and SAML 2.0 tokens. The following properties can be configured on the SAMLTokenRenewer directly:
  • boolean signToken - Whether to sign the renewed token or not. The default is true.
  • ConditionsProvider conditionsProvider - An object used to add a Conditions statement to the token.
  • Map<String, SAMLRealm> realmMap - A map of realms to SAMLRealm objects. See here for an explanation of realms in the SAMLTokenProvider.
  • long maxExpiry - how long a token is allowed to be expired (in seconds) before renewal. The default is 30 minutes.
The SAMLTokenRenewer first checks that the token it extracts from the TokenRenewerParameters is in an expired or valid state, if not it throws an exception. It then retrieves the cached token that corresponds to the token to be renewed. A cache must be configured to use the SAMLTokenRenewer, and the token to be renewed must be in the cache before renewal takes place, for reasons that will become clear in the next section.

2.1) Token validation

Before the received SAML token can be renewed, a number of validation steps (that are specific to renewing SAML tokens) takes place. Two boolean properties are retrieved from the properties of the cached token:
  • org.apache.cxf.sts.token.renewing.allow - Whether the token is allowed to be renewed or not.
  • org.apache.cxf.sts.token.renewing.allow.after.expiry - Whether the token is allowed to be renewed or not after it has expired.
These two properties are set in the SAMLTokenProvider based on a received "<wst:Renewing/>" element when the user is requesting a SAML token via the issue binding. If a user omits a "<wst:Renewing/>" element, or sends "<wst:Renewing/>" or "<wst:Renewing Allow="true"/>", then the token is allowed to be renewed. However, only if the user sends <wst:Renewing OK=True"/>", will the token be allowed to be renewed after expiry. This explains why a TokenStore is required for token renewal, as without access to these two properties it is impossible for the SAMLTokenRenewer to figure out whether the issuer of the token intended for the token to be renewed (after expiry) or not.

If the state of the token is expired, and if the token is allowed to be renewed after expiry, a final check is done against the boolean set via the "setAllowRenewalAfterExpiry" method of TokenRenewer. If this is set to false (the default), then an exception is thrown. So to support token renewal after expiry, you must explicitly define this behaviour on the TokenRenewer implementation. Finally, a check is done on how long ago the SAML Token expired. If it is greater than the value configured in the "maxExpiry" property (30 minutes by default), then an exception is thrown.

The next validation step is to check proof of possession, if this is enabled (true by default). The Subject KeyInfo of the Assertion must contain a PublicKey or X509Certificate that corresponds to either the client certificate if TLS is used, or to the private key that was used to sign some part of the request. Finally, if an "AppliesTo" URI is sent as part of the request, the SAMLTokenRenewer checks that the received Assertion contains at least one AudienceRestrictionURI that matches that address, otherwise it throws an Exception.

2.2) Renewing the SAML Assertion

After the validation steps outlined above have passed, the token is renewed in the following way:
  • A new ID is generated for the token.
  • A new "IssueInstant" is set on the token.
  • A new Conditions Element replaces the old Conditions Element of the token, using the configured ConditionsProvider.
  • The Assertion is (re)-signed if the "signToken" property is true.
The old token is removed from the cache, and the new token is added. Finally, the token is set on the TokenRenewerResponse, along with the token Id, and Lifetime.

3) SAML Token Renewal in action

Finally, let's take a look at a system test in CXF that shows how to renew a SAML Token issued by an STS. The wsdl of the service provider defines a number of endpoints which use the transport binding, with a (endorsing) supporting token requirement which has an IssuedToken policy that requires a SAML token. In other words, the client must request a SAML token from an STS and send it to the service provider over TLS, and optionally use the secret associated with the SAML token to sign the message Timestamp (if an EndorsingSupportingToken policy is specified in the wsdl).

The STS spring configuration is available here. The SAMLTokenRenewer is configured with proof-of-possession enabled, and tokens are allowed to be renewed after they have expired. Let's look at the test code and client configuration. All of the tests follow the same pattern. The client requests a SAML Token from the STS (as per the IssuedToken policy), with a TTL (time-to-live) value of 8 seconds. The client then uses this issued token to make a successful request to the service provider. The test code then sleeps for 8 seconds to expire the token, and tries to invoke on the service provider again. The IssuedTokenInterceptorProvider in the WS-Security runtime in CXF recognises that the token has expired, and sends it to the STS for renewal. The returned (renewed) token is then sent to the service provider.

2 comments:

  1. Hi Colm,

    I have a question that relates to your statement: "Assuming that the client request is authenticated and well-formed". I'm interpreting this as meaning that the request to renew the token must itself contain some valid token in the WS-Security header that enables successful authentication and authorization with the STS for the purpose of token renewal.

    For example, I'm assuming that a service wishing to renew a token (perhaps so it could propagate it to another service) could make the request using a BST tied to its certificate credentials. Is this correct?

    Similarly, I am assuming that you could use another valid SAML token to renew an expired SAML token. Is this also correct?

    Thank you, in general, for the great work and support for this feature.

    ReplyDelete

  2. Yes, this interpretation is correct. You can authenticate using any token that conforms to the WS-SecurityPolicy requirement of the STS port that is in operation. However, you must present the token to be renewed in either the security header (+ reference it in the RST), or else put the token in the RST directly.

    So yes, you could in theory use (another) valid SAML Token to renew a SAML Token, by placing the valid SAML Token in the security header, and putting the expired token to be renewed in the RST. You would need to disable proof of possession I guess in the SAMLTokenRenewer as well.

    Colm.

    ReplyDelete