9.3. Enabling Claims in the STS
Demonstration location
The sample code in this section is taken from an STS system test. If you download and install the source distribution of Apache CXF, you can find the system test Java code under the following directory:
CXFInstallDir/services/sts/systests/advanced/src/test/java/org/apache/cxf/systest/sts
And the system test resource files under the following directory:
CXFInstallDir/services/sts/systests/advanced/src/test/resources/org/apache/cxf/systest/sts
What is a claim?
A claim is an additional piece of data (for example, e-mail address, telephone number, and so on) about a principal, which can be included in a token along with the basic token data. Because this additional data is subject to signing, verification, and authentication, along with the rest of the token, the recipient can be confident that this data is true and accurate.
Requesting claims in an IssuedToken policy
If you want to issue a token with claims embedded, you can add a WS-Trust
Claims
element to the RequestSecurityTokenTemplate
part of the issued token policy, as follows:
<sp:IssuedToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"> <sp:RequestSecurityTokenTemplate> <t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</t:TokenType> <t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey</t:KeyType> <t:Claims Dialect="http://schemas.xmlsoap.org/ws/2005/05/identity" xmlns:ic="http://schemas.xmlsoap.org/ws/2005/05/identity"> <ic:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"/> <ic:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"/> <ic:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/phone" Optional="true"/> </t:Claims> </sp:RequestSecurityTokenTemplate> <wsp:Policy> <sp:RequireInternalReference /> </wsp:Policy> <sp:Issuer> <wsaw:Address>http://localhost:8080/SecurityTokenService/UT</wsaw:Address> </sp:Issuer> </sp:IssuedToken>
By adding the
Claims
element to the RequestSecurityTokenTemplate
element, you ensure that the STS client includes the specified claims in the token issue request that is sent to the STS. The STS responds to this request by retrieving the relevant claim data for the principal and embedding it into the issued token.
Processing claims
Figure 9.9, “Processing Claims” shows an overview of the steps that the STS performs to process claims received in an issue token request.
Figure 9.9. Processing Claims
Steps to process claims
The STS processes claims as follows:
- One of the first things the
TokenIssueOperation
must do is to prepare for parsing the incoming request message.If aClaimsManager
object is registered with theTokenIssueOperation
, theTokenIssueOperation
invokesgetClaimsParsers
on theClaimsManager
instance, to obtain the list of available claims parsers. - The
TokenIssueOperation
initiates parsing of the request message by invoking theparseRequest
method on theRequestParser
object, passing the list ofClaimsParser
objects as one of the arguments toparseRequest
. This ensures that theRequestParser
is capable of parsing anyClaims
elements that might appear in the request message. - If no claims parsers are configured on the claims manager (so that list of claims parsers is
null
), theRequestParser
tries theIdentityClaimsParser
claims parser by default. But theIdentityClaimsParser
is applied to theClaims
element, only if theDialect
attribute of theClaims
element is equal to the identity claims dialect URI. - After parsing the request message, the
TokenIssueOperation
tries to find the appropriate token provider, by callingcanHandleToken
on each of the registered token providers. - In the current scenario, we assume that the client has requested the STS to issue a SAML token, so that the
SAMLTokenProvider
is selected to issue the token. TheTokenIssueOperation
invokescreateToken
on theSAMLTokenProvider
. - Before proceeding to issue the token, the
SAMLTokenProvider
checks whether handlers are available to process all of the non-optional claims. If the required claim handlers are not available, an exception is raised and the SAML token is not issued.For example, in the identity claims dialect, a claim can be tagged as non-optional by setting theOptional
attribute tofalse
on aClaimsType
element in theIssuedToken
policy, as follows:<t:Claims Dialect="http://schemas.xmlsoap.org/ws/2005/05/identity" xmlns:ic="http://schemas.xmlsoap.org/ws/2005/05/identity"> <ic:ClaimType Uri="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email" Optional="false"/> ... </t:Claims>
ImportantIn the identity claims dialect, all claims are required (that is, non-optional) by default. - When specifying the list of SAML attribute statement providers explicitly, it is good practice to include the
DefaultAttributeStatementProvider
instance in the list, so that the default token issuing behavior of theSAMLTokenProvider
is preserved. - In this example, the
CustomAttributeStatementProvider
encapsulates the code that embeds the requisite claim values into the issued SAML token. TheSAMLTokenProvider
invokes thegetStatement
method to obtain the SAML attribute statements containing the required claim values. - The
CustomAttributeStatementProvider
obtains the claim values for the current principal, by invoking theretrieveClaimValues
method on theClaimsManager
object.For example, if the request message included claims for the principal's e-mail address and phone number, it is at this point that the STS actually retrieves the principal's e-mail address and phone number. - The
ClaimsManager
retrieves the claim values by iterating over all of the claims handlers, where each claims handler returns data for as many claims as it can.A claims handler implementation is effectively an intermediate layer between theClaimsManager
and a database. The database stores secure data about each user—such as, address, e-mail, telephone number, department, and so on—which can be used to populate claim values. For example, the database could be an LDAP server and Apache CXF provides anLdapClaimsHandler
class for this scenario—see the section called “The LdapClaimsHandler”. - After retrieving all of the claim values, the
CustomAttributeStatementProvider
proceeds to repackage the claim values as attribute statements, so that they can be embedded in the issued SAML token.
Claim dialects
In order to be as extensible and flexible as possible, the WS-Trust claims mechanism is designed to be pluggable and does not define the syntax of claims. That is, the contents of a WS-Trust
Claims
element is left unspecified by WS-Trust.
The detailed syntax of claims can be defined in third-party specifications, by defining a claim dialect. The Claim element allows you to specify the claim dialect in the
Dialect
attribute, as follows:
<t:Claims Dialect="DialectURI" xmlns:DialectPrefix="DialectURI"> ... </t:Claims>
You can then use the specified dialect to specify claims inside the
Claims
element.
For example, some of the claim dialects defined by the Oasis open standards foundation are as follows:
- Identity claim dialect—defines the kind of data that is typically associated with a user account (for example, address, e-mail, telephone number) and is specified by the Identity Metasystem Interoperability Specification.
- Common claim dialect—(not supported) defines data that is used in WS-Federation and is specified by the WS-Federation Specification. Apache CXF does not provide an implementation of this claims dialect, but you could plug in a custom implementation to the STS, if you wish.
- XSPA claim dialect—(not supported) defines a claim dialect that is used in Cross-Enterprise Security and Privacy Authorization XSPA Specification, which is a security standard used in the context of healthcare organizations. Apache CXF does not provide an implementation of this claims dialect, but you could plug in a custom implementation to the STS, if you wish.
Identity claim dialect
The identity claim dialect is supported by default in Apache CXF. It enables you to request the kind of data fields that are typically stored under a user's LDAP account—for example, address details, telephone number, department, role, and so on. The identity claim dialect is associated with the following dialect URI:
http://schemas.xmlsoap.org/ws/2005/05/identity
You can specify identity claims using the following syntax:
<t:Claims Dialect="http://schemas.xmlsoap.org/ws/2005/05/identity" xmlns:ic="http://schemas.xmlsoap.org/ws/2005/05/identity"> <ic:ClaimType Uri="ClaimTypeURI" Optional="[true|false]"/> ... </t:Claims>
The identity claim dialect defines a single element,
ic:ClaimType
, which has the following attributes:
Uri
- Specifies the type of claim value that you want to include in the issued token. For example, the ClaimTypeURI might identify an e-mail address claim value, a phone number claim value, and so on.
Optional
- Specifies whether or not this particular claim is optional or not. Setting to
true
means that the STS must be capable of populating the issued token with the claim value for the principal, otherwise the token cannot be issued. Default istrue
.
Claim type URIs for the identity claim dialect
The identity claim dialect supports the following claim type URIs:
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
- The subject's first name.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
- The subject's surname.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
- The subject's e-mail address.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress
- The subject's street address.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality
- The subject's locality, which could be a city, county, or other geographic region.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince
- The subject's state or province.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode
- The subject's postal code.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country
- The subject's country.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone
- The subject's home phone number.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone
- The subject's secondary phone number (for example, at work).
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone
- The subject's mobile phone number.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth
- The subject's date of birth.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender
- The subject's gender.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier
- The subject's Private Personal Identifier (PPID). The PPID is described in detail in the Identity Metasystem Interoperability Oasis standard.
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage
- The subject's Web page.
Claims parsers
Because WS-Trust claims have a pluggable architecture, you need a pluggable architecture for parsing claims. The STS allows you to configure a list of claims parsers to customize support for claims. Typically, you register a claims parser for each claim dialect you want to support.
The IdentityClaimsParser
By default, the STS provides a single claims parser implementation: the identity claims parser,
org.apache.cxf.sts.claims.IdentityClaimsParser
, which can parse the identity claim dialect.
You can optionally configure the identity claims parser explicitly, by registering it with the
ClaimsManager
instance. But this is not strictly necessary, because the request parser automatically defaults to the identity claims parser, even if you have not explicitly configured it.
Implementing a custom claims parser
You can extend the claims parsing capability of the STS by implementing a custom claims parser. For this, you would define a custom Java class that implements the following Java interface:
// Java package org.apache.cxf.sts.claims; import org.w3c.dom.Element; public interface ClaimsParser { /** * @param claim Element to parse claim request from * @return RequestClaim parsed from claim */ RequestClaim parse(Element claim); /** * This method indicates the claims dialect this Parser can handle. * * @return Name of supported Dialect */ String getSupportedDialect(); }
Claims handlers
The purpose of a claims handler is to retrieve the requested claim values for the specified principal. Typically, a claims handler is an intermediate layer that looks up claim values in persistent storage.
For example, suppose that an incoming request includes claims for an e-mail address and a phone number (the request claims). When the STS is ready to start populating the issued token with claim values, it calls on the registered claims handlers to retrieve the required claim values for the specified principal. If the principal is the user,
Alice
, for example, the claims handler would contact a database to retrieve Alice's e-mail address and phone number.
The LdapClaimsHandler
Apache CXF provides the claims handler,
org.apache.cxf.sts.claims.LdapClaimsHandler
, which is capable of retrieving claim values from an LDAP server.
Implementing a custom claims handler
You can provide a custom claims handler by defining a class that implements the following Java interface:
// Java
package org.apache.cxf.sts.claims;
import java.net.URI;
import java.security.Principal;
import java.util.List;
import javax.xml.ws.WebServiceContext;
/**
* This interface provides a pluggable way to handle Claims.
*/
public interface ClaimsHandler {
List<URI> getSupportedClaimTypes();
ClaimCollection retrieveClaimValues(
RequestClaimCollection claims,
ClaimsParameters parameters);
@Deprecated
ClaimCollection retrieveClaimValues(
Principal principal,
RequestClaimCollection claims,
WebServiceContext context,
String realm);
}
Configuring the ClaimsManager
The
ClaimsManager
class encapsulates most of the functionality required to support claims and you must configure it if you want to support claims in the STS. In particular, the claims manager encapsulates a list of claims parsers and a list of claims handlers. In practice, if you are using just the identity claims dialect, there is no need to configure the list of claims parsers explicitly; it is sufficient to configure just the list of claims handlers.
For example, the following Spring XML fragment shows how to register a
ClaimsManager
instance with the TokenIssueOperation
bean, where the claims manager is initialized with a claims handler list containing one claims handler, CustomClaimsHandler
.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://cxf.apache.org/configuration/security"
xmlns:http="http://cxf.apache.org/transports/http/configuration"
xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/configuration/security
http://cxf.apache.org/schemas/configuration/security.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://cxf.apache.org/transports/http-jetty/configuration
http://cxf.apache.org/schemas/configuration/http-jetty.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd">
...
<bean id="transportSTSProviderBean"
class="org.apache.cxf.ws.security.sts.provider.SecurityTokenServiceProvider">
<property name="issueOperation" ref="transportIssueDelegate" />
<property name="validateOperation" ref="transportValidateDelegate" />
</bean>
<bean id="transportIssueDelegate" class="org.apache.cxf.sts.operation.TokenIssueOperation">
...
<property name="claimsManager" ref="claimsManager" />
...
</bean>
...
<bean id="claimsManager" class="org.apache.cxf.sts.claims.ClaimsManager">
<property name="claimHandlers" ref="claimHandlerList" />
</bean>
<util:list id="claimHandlerList"> <ref bean="customClaimsHandler" /> </util:list> <bean id="customClaimsHandler" class="org.apache.cxf.systest.sts.deployment.CustomClaimsHandler"> </bean>
...
</beans>
The
CustomClaimsHandler
class is a trivial implementation of a claims handler that appears in one of the STS system tests. For the purposes of the test, it returns a few fixed claim values for a couple of different principals.
Embedding claim values in a SAML token
The key step in processing claims is the point where the STS attempts to issue the token. Whichever token provider is selected to issue the token, it must be capable of inserting the retrieved claim values into the issued token. The token provider must therefore be customized or extended, so that it is capable of embedding the claims in the issued token.
In the case of issuing SAML tokens, the appropriate mechanism for embedding claim values is to generate SAML attribute statements containing the claim values. The appropriate way to extend the SAML token provider, therefore, is to implement a custom
AttributeStatementProvider
class and to register this class with the SAMLTokenProvider
instance (see the section called “SAMLTokenProvider”).
Sample AttributeStatementProvider
Example 9.4, “The CustomAttributeStatementProvider Class” shows a sample implementation of an
AttributeStatementProvider
class, which is capable of embedding claim values in a SAML token. This sample implementation, CustomAttributeStatementProvider
, is taken from the STS system tests, but it is generally quite useful as a starting point for a custom attribute statement provider implementation.
Example 9.4. The CustomAttributeStatementProvider Class
package org.apache.cxf.systest.sts.deployment; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.cxf.sts.claims.Claim; import org.apache.cxf.sts.claims.ClaimCollection; import org.apache.cxf.sts.claims.ClaimsManager; import org.apache.cxf.sts.claims.ClaimsParameters; import org.apache.cxf.sts.token.provider.AttributeStatementProvider; import org.apache.cxf.sts.token.provider.TokenProviderParameters; import org.apache.ws.security.WSConstants; import org.apache.ws.security.saml.ext.bean.AttributeBean; import org.apache.ws.security.saml.ext.bean.AttributeStatementBean; public class CustomAttributeStatementProvider implements AttributeStatementProvider { public AttributeStatementBean getStatement(TokenProviderParameters providerParameters) { // Handle Claims ClaimsManager claimsManager = providerParameters.getClaimsManager(); ClaimCollection retrievedClaims = new ClaimCollection(); if (claimsManager != null) { ClaimsParameters params = new ClaimsParameters(); 1 params.setAdditionalProperties(providerParameters.getAdditionalProperties()); params.setAppliesToAddress(providerParameters.getAppliesToAddress()); params.setEncryptionProperties(providerParameters.getEncryptionProperties()); params.setKeyRequirements(providerParameters.getKeyRequirements()); params.setPrincipal(providerParameters.getPrincipal()); params.setRealm(providerParameters.getRealm()); params.setStsProperties(providerParameters.getStsProperties()); params.setTokenRequirements(providerParameters.getTokenRequirements()); params.setTokenStore(providerParameters.getTokenStore()); params.setWebServiceContext(providerParameters.getWebServiceContext()); retrievedClaims = claimsManager.retrieveClaimValues( 2 providerParameters.getRequestedClaims(), params ); } if (retrievedClaims == null) { return null; } Iterator<Claim> claimIterator = retrievedClaims.iterator(); if (!claimIterator.hasNext()) { return null; } List<AttributeBean> attributeList = new ArrayList<AttributeBean>(); String tokenType = providerParameters.getTokenRequirements().getTokenType(); AttributeStatementBean attrBean = new AttributeStatementBean(); 3 while (claimIterator.hasNext()) { Claim claim = claimIterator.next(); AttributeBean attributeBean = new AttributeBean(); 4 URI name = claim.getNamespace().relativize(claim.getClaimType()); if (WSConstants.WSS_SAML2_TOKEN_TYPE.equals(tokenType) || WSConstants.SAML2_NS.equals(tokenType)) { attributeBean.setQualifiedName(name.toString()); attributeBean.setNameFormat(claim.getNamespace().toString()); } else { attributeBean.setSimpleName(name.toString()); attributeBean.setQualifiedName(claim.getNamespace().toString()); } attributeBean.setAttributeValues(Collections.singletonList(claim.getValue())); 5 attributeList.add(attributeBean); } attrBean.setSamlAttributes(attributeList); return attrBean; } }
- 1
- The first part of the
getStatement
method implementation is centered around the invocation of theClaimsManager.retrieveClaimValues
method.In preparation for invoking theretrieveClaimValues
method, you populate theClaimsParameters
object, which encapsulates most of the parameters needed to invokeretrieveClaimValues
. TheClaimsParameters
object is initialized simply by copying the relevant parameters from theTokenProviderParameters
object. - 2
- Invoke the
retrieveClaimValues
method on the claims manager instance. This has the effect of retrieving the requested claim values from persistent storage, with the help of the claims handlers plug-ins (see Figure 9.9, “Processing Claims”). - 3
- The
AttributeStatementBean
class is a WSS4J class that is used to encapsulate a SAML attribute statement. - 4
- The WSS4J
AttributeBean
class encapsulates a single SAML attribute. - 5
- Each claim value is inserted into an
AttributeBean
instance.
Configuring the custom AttributeStatementProvider
The custom attribute statement provider can be installed into the
SAMLTokenProvider
instance, as follows:
<beans ...> ... <bean id="transportSTSProviderBean" class="org.apache.cxf.ws.security.sts.provider.SecurityTokenServiceProvider"> <property name="issueOperation" ref="transportIssueDelegate" /> <property name="validateOperation" ref="transportValidateDelegate" /> </bean> <bean id="transportIssueDelegate" class="org.apache.cxf.sts.operation.TokenIssueOperation"> ... <property name="tokenProviders" ref="transportTokenProviders" /> <property name="claimsManager" ref="claimsManager" /> ... </bean> <util:list id="transportTokenProviders"> <ref bean="transportSamlTokenProvider" /> ... </util:list> ... <bean id="transportSamlTokenProvider" class="org.apache.cxf.sts.token.provider.SAMLTokenProvider"> <property name="attributeStatementProviders" ref="attributeStatementProvidersList" /> </bean> <util:list id="attributeStatementProvidersList"> <ref bean="defaultAttributeProvider" /> <ref bean="customAttributeProvider" /> </util:list> <bean id="defaultAttributeProvider" class="org.apache.cxf.sts.token.provider.DefaultAttributeStatementProvider"> </bean> <bean id="customAttributeProvider" class="org.apache.cxf.systest.sts.deployment.CustomAttributeStatementProvider"> </bean> ... </beans>
Note that a
DefaultAttributeStatementProvider
instance should also be included in the list of attribute statement providers, so that the issued SAML token also includes the default attribute statement.