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
cd ws-security
mvn compile
mvn compile
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());
SpringBusFactory.setDefaultBus(bus);
SpringBusFactory.setThreadDefaultBus(bus);
DoubleItService service = new DoubleItService();
DoubleItPortType saml2Port = service.getDoubleItSaml2AsymmetricPort();
((BindingProvider)saml2Port).getRequestContext().put(
"ws-security.saml-callback-handler", new SamlCallbackHandler()
);
BigInteger result = saml2Port.doubleIt(BigInteger.valueOf(25));
assert result.equals(BigInteger.valueOf(50));
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:
<sp:SignedSupportingTokens>
<wsp:Policy>
<sp:SamlToken sp:IncludeToken="...AlwaysToRecipient">
<wsp:Policy>
<sp:WssSamlV20Token11/>
</wsp:Policy>
</sp:SamlToken>
</wsp:Policy>
<wsp:Policy>
<sp:SamlToken sp:IncludeToken="...AlwaysToRecipient">
<wsp:Policy>
<sp:WssSamlV20Token11/>
</wsp:Policy>
</sp:SamlToken>
</wsp:Policy>
</sp:SignedSupportingTokens>
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];
callback.setSamlVersion(SAMLVersion.VERSION_20);
callback.setIssuer("sts");
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);
callback.setSubject(subjectBean);
AttributeStatementBean attrBean = new AttributeStatementBean();
attrBean.setSubject(subjectBean);
AttributeBean attributeBean = new AttributeBean();
attributeBean.setSimpleName("subject-role");
attributeBean.setAttributeValues(Collections.singletonList("system-user"));
attrBean.setSamlAttributes(Collections.singletonList(attributeBean));
callback.setAttributeStatementData(Collections.singletonList(attrBean));
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:Issuer>sts</saml2:Issuer>
<saml2:Subject>
<saml2:NameID ...>uid=sts-client,o=mocksts.com</saml2:NameID>
<saml2:SubjectConfirmation Method="...:sender-vouches">
</saml2:SubjectConfirmation>
</saml2:Subject>
<saml2:Conditions NotBefore="..." NotOnOrAfter="..."/>
<saml2:AttributeStatement>
<saml2:Attribute FriendlyName="subject-role" ...>
<saml2:AttributeValue...>system-user</saml2:AttributeValue>
</saml2:Attribute>
</saml2:AttributeStatement>
</saml2:Assertion>
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);
BusFactory.setDefaultBus(busLocal);
setBus(busLocal);
new Server();
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):
<jaxws:endpoint
id="Saml2TokenOverAsymmetric"
address="http://localhost:9001/DoubleItSaml2Asymmetric"
serviceName="s:DoubleItService"
endpointName="s:DoubleItSaml2AsymmetricPort"
xmlns:s="http://WSSec/saml"
implementor="org.apache.cxf.systest.ws.saml.server.DoubleItImpl"
wsdlLocation="wsdl_systest_wssec/saml/DoubleItSaml.wsdl">
<jaxws:properties>
<entry key="ws-security.username" value="bob"/>
<entry key="ws-security.callback-handler"
value="....KeystorePasswordCallback"/>
<entry key="ws-security.signature.properties"
value="...bob.properties"/>
<entry key="ws-security.encryption.properties"
value="...alice.properties"/>
<entry key="ws-security.encryption.username" value="alice"/>
</jaxws:properties>
</jaxws:endpoint>
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.
Hi Colm,
ReplyDeleteI was trying the same approach, I ran in to issue I see that I am observing the following following stack trace :
Caused by: org.apache.ws.security.WSSecurityException: WSHandler: application provided null or empty password
at org.apache.ws.security.handler.WSHandler.getPasswordCB(WSHandler.java:935)
at org.apache.ws.security.action.SAMLTokenSignedAction.execute(SAMLTokenSignedAction.java:70)
at org.apache.ws.security.handler.WSHandler.doSenderAction(WSHandler.java:202)
at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor.access$200(WSS4JOutInterceptor.java:52)
at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor$WSS4JOutInterceptorInternal.handleMessage(WSS4JOutInterceptor.java:260)
... 30 more
Perhaps I am missing to configure the password in one of the properties files.I was wondering if you could provide a working maven project so that we can following the same and understand the implementation much further.Please let me know if you have concerns.
Hi,
ReplyDeleteThe post above links to a system test that can be run with maven, i.e.:
svn co https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.0/systests/ws-security
cd ws-security
mvn test -Dtest=SamlTokenTest
If you are looking for a sample you could download the Talend Service Factory plus examples, see:
http://coheigea.blogspot.com/2011/04/talend-service-factory-240-released.html
Colm.
Hi Colm,
ReplyDeleteI was finally able to get the source code from
svn co https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.0/systests/ws-security
I have got maven set up too and it was able get the workspace configured but for some reason I see there are missing classes :
org.apache.cxf.policytest.doubleit.DoubleIt;
org.apache.cxf.policytest.doubleit.DoubleItFault_Exception;
org.apache.cxf.policytest.doubleit.DoubleItPortType;
org.apache.cxf.policytest.doubleit.DoubleItPortTypeHeader;
org.apache.cxf.policytest.doubleit.DoubleItResponse;
org.apache.cxf.policytest.doubleit.DoubleItService;
Try doing a "mvn compile" before running the test.
ReplyDeleteColm.
Hi Colm,
ReplyDeleteI was able to get the workspace up and running.in the org.apache.cxf.systest.ws.saml.SamlTokenTest.testSaml1OverTransport() method I see that you are explicitly attaching
((BindingProvider)saml1Port).getRequestContext().put(
"ws-security.saml-callback-handler", new SamlCallbackHandler()
);
The callback handler and associated properties file.I was wondering if you could do it more elegantly with a
jaxws:outInterceptors and I chose to use the wssjinterceptor and here is the config code
<---------------------------------------->
<----------------------------------------->
I am able to invoke the my custom callbackhandler but I am observing the following exception and finally get a soap fault.
Here is the error :
Caused by: org.apache.ws.security.WSSecurityException: WSHandler: application provided null or empty password
at org.apache.ws.security.handler.WSHandler.getPasswordCB(WSHandler.java:935)
at org.apache.ws.security.action.SAMLTokenSignedAction.execute(SAMLTokenSignedAction.java:70)
at org.apache.ws.security.handler.WSHandler.doSenderAction(WSHandler.java:202)
at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor.access$200(WSS4JOutInterceptor.java:52)
at org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor$WSS4JOutInterceptorInternal.handleMessage(WSS4JOutInterceptor.java:260)
also is there a personal email address and its hard to paste config info in this box as it is skipping the elements in the post.
Hi Colm,
ReplyDeleteNever mind I worked on it over the weekend and all it needed was some code debugging and patience my implementation with saml is working pefectly fine with cxf 2.40 and wss4j 1.6.0 Thanks all the help.
Hi Colm,
ReplyDeleteI am new to CXF and SAML, in the client section of your article, you mentioned the client.xml file, I have a question on where the client.xml file should be placed, and what mechanism the client application uses to retrieve the configuarations set in the file.
Thanks,
Raymond
Hi Raymond,
ReplyDeleteSee this wiki page for more information:
http://cxf.apache.org/docs/configuration.html
Colm.
Hi Colm,
ReplyDeleteThanks for the link.
I am currently running into another problem creating a web service client app based on your sample.
I have created java artifacts for the web service using wsimport tool of Java 6, wrote a little code to create a client class to comsume the web service, all is fine and tested, and this is done in Eclipse. Then I downloaded and installed CXF 2.4.1 to add SAML support to the code as shown in your sample. I added cxf-manifest.jar to the Java Build Path of the Eclipse project. When I ran I got an exception error as follows
Jul 19, 2011 10:46:43 AM org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
INFO: Creating Service {http://ch33.va.gov/contract/Claimant}ClaimantService from WSDL: file:/C:/LTS/ClaimantWS/Claimant.wsdl
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: Reference to policy #Claimant_PortBindingPolicy could not be resolved.
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:146)
at $Proxy26.getClaimant(Unknown Source)
at wsGetClaimantClient.GetClaimantClient.main(GetClaimantClient.java:38)
Caused by: org.apache.cxf.ws.policy.PolicyException: Reference to policy #Claimant_PortBindingPolicy could not be resolved.
at org.apache.cxf.ws.policy.attachment.AbstractPolicyProvider.checkResolved(AbstractPolicyProvider.java:93)
at org.apache.cxf.ws.policy.attachment.wsdl11.Wsdl11AttachmentPolicyProvider.resolveReference(Wsdl11AttachmentPolicyProvider.java:266)
at org.apache.cxf.ws.policy.attachment.wsdl11.Wsdl11AttachmentPolicyProvider.getElementPolicy(Wsdl11AttachmentPolicyProvider.java:216)
at org.apache.cxf.ws.policy.attachment.wsdl11.Wsdl11AttachmentPolicyProvider.getElementPolicy(Wsdl11AttachmentPolicyProvider.java:170)
at org.apache.cxf.ws.policy.attachment.wsdl11.Wsdl11AttachmentPolicyProvider.getElementPolicy(Wsdl11AttachmentPolicyProvider.java:163)
at org.apache.cxf.ws.policy.attachment.wsdl11.Wsdl11AttachmentPolicyProvider.getEffectivePolicy(Wsdl11AttachmentPolicyProvider.java:100)
at org.apache.cxf.ws.policy.PolicyEngineImpl.getAggregatedEndpointPolicy(PolicyEngineImpl.java:401)
at org.apache.cxf.ws.policy.EndpointPolicyImpl.initializePolicy(EndpointPolicyImpl.java:150)
at org.apache.cxf.ws.policy.EndpointPolicyImpl.initialize(EndpointPolicyImpl.java:139)
at org.apache.cxf.ws.policy.PolicyEngineImpl.createEndpointPolicyInfo(PolicyEngineImpl.java:533)
at org.apache.cxf.ws.policy.PolicyEngineImpl.getEndpointPolicy(PolicyEngineImpl.java:285)
at org.apache.cxf.ws.policy.PolicyEngineImpl.getClientEndpointPolicy(PolicyEngineImpl.java:267)
at org.apache.cxf.transport.http.policy.PolicyUtils.getClient(PolicyUtils.java:150)
at org.apache.cxf.transport.http.HTTPConduit.(HTTPConduit.java:300)
at org.apache.cxf.transport.http.HTTPTransportFactory.getConduit(HTTPTransportFactory.java:246)
at org.apache.cxf.transport.http.HTTPTransportFactory.getConduit(HTTPTransportFactory.java:233)
at org.apache.cxf.binding.soap.SoapTransportFactory.getConduit(SoapTransportFactory.java:228)
at org.apache.cxf.endpoint.AbstractConduitSelector.getSelectedConduit(AbstractConduitSelector.java:81)
at org.apache.cxf.endpoint.UpfrontConduitSelector.prepare(UpfrontConduitSelector.java:61)
at org.apache.cxf.endpoint.ClientImpl.prepareConduitSelector(ClientImpl.java:809)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:505)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:440)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:343)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:295)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:73)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:124)
... 2 more
Is there compatibility issue here ?
Hi Raymond,
ReplyDeleteIt's impossible to say without seeing the WSDL. Are you sure the policy "Claimant_PortBindingPolicy" is available in the WSDL? If so, could you create a JIRA in CXF here:
https://issues.apache.org/jira/browse/CXF
and attach a sample WSDL that shows the problem?
Colm.
Hi Colm,
ReplyDeleteMay be I should rephrase my question, I have CXF 2.4.1 downloaded and installed, this time I was using wsdl2java tool from CXF to create the stubs, and I was getting a different error, how should I configure Eclipse to run your sample(the client)
Thanks
Ray
Hi Raymond,
ReplyDeleteWhat error are you getting from wsdl2java? Could you post to the CXF users list instead of this blog, and give a sample WSDL that shows the problem?
Colm.
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDelete