The previous blog post detailed the AbstractOperation class that provides some core functionality for the STS operations, such as parsing the client request into a format that the TokenProviders/TokenValidators/TokenCancellers can consume. In this post we will examine an extension of AbstractOperation that is used to issue tokens.
1) The TokenIssueOperation
TokenIssueOperation is used to issue tokens in the STS. It implements the IssueOperation and IssueSingleOperation interfaces in the STS provider framework. In addition to the properties that it inherits from AbstractOperation (detailed in the previous post), it has a single property that can be configured:
- ClaimsManager claimsManager - An object that is used to handle claims. This will be covered in a future post.
Recall that AbstractOperation uses the RequestParser to parse a client request into TokenRequirements and KeyRequirements objects. TokenIssueOperation populates a TokenProviderParameters object with values extracted from the TokenRequirements and KeyRequirements objects. A number of different processing steps then occur before a TokenProvider implementation is used to retrieve the desired token, comprising of realm parsing, claims handling, and AppliesTo parsing. Claims handling will be discussed in the next article.
1.1) Realm Parsing
We have discussed how realms are used with TokenProviders to provide tokens, and also how they work with TokenValidators to validate a given token. However, we did not cover how realms are defined in the first place. Recall that the STSPropertiesMBean configuration object defined on AbstractOperation has a RealmParser property. The RealmParser is an interface which defines a pluggable way of defining a realm for the current request. It has a single method:
- String parseRealm(WebServiceContext context) - Return the realm of the current request given a WebServiceContext object.
Therefore if you wish to issue tokens in multiple realms, it is necessary to create an implementation of the RequestParser which will return a realm String given a context object. For example, different realms could be returned based on the endpoint URL or a HTTP parameter. This realm will then get used to select a TokenProvider implementation to use to issue a token of the desired type. It will also be used for token validation in a similar way.
1.2) AppliesTo parsing
An AppliesTo element contains an address that refers to the recipient of the issued token. If an AppliesTo element was sent as part of the request then the CXF STS requires that it must be explicitly handled. This is done by the list of ServiceMBean objects that can be configured on AbstractOperation. The ServiceMBean interface represents a service, and has the following methods (amongst others):
- boolean isAddressInEndpoints(String address) - Return true if the supplied address corresponds to a known address for this service.
- void setEndpoints(List<String> endpoints) - Set the list of endpoint addresses that correspond to this service.
The STS ships with a single implementation of this interface, the StaticService. For the normal use-case of handling an AppliesTo element, the user creates a StaticService object and calls setEndpoints with a set of Strings that correspond to a list of regular expressions that match the allowable set of token recipients (by address). The TokenIssueOperation will extract the URL address from the EndpointReference child of the received AppliesTo element, and then iterate through the list of ServiceMBean objects and ask each one whether the given address is known to that ServiceMBean object. If an AppliesTo address is received, and no ServiceMBean is configured that can deal with that URL, then an exception is thrown.
The ServiceMBean also defines a number of optional configuration options, such as the default KeyType and TokenType Strings to use for that Service, if the client does not supply them. It also allows the user to set a custom EncryptionProperties object, which defines a set of acceptable encryption algorithms to use to encrypt issued tokens for that service.
2) Token creation and response
Once the TokenIssuerOperation has processed the client request, it iterates through the list of defined TokenProvider implementations to see if each "can handle" the desired token type in the configured realm (if any). If no TokenProvider is defined, or if no TokenProvider can handle the desired token type, then an exception is thrown. Otherwise, a token is created, and a response object is constructed containing the following items:
- The context attribute (if any was specified).
- The Token Type.
- The requested token (possibly encrypted, depending on configuration).
- A number of references to that token (can be disabled by configuration).
- The received AppliesTo address (if any).
- The RequestedProofToken (if a Computed Key Algorithm was used).
- The Entropy generated by the STS (if any, can be encrypted).
- The lifetime of the generated token.
- The KeySize that was used (if any).
3) TokenIssueOperation Example
Finally, it's time to look at an example of how to spring-load the STS so that it can issue tokens. This particular example uses a security policy that requires a UsernameToken over the symmetric binding. As the STS is a web service, we first define an endpoint:
<jaxws:endpoint id="UTSTS" implementor="#utSTSProviderBean" address="http://.../SecurityTokenService/UT" wsdlLocation=".../ws-trust-1.4-service.wsdl" xmlns:ns1="http://docs.oasis-open.org/ws-sx/ws-trust/200512/" serviceName="ns1:SecurityTokenService" endpointName="ns1:UT_Port"> <jaxws:properties> <entry key="ws-security.callback-handler" value="..."/> <entry key="ws-security.signature.properties" value="stsKeystore.properties"/> </jaxws:properties> </jaxws:endpoint>
The jaxws:properties are required to parse the incoming message. The CallbackHandler is used to validate the UsernameToken and provide the password required to access the private key defined in the signature properties parameter. The "implementor" of the jaxws:endpoint is the SecurityTokenServiceProvider class defined in the STS provider framework:
<bean id="utSTSProviderBean" class="org.apache.cxf.ws.security.sts.provider.SecurityTokenServiceProvider"> <property name="issueOperation" ref="utIssueDelegate"/> ... </bean>
This bean supports the Issue Operation via a TokenIssueOperation instance:
<bean id="utIssueDelegate" class="org.apache.cxf.sts.operation.TokenIssueOperation"> <property name="tokenProviders" ref="utSamlTokenProvider"/> <property name="services" ref="utService"/> <property name="stsProperties" ref="utSTSProperties"/> </bean>
This TokenIssueOperation instance has a single TokenProvider configured to issue SAML Tokens (with a default Subject and Attribute statement):
<bean id="utSamlTokenProvider" class="org.apache.cxf.sts.token.provider.SAMLTokenProvider"> </bean>
The TokenIssueOperation also refers to a single StaticService implementation, which in turn defines a single URL expression to use to compare any received AppliesTo addresses:
<bean id="utService" class="org.apache.cxf.sts.service.StaticService"> <property name="endpoints" ref="utEndpoints"/> </bean>
<util:list id="utEndpoints"> <value>http://localhost:(\d)*/(doubleit|metrowsp)/services/doubleit(UT|.*symmetric.*|.*)</value> </util:list>
Finally, the TokenIssueOperation is configured with a StaticSTSProperties object. This class contains properties that define what private key to use to sign issued SAML tokens, as well as the Issuer name to use in the generated token.
<bean id="utSTSProperties" class="org.apache.cxf.sts.StaticSTSProperties"> <property name="signaturePropertiesFile" value="stsKeystore.properties"/> <property name="signatureUsername" value="mystskey"/> <property name="callbackHandlerClass" value="..."/> <property name="issuer" value="DoubleItSTSIssuer"/> ... </bean>
Is there a way to configure the CXF/Talend STS to use a custom UsernameTokenValidator?
The reason I ask is that I would like to call an external identity store to validate the username/password. The identity store has an interface which accepts a username and password and returns true of false depending on whether the password is correct or not.
So far the only way I know to affect the behavior of the UsernameTokenValidtor is to specify a callback handler. Unfortunately, the default UsernameTokenValidator does not explicitly pass the password received from the username token to the callback handler and the identity store I am trying to work with will not return the password associated with the id (it only validates a username/password pair).
While I can create a solution using the username callback handler but it is not very pretty. The solution takes advantage of the fact that while the default UsernameTokenValidator does not explicitly pass the password in the username token, it does pass the entire contents of the soap message as part of the "data" property. From that data, I can extract the SOAP message, then the WSSecurity header, then the username token, and finally the password.
What I don't like about this solution is that the process of extracting the username and password is already performed by the code that calls the callback handler. I'm doing it a second time in the callback handler which seems wasteful.
Thus, I'm wondering if I can create my own UsernameTokenValidtor and overrride the one that the CXF STS uses by default.
Yes, you can inject your custom WSS4J Validator implementation into the STS's UsernameTokenValidator via the "setValidator" method. See here:
I see that. But am I right in assuming that I could not reuse the DefaultSecurityTokenServiceProvider if I wanted to call my own validator?Delete
It looks like I would have to create my own version of DefaultSecurityTokenServiceProvider that sets the custom validator in the populateAbstractOperation() method.
Correct. The DefaultSecurityTokenServiceProvider is just an easy way to set up the STS for most use-cases. You can just create an STS using the more configurable way.
Unfortunately, this does not work as expected. I've created a new DefaultSecurityTokenProvider class that is identical to the one provided by CXF except that when I create the UsernameTokenValidtor I set the validator to a custom implementation. However, it seems like it still uses the default validator.Delete
Am I going about this the wrong way?
To add to this. I don't even see where the org.apache.cxf.sts.token.validator.UsernameTokenValidator is ever used. In fact, in the debugger it looks like the only validator ever called when submitting an RST with a username token is org.apache.ws.security.validate.UsernameTokenValidtor. This despite the fact that I am using the CXF DefaultSecurityTokenProvider.Delete
I am truly confused.
Please take these questions to the CXF user list instead.