1) Running the Test
To run the SAML system test you can do the following:
svn co https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.0/systests/ws-security
mvn test -Dtest=SamlTokenTest
2) The Client
2.1) The Client code
You can view the source of the tests here. There are a number of tests involving creating SAML 1.1 and 2.0 assertions, and sending them to a service provider over various security bindings (Transport/Symmetric/Asymmetric). To simplify things, we will focus on the fourth test named "testSaml2OverAsymmetric". Minus some negative tests, the basic test client invocation code is as simple as:
SpringBusFactory bf = new SpringBusFactory();
URL busFile = SamlTokenTest.class.getResource("client/client.xml");
Bus bus = bf.createBus(busFile.toString());
DoubleItService service = new DoubleItService();
DoubleItPortType saml2Port = service.getDoubleItSaml2AsymmetricPort();
"ws-security.saml-callback-handler", new SamlCallbackHandler()
BigInteger result = saml2Port.doubleIt(BigInteger.valueOf(25));
2.2) The WSDL
The service is described in the WSDL here. Take a look at the WS-SecurityPolicy called "DoubleItSaml2AsymmetricPolicy", which defines the security requirements for the "DoubleItSaml2AsymmetricPort". It defines an Asymmetric Binding, where the InitiatorToken (which defines the credential used to sign the request) is always sent to the recipient, and the RecipientToken (which defines the credential used to encrypt the request) is never sent to the recipient. Both Initiator and Recipient tokens are defined as X509 tokens. The input and output policies in the WSDL enforce that the SOAP Body must be signed using the Initiator credential, and encrypted using the Recipient credential.
In addition to specifying an asymmetric binding, the policy also defines a SignedSupportingToken, which contains a SAML (2.0) Token which is always sent to the recipient. In order to successfully invoke on the service, the client must include a SAML 2.0 token in the security header of the request. This policy looks like:
2.3) The Client configuration
The client.xml referenced in the code block above contains a jaxws:client configuration for the DoubleItSaml2AsymmetricPort. It sets the following relevant jaxws:properties:
- ws-security.encryption.properties - The Crypto properties file which describes where to find the service provider's public key.
- ws-security.encryption.username - The alias to use to obtain the service provider's public key from the keystore reference in the Crypto properties file above.
- ws-security.callback-handler - A CallbackHandler object which is expected to supply the password used to access the private key for signature creation, or decryption.
- ws-security.signature.properties - The Crypto properties file which describes where to find the client's public/private key.
- ws-security.signature.username - The alias to use to obtain the client's private key from the keystore reference in the Crypto properties file above.
CXF 2.4.0 defines a new jaxws:property ("ws-security-saml-callback-handler") which specifies a CallbackHandler instance used to create SAML Assertions. This object is added to the outbound request context above dynamically, however it could also have been configured in the spring bean along with the other ws-security parameters. The CallbackHandler object used in this test can be seen here. The CallbackHandler implementation is expected to obtain a SAMLCallback object, and to set the appropriate values on this object, e.g. SAML version, Subject, issuer, Authentication/Authorization/Attribute Statements, etc. In the example provided in this test, it creates a SAML 2.0 assertion (by default), sets a mock issuer, subject and attribute statement, and sets a subject confirmation method of sender-vouches. Some code in WSS4J then constructs a SAML Assertion by processing this SAMLCallback object. It's easy to construct a SAML Assertion in this way, as the following (edited) code shows:
SAMLCallback callback = (SAMLCallback) callbacks[i];
String subjectName = "uid=sts-client,o=mock-sts.com";
String subjectQualifier = "www.mock-sts.com";
SubjectBean subjectBean = new SubjectBean(subjectName, subjectQualifier, SAML2Constants.CONF_SENDER_VOUCHES);
AttributeStatementBean attrBean = new AttributeStatementBean();
AttributeBean attributeBean = new AttributeBean();
2.4) The service request
The service request has a security header that contains the following elements:
- A BinarySecurityToken which consists of the X509Certificate of the client.
- A Timestamp.
- An EncryptedKey which consists of a symmetric key encrypted with the public key of the service provider, which is used to encrypt the SOAP Body.
- A SAML2 Assertion.
- A SecurityTokenReference to the SAML Assertion.
- A signature which signs the Timestamp, the SAML Assertion (via the SecurityTokenReference) and the (decrypted) SOAP body. The signing credential is the BinarySecurityToken element described above.
<saml2:Assertion ... Version="2.0">
<saml2:Conditions NotBefore="..." NotOnOrAfter="..."/>
<saml2:Attribute FriendlyName="subject-role" ...>
One thing to note is that as the SAML Assertion has a subject confirmation method of "sender-vouches", the client will automatically add the quality-of-service requirement that the signature which covers the SOAP Body will also cover the SAML Assertion.
3) The Server
3.1) The Server code
The SEI implementation is here, and the Server code itself is here. The configuration is entirely driven through the WSDL and spring configuration, and so the code is as trivial as (edited):
URL busFile = Server.class.getResource("server.xml");
Bus busLocal = new SpringBusFactory().createBus(busFile);
3.2) The Server configuration
The server.xml configuration file referenced above can be seen here. The jaxws:Endpoint configuration for this port should be self-explanatory (edited):
<entry key="ws-security.username" value="bob"/>
<entry key="ws-security.encryption.username" value="alice"/>
The server will process the request as per the security policy in the WSDL, checking that there is a signature in the security header, that covers the SOAP Body and SAML Assertion, that the SOAP Body is Encrypted, that a Timestamp is present and valid, and that the SAML Assertion is present, and is the correct version, etc. Authentication is done on the basis of trust verification of the client's X509Certificate, which was used to verify the signature element.
The SAML Assertion is ignored beyond this point for this system test. It is saved in the security processing results, so that a custom interceptor can do some additional validation or processing on it. In a future blog post, I will describe how to validate the Assertion that has been received in some custom manner.