Friday, January 14, 2011

Spring WS 2: Client-side WS-Security Using XWSS

In this tutorial we will implement a client-side WS-Security using XWSS. We will based our client application on an existing web service client which can be found at Spring WS - MVC: Implementing a Client Tutorial. It's imperative that you've read that first to understand the client application.

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/ws
You 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")
The client will validate the username and password sent by the provider using 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

 <!-- 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.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring WS 2: Client-side WS-Security Using XWSS ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

5 comments: