Monday, February 23, 2009

.NET client consuming Spring WS 1.5.5 using XWSS WS security Implementation

Introduction

I am currently working on an integration project where a .NET client has to consume a Java Web Service. Since I have been using Spring framework for a very long time, my natural inclination is to try out Spring WS 1.5.5 as the web service framework. So I picked spring framework 2.5.6, spring security 2.0.4 and spring web services 1.5.5 for the java web service implementation. I am using XWSS implementation of WS security under the hoods for the username token with password digest authentication mechanism.

The service consumer is a .NET client and apparently there were configuration issues with .NET that prevented them from consuming our service successfully. So I searched in google and posted the issue with Spring forums with no luck. So finally with the help of our .NET Software Engineer, I have managed to come up with a standard procedure as to how to consume a Java Web Service that implements WS security from a .NET client perspective.

Java Web Service Implementation

I am using Spring WS 1.5.5 Airline sample application that comes with its distribution to demonstrate the integration capability. The Airline sample is a normal web application that connects to an embedded HSQLDB database. This application uses XWSS implementation of WS-Security.

applicationContext-security.xml

<?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:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">

<description>
This application context contains the WS-Security and Sprign Security beans.
</description>

<security:global-method-security secured-annotations="enabled"/>

<security:authentication-provider user-service-ref="securityService"/>

<bean id="securityService"
class="org.springframework.ws.samples.airline.security.SpringFrequentFlyerSecurityService">
<description>
A security service used to obtain Frequent Flyer information.
</description>
<constructor-arg ref="frequentFlyerDao"/>
</bean>

<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<description>
This interceptor validates incoming messages according to the policy defined in 'securityPolicy.xml'.
The policy defines that all incoming requests must have a UsernameToken with a password digest in it.
The actual authentication is performed by the Spring Security callback handler.
</description>
<property name="secureResponse" value="false"/>
<property name="policyConfiguration"
value="classpath:org/springframework/ws/samples/airline/security/securityPolicy.xml"/>
<property name="callbackHandler">
<bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="securityService"/>
</bean>
</property>
</bean>

</beans>
Spring ws currently supports two implementations of WS Security. One is based on XWSS and the other one is WSS4J from Apache.

XWSS


XWSS stands for XML and WebServices Security runtime. It is part of Project GlassFish and is used for securing WebServices requests and responses.

XWSS 2.0 was based on OASIS WSS specification version 1.0 and XWSS 3.0 is based on OASIS WSS specification 1.1

XwsSecurityInterceptor

The XwsSecurityInterceptor is an EndpointInterceptor that is based on SUN's XML and Web Services Security package (XWSS). This WS-Security implementation is part of the Java Web Services Developer Pack ( Java WSDP ). Like any other endpoint interceptor, it is defined in the endpoint mapping. This means that you can be selective about adding WS-Security support: some endpoint mappings require it, while others do not.

Note that XWSS requires both a SUN 1.5 JDK and the SUN SAAJ reference implementation. The WSS4J interceptor does not have these requirements.

The XwsSecurityInterceptor requires a security policy file to operate. This XML file tells the interceptor what security aspects to require from incoming SOAP messages, and what aspects to add to outgoing messages.

securityPolicy.xml

<xwss:SecurityConfiguration dumpMessages="false" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/>
</xwss:SecurityConfiguration>

Spring WS endpoint Mappings
The endpoint mapping is responsible for mapping incoming messages to appropriate endpoints. There are some endpoint mappings you can use out of the box. Please refer the example in the applicationContext-ws.xml file below.

applicationContext-ws.xml

<?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:oxm="http://www.springframework.org/schema/oxm"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-1.5.xsd">

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

<bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

<bean id="schemaCollection" class="org.springframework.xml.xsd.commons.CommonsXsdSchemaCollection">
<description>
This bean wrap the messages.xsd (which imports types.xsd), and inlines them as a one.
</description>
<property name="xsds" value="/messages.xsd"/>
<property name="inline" value="true"/>
</bean>

<!-- ===================== ENDPOINTS ===================================== -->

<!--
The marshallingEndpoint and xpathEndpoint handle the same messages. So, you can only use one of them at the
same time. This is done for illustration purposes only, typically you would not create two endpoints which
handle the same messages.
-->

<bean id="marshallingEndpoint" class="org.springframework.ws.samples.airline.ws.MarshallingAirlineEndpoint">
<description>
This endpoint handles the Airline Web Service messages using JAXB2 marshalling.
</description>
<constructor-arg ref="airlineService"/>
</bean>

<!--
<bean id="xpathEndpoint" class="org.springframework.ws.samples.airline.ws.XPathAirlineEndpoint">
<description>
This endpoint handles the Airline Web Service messages using XPath expressions and JAXB2 marshalling.
</description>
<constructor-arg ref="airlineService"/>
<constructor-arg ref="marshaller"/>
</bean>
-->

<bean id="getFrequentFlyerMileageEndpoint"
class="org.springframework.ws.samples.airline.ws.GetFrequentFlyerMileageEndpoint">
<description>
This endpoint handles get frequent flyer mileage requests.
</description>
<constructor-arg ref="airlineService"/>
</bean>

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

<!-- ===================== ENDPOINT MAPPINGS ============================== -->

<!--
The endpoint mappings map from a request to an endpoint. Because we only want the security interception to
occur for the GetFrequentFlyerMileageEndpoint, we define two mappings: one with the securityInterceptor, and
a general one without it.
-->

<bean id="annotationMapping"
class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
<description>
Detects @PayloadRoot annotations on @Endpoint bean methods. The MarshallingAirlineEndpoint
has such annotations. It uses two interceptors: one that logs the message payload, and the other validates
it accoring to the 'airline.xsd' schema file.
</description>
<property name="interceptors">
<list>
<bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
<property name="xsdSchemaCollection" ref="schemaCollection"/>
<property name="validateRequest" value="true"/>
<property name="validateResponse" value="true"/>
</bean>
</list>
</property>
<property name="order" value="1"/>
</bean>

<bean id="secureMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
<description>
This endpoint mapping is used for endpoints that are secured via WS-Security. It uses a
securityInterceptor, defined in applicationContext-security.xml, to validate incoming messages.
</description>
<property name="mappings">
<props>
<prop key="{http://www.springframework.org/spring-ws/samples/airline/schemas/messages}GetFrequentFlyerMileageRequest">
getFrequentFlyerMileageEndpoint
</prop>
</props>
</property>
<property name="interceptors">
<list>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor"/>
<ref bean="wsSecurityInterceptor"/>
</list>
</property>
<property name="order" value="2"/>
</bean>


<!-- ===================== ENDPOINT ADAPTERS ============================== -->

<!--
Endpoint adapters adapt from the incoming message to a specific object or method signature. Because this
example application uses three different endpoint programming models, we have to define three adapters. This
is done for illustration purposes only, typically you would use one adapter, for instance the
MarshallingMethodEndpointAdapter.
-->


<sws:marshalling-endpoints/>

<sws:xpath-endpoints>
<sws:namespace prefix="messages"
uri="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"/>
</sws:xpath-endpoints>

<bean class="org.springframework.ws.server.endpoint.adapter.PayloadEndpointAdapter">
<description>
This adapter allows for endpoints which implement the PayloadEndpoint interface. The Get
FrequentFlyerMileageEndpoint implements this interface.
</description>
</bean>

<!-- ===================== ENDPOINT EXCEPTION RESOLVER ===================== -->

<!--
Endpoint exception resolvers can handle exceptions as they occur in the Web service. We have two sorts of
exceptions we want to handle: the business logic exceptions NoSeatAvailableException and NoSuchFlightException,
which both have a @SoapFault annotation, and other exceptions, which don't have the annotation. Therefore, we
have two exception resolvers here.
-->

<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver">
<description>
This exception resolver maps exceptions with the @SoapFault annotation to SOAP Faults. The business logic
exceptions NoSeatAvailableException and NoSuchFlightException have these.
</description>
<property name="order" value="1"/>
</bean>

<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
<description>
This exception resolver maps other exceptions to SOAP Faults. Both UnmarshallingException and
ValidationFailureException are mapped to a SOAP Fault with a "Client" fault code.
All other exceptions are mapped to a "Server" error code, the default.
</description>
<property name="defaultFault" value="SERVER"/>
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.UnmarshallingFailureException">CLIENT,Invalid request</prop>
<prop key="org.springframework.oxm.ValidationFailureException">CLIENT,Invalid request</prop>
</props>
</property>
<property name="order" value="2"/>
</bean>

</beans>



After deploying the above said service, the service will run at http://localhost:8080/airline/services. The wsdl is published at http://localhost:8080/airline/airline.wsdl

NOTE: There are other applicationContext xml files within the airline sample application which I am not mentioning here in this article for brevity. I would highly recommend you refer the sample airline application that comes with Spring WS 1.5.5 or 1.5.6.

There is already a .NET example within airline application which does not require to go to WS security intereceptor configured at the airline service level. The next section will give you enough details as to how to configure the .NET client side to access the airline sample service that goes through XWSS security interceptor configured within the application.

.NET client configuration using WSE 2.0

Prerequisites
:

1) Make sure you have WSE2.0 installed and .NET 1.1 (at least)

How do you verify the above prerequisite?


Check
whether Microsoft.Web.Services2.dll exists under C:\Program Files\Microsoft WSE\v2.0\Microsoft.Web.Services2.dll

If WSE 2.0 is not installed, please download it from here.

NOTE: The dll Microsoft.Web.Services2.dll plays the role of encrypting or decrypting the request and responses of the service.

.NET consuming Java Web Service

Please follow the steps below to consume web service using WSE 2.0

1. Enable WSE 2.0 extensions


In visual studio,
  • Select your project and right click
  • Select WSE setting 2.0 from the context menu
  • In the General tab, check 'Enable this project for Web Services Enhancements' checkbox
  • check 'Enable Microsoft Web Services Enhancement soap extensions'

2. Add Web References

In visual studio,
  • Select your project and right click
  • Select 'Add Web References' from the context menu
  • Enter the web service URL. For e.g. http://localhost:8080/airline/airline.wsdl
  • Hit go button to search the service
  • If the service is found, name the service and 'Add Reference'

3. How to verify whether the added service has WSE 2.0 service extension

  • Expand the service and open the source code of 'Reference.cs'
  • Class should extend Microsoft.Web.Services2.WebServicesClientProtocol

4. Changes in .NET Client class which consume the service

  • Include following namespace (this is to create 'UsernameToken')
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;


  • In the code, add UsernameToken in the soap header

For e.g.,

UsernameToken un =
new UsernameToken("UN","Pwd", PasswordOption.SendHashed);
svc.RequestSoapContext.Security.Tokens.Add(un);

In the above example, replace UN with 'john' and PWD with 'changeme' since this is what airline sample application uses.

5. Changes in web.config

Add <configSections> element under the <configuration> element if it is not already added



<configSections>
<section name="microsoft.web.services2" type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration,
Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>

Add <webServices> element under <system.web> element if not added



<webServices>
<soapExtensionTypes>
<add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
</soapExtensionTypes>
</webServices>


References

1. Spring WS 1.5.5 Reference Document
2. Spring WS forum
3. Microsoft .NET framework developer center (Article 1 and Article 2)

Conclusion

I hope this article will help any novice .NET software engineer in integrating with Java Web Services that implements WS Security. I sincerely thank my friend and colleague Mr. Thanasekaran Mariappan for helping me put together this article.