In this post I will walk through the WS-SecurityPolicy sample that ships with Talend Service Factory 2.4.0. This sample shows how to secure a web service provider using both a UsernameToken and a SAML Assertion. For this post I will concentrate exclusively on the SAML case.
1) Download the artifacts
Go
here and download Talend Service Factory 2.4.0. When this is done, go
here and download the Talend Service Factory 2.4.0 examples (registration required). Extract the examples into the Talend Service Factory (TSF) install directory ($TSF_HOME). Finally, this sample requires that unlimited strength security policies be installed in the JDK.
2) Build and run the sample
Go to $TSF_HOME/examples/jaxws-ws-secpol and start with the README.txt. Run "mvn eclipse:eclipse" to generate eclipse projects, and "mvn install" to build and install the various modules. There are two options to run the sample.
2.1) Run the sample using maven
To start the service go to the service folder, and run "mvn exec:java". To run the test then go to the client folder and also run "mvn exec:java".
2.2) Run the sample in Karaf
To deploy the service and run the client in an OSGi environment, we can take advantage of the Karaf distribution that ships with TSF. Go to $TSF_HOME/container/bin and run the "karaf" binary. Install the three bundles in Karaf with:
- install mvn:com.talend.sf.examples.jaxws-ws-secpol/ws-secpol-common/1.0
- install mvn:com.talend.sf.examples.jaxws-ws-secpol/ws-secpol-server/1.0
- install mvn:com.talend.sf.examples.jaxws-ws-secpol/ws-secpol-client/1.0
Each of these commands will print out a bundle id. Run the sample by invoking "start <id>" for each of the three bundle ids in turn.
3) The Service Provider
3.1) The Security Policy
The Service endpoint is secured via the following security policy fragment, which is defined in the WSDL ("ws-secpol-wsdl/greeter.wsdl" in the common folder):
<wsp:All>
<sp:AsymmetricBinding xmlns:sp=".../ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=".../AlwaysToRecipient">
<wsp:Policy>
<sp:RequireThumbprintReference />
<sp:WssX509V3Token10 />
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token sp:IncludeToken=".../Never">
<wsp:Policy>
<sp:RequireThumbprintReference />
<sp:WssX509V3Token10 />
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
...
</wsp:Policy>
</sp:AsymmetricBinding>
...
<sp:SignedSupportingTokens xmlns:sp=".../ws-securitypolicy/200702">
<wsp:Policy>
<sp:SamlToken sp:IncludeToken=".../AlwaysToRecipient">
<wsp:Policy>
<sp:WssSamlV20Token11/>
</wsp:Policy>
</sp:SamlToken>
</wsp:Policy>
</sp:SignedSupportingTokens>
</wsp:All>
This Security Policy defines that the Asymmetric Binding is to be used in communication with the service provider, i.e. that the client must sign the request using its private key, and include the corresponding X509 Certificate in the security header of the request, and encrypt the request using the public key of the service provider. Authentication is performed on the basis of trust verification of the client's certificate, as the client illustrates proof-of-possession by signing some part of the request.
In addition to the Asymmetric Binding, the policy requires that a SAML 2.0 Assertion must be included in the service request, and it must be signed by the signature defined by the Asymmetric Binding policy. The (policy-driven) ability to add a SAML Assertion to a SOAP Request is new to Apache CXF 2.4.
Finally, the WSDL defines input and output policies (not included here), which specify that the SOAP Body must be signed and encrypted, and that all of the addressing headers must be signed if present.
3.2) The configuration
The standalone (maven-driven) case is configured entirely in code. The various security configuration items are added as properties to the JAX-WS Endpoint object. The same security configuration items are also used for the case of deploying the service provider in Karaf, except that it is all driven through spring, e.g.:
<jaxws:server id="SAMLGreeter" xmlns:ns1="..."
endpointName="ns1:SAMLGreeterPort"
serviceClass="demo.secure_greeter.server.GreeterImpl">
<jaxws:properties>
<entry key="ws-security.callback-handler" value="..."/>
<entry key="ws-security.encryption.properties" value="..."/>
<entry key="ws-security.signature.properties" value="..."/>
<entry key="ws-security.saml2.validator" value="..."/>
</jaxws:properties>
</jaxws:server>
Four security-related configuration options are used for the service provider. "ws-security.callback-handler" points to a CallbackHandler implementation that is used to supply a password for extracting a private key from a keystore to decrypt the request and sign the response. "ws-security.encryption.properties" refers to a properties file which contains configuration options for loading the keystore used for encryption. Similarly, "ws-security.signature.properties" refers to a properties file for signature creation (and decryption).
"ws-security.saml2.validator" is a configuration tag new to CXF 2.4, and refers to an instance of the WSS4J
Validator interface. The Validator interface is used in WSS4J to validate received security tokens. In this case, we are extending the default SAML2 Token Validation with a custom Validator. This Validator does some additional verification on the received token, namely checking who the issuer is, checking that the confirmation method is "sender-vouches", and checking that the Assertion contains an AttributeStatement, with an Attribute "attribute-role" containing a value "authenticated-client".
All of these configuration tags are defined in the
SecurityConstants class in CXF.
4) The client
When the client wants to invoke on the service provider, it parses the security policy described above in the WSDL. As with the service provider, the client standalone case is configured entirely in code. For the case of deploying the client in Karaf, it is configured in spring as follows:
<jaxws:client id="samlgreeter" wsdlLocation="..." serviceClass="..."
xmlns:ns1="" serviceName="ns1:SecureGreeterService"
endpointName="ns1:SAMLGreeterPort">
<jaxws:properties>
<entry key="ws-security.signature.username" value="..."/>
<entry key="ws-security.encryption.username" value="..."/>
<entry key="ws-security.callback-handler"><bean class="..."/></entry>
<entry key="ws-security.signature.properties" value="..."/>
<entry key="ws-security.encryption.properties" value="..."/>
<entry key="ws-security.saml-callback-handler" value="..."/>
</jaxws:properties>
</jaxws:client>
"ws-security.callback-handler", "ws-security.signature.properties" and "ws-security.encryption.properties" have been covered in the section on configuring the service provider. "ws-security.signature.username" refers to the keystore alias of the private key to use to sign the request, and "ws-security.encryption.username" refers to the keystore alias to use to encrypt the request.
"ws-security.saml-callback-handler" is a configuration tag new to CXF 2.4, and refers to a CallbackHandler which will supply WSS4J with the information to create a SAML Assertion. This sample ships with a CallbackHandler that creates a simple SAML 2.0 Assertion with a subject confirmation method of "sender-vouches". It adds an Attribute to the Assertion that conveys to the Web Service Provider that the client has authenticated an external user in some way (not shown as part of this sample), and has assigned the attribute role of "authenticated-client" to the external user. The assertion that will be generated from this CallbackHandler instance will be signed by the client, as per the policy definition ("SignedSupportingTokens").
5) The Client Request
The security header of the client request contains a BinarySecurityToken which contains the certificate to use to verify the signature. It also contains a Timestamp, an EncryptedKey which is used to encrypt the SOAP Body, a SAML2 Assertion, and a Signature which signs the Timestamp, Assertion and encrypted SOAP Body. The Assertion looks like this:
<saml2:Assertion xmlns:saml2="..." xmlns:xsi="..." ID="..." IssueInstant="..."
Version="2.0" xsi:type="saml2:AssertionType">
<saml2:Issuer>...</saml2:Issuer>
<saml2:Subject>
<saml2:NameID Format=...:unspecified">uid=auth_client</saml2:NameID>
<saml2:SubjectConfirmation Method="...:sender-vouches">
<saml2:SubjectConfirmationData/>
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="..." NotOnOrAfter="..."/>
<saml2:AttributeStatement>
<saml2:Attribute FriendlyName="attribute-role" NameFormat="...">
<saml2:AttributeValue xsi:type="xs:string">
authenticated-client
</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
The service provider processes the received security header as follows:
- It checks that the Timestamp is valid
- It uses its private key to decrypt the EncryptedKey, and then uses the decrypted key to decrypt the SOAP Body.
- It verifies that the Assertion is valid, and passes the Assertion to the custom Validator defined above to validate the contents of the Assertion.
- It verifies that the certificate defined in the BinarySecurityToken can validate the signature, verifies trust in the certificate, and checks that each of the References of the signature produce the correct digest.
At this point security processing is complete, and the service provider constructs a secured response to the client.