The previous blog post introduced the TokenIssueOperation, which is used to issue tokens in the STS. The article covered the various processing steps that the TokenIssueOperation does on the parsed request, before it calls a TokenProvider instance to provide a token that is returned to the client. One step that was not covered was claims handling, as this is a complex topic that merits a deeper discussion. In this post, we will look at how claims are handled in the STS as a whole.1) Claims Handling in the STS
A typical scenario for WS-Trust is when the client requires a particular security token from an STS to access a service provider. The service provider can let the client know what the requirements are for the security token in an IssuedToken policy embedded in the WSDL of the service. In particular, the service provider can advertise the claims that the security token must contain in the policy (either directly as a child element of IssuedToken, or else as part of the RequestSecurityTokenTemplate). An example is contained in the STS systests:
<sp:RequestSecurityTokenTemplate> <t:TokenType>http://...#SAMLV1.1</t:TokenType> <t:KeyType>http://.../PublicKey</t:KeyType> <t:Claims Dialect="http://.../identity"> <ic:ClaimType Uri="http://.../claims/role"/> </t:Claims> </sp:RequestSecurityTokenTemplate>
This template specifies that a SAML 1.1 Assertion is required with an embedded X509 Certificate in the subject of the Assertion. The issued Assertion must also contain a "role" claim. The template is sent verbatim by the client to the STS when requesting a security token.1.1) Parsing claims
The RequestParser object parses the client request into TokenRequirements and KeyRequirements objects. As part of this processing it converts a received Claims element into a RequestClaimCollection object. The RequestClaimCollection is just a list of RequestClaim objects, along with a dialect URI. The RequestClaim object holds the claimType URI as well as a boolean indicating whether the claim is optional or not.1.2) The ClaimsHandler
The ClaimsHandler is an interface that the user must implement to be able to "handle" a requested claim. It has two methods:
getSupportedClaimTypes() - Return the list of ClaimType URIs that this ClaimHandler object can handle.
- ClaimCollection retrieveClaimValues(Principal principal, RequestClaimCollection claims) - Return the claim values associated with the requested claims (and client principal).
The ClaimCollection object that is returned is just a list of Claim objects. This object represents a Claim that has been processed by a ClaimsHandler instance. It essentially contains a number of properties that the ClaimsHandler implementation will set, e.g.:
- URI claimType - The claimtype URI as received from the client.
- String value - The claim value
Each Claim object in a ClaimCollection corresponds to a RequestClaim object in the RequestClaimCollection, and contains the Claim value corresponding to the requested claim. How the ClaimsHandler is invoked to create the ClaimCollection will be covered later. The STS ships with a single ClaimsHandler implementation, the LDAPClaimsHandler, which can retrieve claims from an LDAP store. A simpler example is available in the unit tests.1.3) The ClaimsManager
Recall that in the previous post, the TokenIssueOperation has a single additional property that can be configured:
- ClaimsManager claimsManager - An object that is used to handle claims.
The ClaimsManager holds a list of ClaimsHandler objects. So to support claim handling in the STS, it is necessary to implement one or more ClaimsHandler objects for whatever Claim URIs you wish to support, and register them with a ClaimsManager instance, which will be configured on the TokenIssueOperation object.
As detailed in the previous article, the TokenIssueOperation gets the realm of the current request, and does some processing of the AppliesTo address, after the RequestParser has finished parsing the request. The RequestClaimCollection object that has been constructed by the RequestParser is then processed. For each RequestClaim in the collection, it checks to see whether the ClaimsManager has a ClaimsHandler implementation registered that can "handle" that Claim (by checking the URIs). If it does not, and if the requested claim is not optional, then an exception is thrown.
If a ClaimsHandler implementation is registered with the ClaimsManager that can handle the desired claim, then the claims are passed through to the TokenProvider implementation, which is expected to be able to invoke the relevant ClaimHandler object, and insert the processed Claim into the generated security token. How this is done is entirely up to the user. For example, for the use-case given above of a SAML 1.1 token containing a "role" claim, the user could implement a custom AttributeStatementProvider instance that evaluates the claim values (via a custom ClaimsHandler implementation registered with the ClaimsManager) and constructs a set of Attributes accordingly in an AttributeStatement. An example of how to do this is given in the unit tests.