Our web service provider will be based from the Spring-WS 2: WS-Security Using XWSS tutorial. We can also use the web service provider from the Spring-WS 2: WS-Security Using WSS4J 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-xwss/krams/ws" p:messageSender-ref="messageSender"> <constructor-arg ref="messageFactory"/> <property name="interceptors"> <list> <ref local="xwsSecurityInterceptor"/> </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.2 XwsSecurityInterceptor - http://static.springsource.org/spring-ws/sites/2.0/reference/html/security.html#security-xws-security-interceptor What is the XWS-Security Framework? - http://download.oracle.com/docs/cd/E17802_01/webservices/webservices/docs/1.6/tutorial/doc/XWS-SecurityIntro4.html#wp564887 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="xwsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/> <property name="callbackHandlers"> <list> <ref bean="callbackHandler"/> </list> </property> </bean> <!-- As a client the username and password generated by the server must match with the client! --> <!-- a simple callback handler to configure users and passwords with an in-memory Properties object. --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.xwss.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="xwsSecurityInterceptor"/> </list> </property>
This is the property that's responsible for all the client-side WS-Security using XWSS. 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-xwss/krams/wsYou may need to modify this property depending on how you setup your provider.
The Client-side XWSS WS-Security
Let's examine further the xwsSecurityInterceptor bean. Notice it contains a couple of properties. If you have read the Spring-WS 2: WS-Security Using XWSS, you will find the following settings familiar because we're using the same exact interceptor!
<bean id="xwsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/> <property name="callbackHandlers"> <list> <ref bean="callbackHandler"/> </list> </property> </bean> <!-- As a client the username and password generated by the server must match with the client! --> <!-- a simple callback handler to configure users and passwords with an in-memory Properties object. --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="mojo">mojopass</prop> <prop key="user">pass</prop> </props> </property> </bean>The policyConfiguration property defines an external securityPolicy.xml containing WS-Security settings pertaining to this application.
Here's the securityPolicy.xml
securityPolicy.xml
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:RequireTimestamp maxClockSkew="60" timestampFreshnessLimit="300"/> <xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/> <xwss:Timestamp /> <xwss:UsernameToken name="admin" password="secret" digestPassword="false" useNonce="false"/> </xwss:SecurityConfiguration>
The securityPolicy contains two parts:
1. The elements that the provider must provide when sending a response message.
- RequireTimestamp: When the provider sends a reply, it should contain a Timestamp element
- RequireUsernameToken: When the provider sends a reply, it should contain a UsernameToken where the password is encrypted (passwordDigestRequired="true") and it should contain a Nonce element (nonceRequired="true")
mojo/mojopass user/pass
<!-- a simple callback handler to configure users and passwords with an in-memory Properties object. --> <bean id="callbackHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="mojo">mojopass</prop> <prop key="user">pass</prop> </props> </property> </bean>
2. The elements that the client must provide when sending a request message.
- Timestamp: When the client sends a message, a Timestamp element is added in the SOAP Header section.
- UsernameToken: When the client sends a message, a UsernameToken element is added in the SOAP Header section, including the username and password attributes. The password must be in plain text (digestPassword="false") and there should be no Nonce element (useNonce="false")
In other words, our client application can only send a successful request message if we put a Timestamp and UsernameToken, and we'll only accept the reply if it has a Timestamp and UsernameToken as well.
Remember this securityPolicy configuration only applies to the client application! If the provider application is also using XWSS, then it also has its own independent securityPolicy.xml! If the provider is using WSS4J, then it doesn't have an external configuration but it still has a similar setting within the spring-ws.xml
Run the Client Application
Let's run our client application and examine the actual SOAP request message sent by the c
<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"> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1294933314141305956507"> <wsu:Created>2011-01-13T15:42:00.350Z</wsu:Created> <wsu:Expires>2011-01-13T15:47:00.350Z</wsu:Expires> </wsu:Timestamp> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1294933314143968334688" 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#PasswordText">secret</wsse:Password> </wsse:UsernameToken> </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/secret(unencrypted password).
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"> <wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-1294933019426-1489568038"> <wsu:Created>2011-01-13T15:42:00.516Z</wsu:Created> <wsu:Expires>2011-01-13T15:47:00.516Z</wsu:Expires> </wsu:Timestamp> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XWSSGID-12949330194261896507786" 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#PasswordDigest">ZalI6+DTAFvlYM2h4DBg56rpyhY=</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">smqvjzTKmKJkQlrSCubs/ZSm</wsse:Nonce> <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2011-01-13T15:42:00.521Z</wsu:Created> </wsse:UsernameToken> </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/(encrypted password). A Nonce and Created date has been added as well. These properties are exactly the same in both the provider and the client, except for the Nonce and Created.
However, do not be mistaken. Our client application can also access WSS4J-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 WSS4J tutorial.
That's it. We've completed our client-side WS-Security application using XWSS. It's amazing how we enabled security on the client side. We just added a simple XwsSecurityInterceptor (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-xwss/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-xwss.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