In the previous
post, I covered the TokenValidator interface that is used by the CXF STS to validate tokens, and two implementations that ship with the STS to validate SecurityContextTokens and BinarySecurityTokens (X509 Certificates). In this post I will cover the other two TokenValidator implementations that ship with the STS, which can validate UsernameTokens and SAML Tokens (both 1.1 and 2.0).
1) The UsernameTokenValidator
The
UsernameTokenValidator is used to validate WS-Security UsernameTokens. Two properties can be set directly on the UsernameTokenValidator:
- void setValidator(Validator validator) - Set the WSS4J Validator instance to use to validate the received UsernameToken. The default is the UsernameTokenValidator (note that this is in WSS4J and not the same as the UsernameTokenValidator in the STS!).
- void setUsernameTokenRealmCodec(UsernameTokenRealmCodec usernameTokenRealmCodec) - Set the UsernameTokenRealmCodec instance to use to return a realm from a validated token. This will be explained later.
The UsernameToken is first checked to make sure that it is well-formed. If it has no password element then it is rejected. If a cache is configured, then it sees if the UsernameToken has been previously stored in the cache (searching by wsu:Id). If it is, and if the retrieved SecurityToken has an "associated hash" corresponding to the hashcode of the received UsernameToken, then the received UsernameToken has already been validated, and no further validation is required. It is necessary to check the "associated hash" value, in case two different UsernameTokens are being compared that have the same wsu:Id. Note that the CXF STS does not have a UsernameTokenProvider as of yet, so for this use-case perhaps the cache is shared with a custom TokenProvider.
If the token is not stored in the cache, then the WSS4J Validator instance is used to validate the received UsernameToken. As stated above, the default implementation that is used is the
UsernameTokenValidator in WSS4J. This implementation uses a CallbackHandler to supply a password to validate the UsernameToken. This CallbackHandler implementation is supplied by the STSPropertiesMBean object. WSS4J also ships with an
implementation that validates a UsernameToken via a JAAS LoginModule, which can be plugged in to the STS UsernameTokenValidator. If validation is successful, then a principal is created from the received UsernameToken and set on the response.
2) Realms in the TokenValidators
Recall that the TokenValidator interface has a method that takes a realm parameter:
- boolean canHandleToken(ReceivedToken validateTarget, String realm) -
Whether this TokenValidator implementation can validate the given token
in the given realm
How the STS knows what the desired realm is will be covered in a
future post.
Realms are handled in a slightly different way in TokenValidators compared to TokenProviders. Recall that for
TokenProviders, the implementation is essentially asked whether it can provide a token in a given realm. For the SCTProvider, the realm is ignored in this method. However, when creating a token, the SCTProvider will store the given realm as a property associated with that token in the cache. The SAMLTokenProvider checks to see if the given realm is null, and
if it is not null then the realmMap *must* contain a key which matches
the given realm.
There is a subtle distinction between the realm passed to "canHandleToken" for TokenValidators and the realm returned after a token is validated as part of the TokenValidatorResponse object. The realm passed to "canHandleToken" is the realm to validate the token in. So for example, you could have two TokenValidator instances registered to validate the same token, but in different realms. All of the TokenValidator implementations that ship with the STS ignore the realm as part of this method. However, the method signature gives the user the option to validate tokens in different realms in a more flexible manner.
The realm that is returned as part of the TokenValidatorResponse is the realm that the validated token is in (if any). This can be different to the realm the token was validated in. The X509TokenValidator ignores this parameter altogether. The SCTValidator checks to see whether the SecurityToken that was stored in the cache has a realm property, and if so sets this on the TokenValidatorResonse. The UsernameTokenValidator and SAMLTokenValidator handle realms in a more sophisticated manner. I will cover the UsernameTokenValidator here, and the SAMLTokenValidator later. Recall that the UsernameTokenValidator has the following method:
- void setUsernameTokenRealmCodec(UsernameTokenRealmCodec usernameTokenRealmCodec) - Set the UsernameTokenRealmCodec instance to use to return a realm from a validated token.
The UsernameTokenRealmCodec has a single method:
- String getRealmFromToken(UsernameToken usernameToken) - Get the realm associated with the UsernameToken parameter.
No UsernameTokenRealmCodec implementation is set by default on the UsernameTokenValidator, hence no realm is returned in TokenValidatorResponse. If an implemention is specified, then the UsernameTokenValidator will retrieve a realm from the UsernameTokenRealmCodec implementation corresponding to the validated UsernameToken. If a cache is configured, and the UsernameToken was already stored in the cache, then the realm is compared to the realm of the cached token, stored under the tag "org.apache.cxf.sts.token.realm".
If they do not match then validation fails.
3) The SAMLTokenValidator
The
SAMLTokenValidator is used to validate SAML (1.1 and 2.0) tokens. The following properties can be set directly on the SAMLTokenValidator:
- void setValidator(Validator validator) - Set the WSS4J Validator instance to use to validate the received certificate. The default is SignatureTrustValidator.
- void setSamlRealmCodec(SAMLRealmCodec samlRealmCodec) - Set the SAMLRealmCodec instance to use to return a realm from a validated token.
- void setSubjectConstraints(List<String> subjectConstraints) - Set a list of Strings corresponding to regular expression constraints on the subject DN of a certificate that was used to sign an Assertion.
These methods are covered in more detail below. The Assertion is first checked to make sure that it is well-formed. If a cache is defined, then the signature value of the received assertion (if it is signed) is hashed and compared against the tokens in the cache (via getTokenByAssociatedHash()). If a match is found in the cache, then the Assertion is taken to be valid. If a match is not found, then the Assertion is validated.
3.1) Validating a received SAML Assertion
If the token is not stored in the cache then it must be validated. Firstly a check is performed to make sure that the Assertion is signed, if it is not then it is rejected. The signature of the Assertion is then validated using the Crypto object retrieved from the
STSPropertiesMBean passed in the TokenValidatorParameters. Finally, trust is verified in the certificate/public-key used to sign the Assertion. This is done using the
Validator object that can be configured via "setValidator". The default Validator is the WSS4J
SignatureTrustValidator, which checks that the received certificate is known (or trusted) by the STS Crypto object.
Recall that a List of Strings can be set on the SAMLTokenValidator via the "setSubjectConstraints" method. These Strings correspond to regular expression constraints on
the subject DN of a certificate that was used to sign an Assertion. This provides additional flexibility to validate a received SAML Assertion. For example, the Assertion could be signed by an entity that has a certificate issued by a particular CA, which in turn is trusted by the STS Crypto object. However, one might want to restrict the list of "valid" entities who can sign a SAML Assertion. This can be done by adding a list of regular expressions that match the Subject DN of all acceptable certificates that might be used to sign a valid SAML Assertion. This matching is done by the
CertConstraintsParser.
3.2) Realm handling in the SAMLTokenValidator
Recall that the SAMLTokenValidator has the following method:
- void setSamlRealmCodec(SAMLRealmCodec samlRealmCodec) - Set the SAMLRealmCodec instance to use to return a realm from a validated token.
The SAMLRealmCodec has a single method:
- String getRealmFromToken(AssertionWrapper assertion) - Get the realm associated with the (SAML Assertion) parameter.
No SAMLRealmCodec implementation is set by default on
the SAMLTokenValidator, hence no realm is returned in
TokenValidatorResponse. If an implemention is specified, then the SAMLTokenValidator will retrieve a realm from theSAMLRealmCodec implementation corresponding to the validated Assertion. If a cache is configured, and the Assertion was
already stored in the cache, then the realm is compared to the realm of
the cached token, stored under the tag "org.apache.cxf.sts.token.realm".
If they do not match then validation fails.