Our web service provider will be based from the Spring-WS 2: WS-Security Using WSS4J tutorial. We can also use the web service provider from the Spring-WS 2: WS-Security Using XWSS tutorial. This means you will be required to setup two servers with different ports, i.e two Tomcat instances (which is trivial to setup in Eclipse), one for the client and one for the provier. This also means you've read those articles first to understand what security tokens are required from the client.
We actually just need to edit a single file from our current web service client, the spring-ws.xml config. I'll show you the new config and the old config for comparison purposes.
Here's the new config:
spring-ws.xml (new)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:sws="http://www.springframework.org/schema/web-services" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd"> <!-- * The WebServiceTemplate requires a messageSender and messageFactory * In order to facilitate the sending of plain Java objects, the WebServiceTemplate requires a marshaller and unmarshaller. * The WebServiceTemplate class uses an URI as the message destination. See: http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate" p:marshaller-ref="jaxbMarshaller" p:unmarshaller-ref="jaxbMarshaller" p:defaultUri="http://localhost:8081/spring-ws-wss4j/krams/ws" p:messageSender-ref="messageSender"> <constructor-arg ref="messageFactory"/> <property name="interceptors"> <list> <ref local="wss4jSecurityInterceptor"/> </list> </property> </bean> <!-- References Chapter 7. Securing your Web services with Spring-WS - http://static.springsource.org/spring-ws/sites/2.0/reference/html/security.html 7.3. Wss4jSecurityInterceptor - http://static.springsource.org/spring-ws/sites/2.0/reference/html/security.html#security-wss4j-security-interceptor Apache WSS4J - http://ws.apache.org/wss4j/ Example of SOAP request authenticated with WS-UsernameToken - http://stackoverflow.com/questions/3448498/example-of-soap-request-authenticated-with-ws-usernametoken --> <!-- This is not documented in the Spring WS Reference but the API shows that we can add interceptors --> <bean id="wss4jSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <!-- The web service provider requires us to pass a timestamp, username, and password --> <property name="securementActions" value="Timestamp UsernameToken" /> <property name="securementUsername" value="admin" /> <property name="securementPassword" value="secret" /> <!-- When the web service replies, it will send a timestamp, username, and password as well. We want to verify that it is still the same provider --> <property name="validationActions" value="Timestamp UsernameToken"/> <property name="validationCallbackHandler" ref="callbackHandler" /> </bean> <!-- Simple callback handler that validates passwords against a in-memory Properties object. Password validation is done on a case-sensitive basis --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="mojo">mojopass</prop> <prop key="user">pass</prop> </props> </property> </bean> <!-- There are two implementations of the WebServiceMessageSender: HttpUrlConnectionMessageSender and CommonsHttpMessageSender. The CommonsHttpMessageSender provides advanced and easy-to-use functionality (such as authentication, HTTP connection pooling, and so forth). This uses the Jakarta Commons HttpClient. See http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="messageSender" class="org.springframework.ws.transport.http.CommonsHttpMessageSender"/> <!-- There are two message factories for SOAP: SaajSoapMessageFactory and AxiomSoapMessageFactory. If no message factory is specified (via the messageFactory property), Spring-WS will use the SaajSoapMessageFactory by default. See: http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <!-- Here we use the Jaxb2 marshaller to marshall and unmarshall our Java objects --> <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" p:contextPath="org.krams.tutorial.oxm"/> </beans>
Here's the old config:
spring-ws.xml (old)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:sws="http://www.springframework.org/schema/web-services" xmlns:oxm="http://www.springframework.org/schema/oxm" xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd"> <!-- * The WebServiceTemplate requires a messageSender and messageFactory * In order to facilitate the sending of plain Java objects, the WebServiceTemplate requires a marshaller and unmarshaller. * The WebServiceTemplate class uses an URI as the message destination. See: http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate" p:marshaller-ref="jaxbMarshaller" p:unmarshaller-ref="jaxbMarshaller" p:defaultUri="http://localhost:8081/spring-ws-standalone/krams/ws" p:messageSender-ref="messageSender"> <constructor-arg ref="messageFactory"/> </bean> <!-- There are two implementations of the WebServiceMessageSender: HttpUrlConnectionMessageSender and CommonsHttpMessageSender. The CommonsHttpMessageSender provides advanced and easy-to-use functionality (such as authentication, HTTP connection pooling, and so forth). This uses the Jakarta Commons HttpClient. See http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="messageSender" class="org.springframework.ws.transport.http.CommonsHttpMessageSender"/> <!-- There are two message factories for SOAP: SaajSoapMessageFactory and AxiomSoapMessageFactory. If no message factory is specified (via the messageFactory property), Spring-WS will use the SaajSoapMessageFactory by default. See: http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#client-web-service-template --> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <!-- Here we use the Jaxb2 marshaller to marshall and unmarshall our Java objects --> <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller" p:contextPath="org.krams.tutorial.oxm"/> </beans>
Notice we still have the same basic bean declaration:
webServiceTemplate messageSender messageFactory jaxbMarshaller
However the webServiceTemplate bean now has a reference to the interceptors property:
<property name="interceptors"> <list> <ref local="wss4jSecurityInterceptor"/> </list> </property>This is the property that's responsible for all the client-side WS-Security using WSS4J. It's really a simple addition to our existing web service client.
We also have to modify the defaultUri to match our current web service provider:
http://localhost:8081/spring-ws-wss4j/krams/wsYou may need to modify this property depending on how you setup your provider.
The Client-side WSS4J WS-Security
Let's examine further the wss4jSecurityInterceptor bean. Notice it contains a couple of properties. If you have read the Spring-WS 2: WS-Security Using WSS4J, you will find the following settings familiar because we're using the same exact interceptor!
<bean id="wss4jSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <!-- The web service provider requires us to pass a timestamp, username, and password --> <property name="securementActions" value="Timestamp UsernameToken" /> <property name="securementUsername" value="admin" /> <property name="securementPassword" value="secret" /> <!-- When the web service replies, it will send a timestamp, username, and password as well. We want to verify that it is still the same provider --> <property name="validationActions" value="Timestamp UsernameToken"/> <property name="validationCallbackHandler" ref="callbackHandler" /> </bean> <!-- Simple callback handler that validates passwords against a in-memory Properties object. Password validation is done on a case-sensitive basis --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="mojo">mojopass</prop> <prop key="user">pass</prop> </props> </property> </bean>The configuration defines policies pertaining to client-side WS-Security. It contains two parts:
1. The elements that the client must provide when sending a request message.
- securementActions: When the client sends a message, a Timestamp and UsernameToken elements (including the username and password) will be added in the SOAP Header section.
- securementUsername: The username that the provider expects
- securementPassword: The password that the provider expects
2. The elements that the provider must provide when sending a response message.
- validationActions: When the provider sends a reply, it should contain the actions we specified in the validationActions. If it doesn't contain the actions, then the reply is invalid. For our current configuration, we expect a Timestamp and UsernameToken as well from the provider. This is like a dual verification on both sides. The provider requires a Timestamp and UsernameToken. The client requires the same
- validationCallbackHandler: When the provider sends a reply, it adds a username and password attributes. To validate these values in the client-side, we provide a callback handler, a SimplePasswordValidationCallbackHandler (see below) . In our current configuration, the client expects that the provider will send either of the following username/password pair:
mojo/mojopass user/pass
<bean id="wss4jSecurityInterceptor" <!-- Simple callback handler that validates passwords against a in-memory Properties object. Password validation is done on a case-sensitive basis --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="mojo">mojopass</prop> <prop key="user">pass</prop> </props> </property> </bean>
Run the Client Application
Let's run our client application and examine the actual SOAP request message sent by the client:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-2" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Username>admin</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">1pN5RLpgXRhZxlYwHovlstf75do=</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">TOvp53qb3mPvX4YMi+Ac8g==</wsse:Nonce> <wsu:Created>2011-01-13T14:17:59.486Z</wsu:Created> </wsse:UsernameToken> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-1"> <wsu:Created>2011-01-13T14:17:59.473Z</wsu:Created> <wsu:Expires>2011-01-13T14:22:59.473Z</wsu:Expires> </wsu:Timestamp> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <ns2:subscriptionRequest xmlns:ns2="http://krams915.blogspot.com/ws/schema/oss"> <ns2:id>1234567</ns2:id> <ns2:name>John Smith</ns2:name> <ns2:email>john@dummy.com</ns2:email> </ns2:subscriptionRequest> </SOAP-ENV:Body> </SOAP-ENV:Envelope>Notice the SOAP Header has been augmented with a Timestamp and UsernameToken including the username and password: admin/(encrypted password). A Nonce and Created date has been added as well.
Run the Provider Application
Let's run our web service provider and examine the actual SOAP reply message sent by the provider:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" SOAP-ENV:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-3" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Username>mojo</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">mojopass</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">wVExJqRYGv9tYq8YUGkFyw==</wsse:Nonce> <wsu:Created>2011-01-13T14:24:17.026Z</wsu:Created> </wsse:UsernameToken> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Timestamp-2"> <wsu:Created>2011-01-13T14:24:17.018Z</wsu:Created> <wsu:Expires>2011-01-13T14:29:17.018Z</wsu:Expires> </wsu:Timestamp><wsse11:SignatureConfirmation xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SigConf-1"/> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <subscriptionResponse xmlns="http://krams915.blogspot.com/ws/schema/oss"> <code>SUCCESS</code> <description>User has been subscribed</description> </subscriptionResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>"
Notice the SOAP Header has been augmented with a Timestamp and UsernameToken including the username and password: mojo/mojopass. A Nonce and Created date has been added as well. These properties are exactly the same in both the provider and the client.
However, do not be mistaken. Our client application can also access XWSS-based web service providers! Just make sure you've set the correct URI. If you like to test this, feel free to run the provider application from Spring-WS 2: WS-Security Using XWSS tutorial.
That's it. We've completed our client-side WS-Security application using WSS4J. It's amazing how we enabled security on the client side. We just added a simple Wss4jSecurityInterceptor (the same bean we used in the provider application) and everything is running smoothly.
To run the client application, use the following URL:
http://localhost:8080/spring-ws-client-wss4j/krams/main/subscribe
Here's a screenshot of the client application:
The best way to learn further is to try the actual application
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-ws-2-0-0-rc2-tutorial/
You can download the project as a Maven build. Look for the spring-ws-client-wss4j.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share
Really nice tutorial, truly showcasing how easy we have it now that wss4jSecurityInterceptor is here :)
ReplyDeleteHi Mark,
ReplyDeleteThank you for the nice tutorial. Do you have also some example how to set up wss4j 1.6+ validators?
I do not want to use callbackHandlers but validators that doesn't need call the method setPassword to the callback.
Thank you!
I get error:
ReplyDeletePropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'securementActions' threw exception; nested exception is java.lang.NoSuchMethodError: org.apache.ws.security.util.WSSecurityUtil.decodeAction(Ljava/lang/String;Ljava/util/List;)I
Maybe I use wrong version? Why it does not find method?
Ey! Did you solve that problem!? We are having the same issue!
DeleteThis comment has been removed by the author.
ReplyDeleteAny idea how I could use the interceptor to allow for different usernames and passwords accessing the backend Soap API? My application needs to access this Soap API on behalf of the users of my application and they have their own credentials in the Soap API. So I can't just set the username and password once on configuration, I need to have it look up the username and password when it goes to make a soap call. Can you point me in the write direction?
ReplyDeleteIt's very straightforward to find out any matter on net as compared to books, as I found this post at this web page.
ReplyDeletenike sb dunks
ReplyDeleteoff white nike
off white hoodie
kyrie 7
kd 14
jordans shoes
pg shoes
hermes birkin
supreme
jordan outlet