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.
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
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.
- String getRealmFromToken(UsernameToken usernameToken) - Get the realm associated with the UsernameToken parameter.
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.
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.
- String getRealmFromToken(AssertionWrapper assertion) - Get the realm associated with the (SAML Assertion) parameter.
I am getting the following error while trying to have CXF consume a SAML token. What could be the cause of this error?
ReplyDeleteCaused by: org.apache.ws.security.WSSecurityException: General security error (Provided SAML token does not contain a suitable key)
at org.apache.ws.security.validate.SamlAssertionValidator.validate(SamlAssertionValidator.java:61)
at org.apache.ws.security.processor.SAMLTokenProcessor.handleSAMLToken(SAMLTokenProcessor.java:118)
at org.apache.ws.security.processor.SAMLTokenProcessor.handleToken(SAMLTokenProcessor.java:53)
at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:396)
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:249)
By the way, I am using CXF 2.5.0 for the server while the client is .NET.
Hi Dan,
ReplyDeleteI already answered your question on dev@cxf here:
http://cxf.547215.n5.nabble.com/Re-General-security-error-Provided-SAML-token-does-not-contain-a-suitable-key-td4990489.html
Colm.
Thanks. I replied to the cxf-dev forum post.
ReplyDeleteHi,
ReplyDeleteI am sending a username token and then validating the username token. On validation of user name token STS provides me SAML token. everything is working fine but i am not able to see SAML token in my response from STS . However i am able to see username token. I want to see SAML token. Please help.
Hi,
ReplyDeleteI suggest posting a more in-depth explanation of your use-case to the CXF users mailing list.
Colm.
Colm:
ReplyDeleteI have a similar issue (as above). I am using PicketLinkSTS to issue a token for a secure service (that is using MEX tag). The problem appears to be inside the Secure Service, at the point the token is issued and returned from the STS. I stepped through the classes and they match up with your explanation above. However I am not sure why I'd get the following exception
Caused by: org.apache.ws.security.WSSecurityException: General security error (Unable to load class org.apache.ws.security.validate.SamlAssertionValidator)
at org.apache.ws.security.WSSConfig.getValidator(WSSConfig.java:765) [wss4j-1.6.5.jar:1.6.5]
at org.apache.ws.security.handler.RequestData.getValidator(RequestData.java:451) [wss4j-1.6.5.jar:1.6.5]
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor$CXFRequestData.getValidator(WSS4JInInterceptor.java:692) [cxf-rt-ws-security-2.4.6.jar:2.4.6]
at org.apache.ws.security.processor.SAMLTokenProcessor.handleToken(SAMLTokenProcessor.java:51) [wss4j-1.6.5.jar:1.6.5]
at org.apache.ws.security.WSSecurityEngine.processSecurityHeader(WSSecurityEngine.java:397) [wss4j-1.6.5.jar:1.6.5]
at org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor.handleMessage(WSS4JInInterceptor.java:249) [cxf-rt-ws-security-2.4.6.jar:2.4.6]
... 28 more
Caused by: java.lang.NoClassDefFoundError: org/opensaml/xml/validation/ValidationException
at java.lang.Class.getDeclaredConstructors0(Native Method) [rt.jar:1.7.0_21]
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2413) [rt.jar:1.7.0_21]
at java.lang.Class.getConstructor0(Class.java:2723) [rt.jar:1.7.0_21]
at java.lang.Class.newInstance0(Class.java:345) [rt.jar:1.7.0_21]
at java.lang.Class.newInstance(Class.java:327) [rt.jar:1.7.0_21]
at org.apache.ws.security.WSSConfig.getValidator(WSSConfig.java:760) [wss4j-1.6.5.jar:1.6.5]
I've been pointed to the last comment by Colm; if the stacktrace is from JBoss AS 7, I basically suggest upgrading to a newer version / to WildFly, basically because at the time Apache CXF 2.4.x was used in the JBoss integration with CXF, the SAML and WS-Trust functionalities were not tested / working.
ReplyDeleteBtw, I believe this is related to the JBoss forum thread I just comment on at https://community.jboss.org/message/844345#844345