utorak, 12. veljače 2008.

Java and Web Services Security: Axis2- part 1


For the first article in this series i have chosen Apache Axis2. Today, Axis2 is one of the most popular Java Web Service Api's, its battle tested, robust and as you will see quite simple to use. At the moment of writing of this article current version was 2.1.3, which is therefore the one used for building all examples you can find here. More information about Axis2 can be found at its homepage.

1. Environment and IDE setting up
First step is to download Axis2 binary distribution (here), after downloading it follow instructions at Axis2 web site on how to install it.
Web Services built with Axis2 can be run either in "Stand alone Axis2 Server" or within some Application Server, for my environment I have opted for the later and used Tomcat Application server (version 5.5.20). In this scenario, Axis2 function as a Java Web Application in which you deploy your web services as service jars, jars with axis2 specific structure and with extenstion ".aar".
Now its time to open up your favorite IDE and Set up a project. My favorite weapon of choice is MyEclipse IDE, whichis is derivate of popular Eclipse IDE. MyEclipse comes with lots of tools, many of which reassemble those usually find in eclipse WTP.

After building Axis2 war, as is described in installation instructions, instead of depolying to Application server i have created new WebProject in my IDE, and unzped war in its WebContent folder! This way I ended upa with a web application I could easily manage (add new, change existing files... ) through my IDE. After unzipping files and refreshing, my Project looked like this:

By no means I hold that the approach described above, is the recommended way to organize your Axis2 project. Everyone is entitled to its own way of doing things, but since there are several ways you can do this I though it to be good idea to share my approach in dealing with Axis2 and my Java IDE.
The main reason I have taken this approach is because I wanted to have Spring IoC capabilities available in my Web Services, of which more will be said later.

2. Service Creation
Creating service from WSDL is pretty simple with Axis2. First, take Shipping Service WSDL from initial post of this series of articles. Create new wsdl folder in your project and put shipping-service.wsdl file there. Then, open shell and position yourself in root folder of your project. Now execute following line:
%AXIS2_HOME%\bin\wsdl2java.bat -uri file:///{rootOfYourProject}/wsdl/shipping-services.wsdl -o {rootOfYourProject} -d adb -s -wv 1.1 -ss -sd -ssi
Do not forget to substitute value for {rootOfYourProject} with correct path of your own Project. If all goes well after wsdl2java script finishes its work your project should look like this:

As you can see wsdl2java script has generated, based on definitions from wsdl, Java files for both model and skeleton of your service. In addition it has also created folder resources/, containing necessary axis2 configuration files and build.xml with helper tasks used to build Axis2 service jar (aar).
Most important file to notice is: ShippingServiceSkeleton, that is file you should modify and put your bussines logic in it.
I have decided not to put bussines logic in it, but rather to create separate Java classes which implement bussines logic and is called from ShippingServiceSkeleton.
To do so I have created new simple interface:
ShippingOrderManager (which represents entry point to bussines implementation) and Dummy implementation of this interface (ShippingOrderManagerDummyImpl).

Following listing give content of both files:


package net.codah.sm.bussines;

import net.codah.sm.ws.schemas.ShippingOrderRequest;
import net.codah.sm.ws.schemas.ShippingOrderResponse;

public interface ShippingOrderManager {

ShippingOrderResponse processOrder(ShippingOrderRequest shippingRequest);
}

[ShippingOrderManager.java]

package net.codah.sm.bussines;

import java.math.BigDecimal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.codah.sm.ws.schemas.AmmountType;
import net.codah.sm.ws.schemas.ShippingOrderRequest;
import net.codah.sm.ws.schemas.ShippingOrderResponse;

public class ShippingOrderManagerDummyImpl implements ShippingOrderManager {

protected final Log logger = LogFactory.getLog(ShippingOrderManager.class);

public ShippingOrderResponse processOrder(ShippingOrderRequest shippingOrderRequest) {
logger.debug("RECIEVED: " + shippingOrderRequest);
ShippingOrderResponse response = new ShippingOrderResponse();
response.setShippingId(new Long(System.currentTimeMillis()).toString());
response.setShippingAgencyId("SomeAgencyID");
AmmountType cost = new AmmountType();
cost.setAmmount(new BigDecimal(43.59));
cost.setCurrencyCode("EUR");
response.setCost(cost);

return response;
}

}

[ShippingOrderManagerDummyImpl]
And finally ShippingServiceSkeleton.java now look like this:


package net.codah.sm.ws.services;

import net.codah.sm.bussines.ShippingOrderManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* ShippingServiceSkeleton java skeleton for the axisService
*/
public class ShippingServiceSkeleton implements ShippingServiceSkeletonInterface {

protected final Log logger = LogFactory.getLog(ShippingServiceSkeleton.class);

protected ShippingOrderManager shippingOrderManager;
/**
* Auto generated method signature
* @param shippingOrderRequest0
*/
public net.codah.sm.ws.schemas.ShippingOrderResponse processOrder(
net.codah.sm.ws.schemas.ShippingOrderRequest shippingOrderRequest0) {

return shippingOrderManager.processOrder(shippingOrderRequest0);
}

public void setShippingOrderManager(ShippingOrderManager shippingOrderManager) {
this.shippingOrderManager = shippingOrderManager;
}
}

As can be seen from the code above ShippingServiceSkeleton has setter for ShippingOrderManager, in order to wire it up with ShippingOrderManagerDummyImpl I have set up Axis2 to cooperate with spring.

3. Axis2 and Spring Configuration
Few years ago I have hooked up to Spring, and today can not any more imagine Java development without it. So I decided to integrate Spring IoC with my Axis2 Web Service. There are several ways how you can integrate Spring into Axis2, detailed instructions can be found here,
I have chosen the simples approach, one which relies on web.xml to load up Application Context over listener.
First put spring.jar file into WEB-INF/lib fodler, then modify web.xml file by adding following lines:


<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Then create applicationContex.xml like the one bellow and put it to WEB-INF/ folder.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

<!-- Axis2 Web Service -->
<bean id="shippingService" class="net.codah.sm.ws.services.ShippingServiceSkeleton">
<property name="shippingOrderManager" ref="shippingOrderManager"/>
</bean>

<bean id="shippingOrderManager" class="net.codah.sm.bussines.ShippingOrderManagerDummyImpl">
</bean>

</beans>

Now open file service.xml from resources folder. This is a file created by wsdl2java script and is used by Axis2 to describe the service. Now change it so it looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!-- This file was auto-generated from WSDL -->
<!-- by the Apache Axis2 version: 1.3 Built on : Aug 10, 2007 (04:45:47 LKT) -->
<serviceGroup>
<service name="ShippingService">
<parameter name="ServiceObjectSupplier">org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier</parameter>
<parameter name="SpringBeanName">shippingService</parameter>
<parameter name="useOriginalwsdl">true</parameter>
<parameter name="modifyUserWSDLPortAddress">true</parameter>
<operation name="processOrder" mep="http://www.w3.org/ns/wsdl/in-out">
<actionMapping>http://codah.net/ShippingOrderRequest</actionMapping>
<outputActionMapping>http://codah.net/sm/ws/services/ShippingServicesPortType/ShippingOrderResponse</outputActionMapping>
</operation>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/ns/wsdl/in-out" class="net.codah.sm.ws.services.ShippingServiceMessageReceiverInOut"/>
</messageReceivers>
</service>
</serviceGroup>

Two key lines in the configuration above are:

<parameter name="ServiceObjectSupplier">org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier</parameter>
<parameter name="SpringBeanName">shippingService</parameter>

which tell axis to take Service object from Spring Application context.

Now that we have Spring appliction context set up, and integrated with axis the only remaining thing to do is to change ant script that builds service aar file which is deployed in Axis2 Web Application, in WEB-INF/services/ folder.

4. Change Ant build
The problem is that default ant task "jar.server" which is used to create aar file will compile into it also the Java classes, which since they are in src/ folder now we do not need there. All we need in ShippingService.arr file now are the following files:
./ShippingService.aar
./META-INF
./META-INF/MANIFEST.MF
./META-INF/services.xml
To do so all you need to do is exclude java classes from jar creation prcess, locata and change following lines in build.xml file (jar.server task):

<jar destfile="${lib}/${name}.aar">
<fileset excludes="**/Test.class" dir="${classes}"/>
</jar>

so they would look like this:

<jar destfile="${lib}/${name}.aar">
<fileset excludes="**/*.class" dir="${classes}"/>
</jar>

Now execute ant task "jar.server" and drop newly created ShippingService.arr file from the build/lib/ folder to the WEB-INF/services/ folder and you should have fully functional service.
Now its time to add some security to this service, we will do this in next article of this series, until then...

ponedjeljak, 11. veljače 2008.

Java and Web Services Security: part 0

Prologue
Since my last post, I have been doing a lot of research, and playing with several available Java web services API-s, to see what are the benefits and goals of each in particular. In the process, I have checked out JAX-WS, Axis2 and Spring Web Services. Of the three I was quite disappointed by JAX-WS, not so much with the implementation (although I do not like annotations) but rather with the available documentation and working samples. Working with JAX-WS was like German advance on the Eastern Front, slow, painful and frustrating.... so I gave up on it and concentrated on the remaining two.
The goal I set up before my self, when working with each of the above API-'s was to develop simple Web Service using particular Java API and then to apply WS Security to it in the same way as I have done it in the CXF example, namely to add
UsernameToken and Timestamp token to the message header and finally to sign it. Other rule I decided to adher to was to ALWAYS use the "Top Down" or if you more like it "Contract first" approach in development of my web services. For those interested why I have decided to to so, there is a very good argumentation in favor of this approach on the Spring web services site. Furthermore I have decided to turn this (on going) private initiative into series of web articles all dedicated to Java Web Services Security.

The Service
The first thing I needed for my research was a specification of the web service I wanted to develop. I have decided to always work with the same Web Service, preferably one that has some resemblance with real life Web Services, and thus have devised the following Web Service, one used by virtual shipping company to receive shipping orders from various partners.
As its input Service accepts following data structures:

<shippingorderrequest>
<partnerid>PartnerId</partnerid>
<shippingdestination>
<fullname>FullName</fullname>
<city>City</city>
<zipcode>ZipCode</zipcode>
<country>Country</country>
<phone>Phone</phone>
</shippingdestination>
<orders>
<product>
<id>Id</id>
<name>Name</name>
<quantity>0</quantity>
<giftwrap>n</giftwrap>
<giftnote>Some text...</giftnote>
</product>
</orders>
<shippingmethod>STANDARD</shippingmethod>
</shippingorderrequest>

And after processing of the order (finding best transport company, nearest storage that contains ordered item etc.) it returns following data structure:


<ShippingOrderResponse>
<ShippingId>ShippingId</ShippingId>
<ShippingAgencyId>ShippingAgencyId</ShippingAgencyId>
<Cost>
<Ammount>59.99</Ammount>
<CurrencyCode>EUR</CurrencyCode>
</Cost>
</ShippingOrderResponse>

For the start I decieded that there should be only one operation called "processOrder" and that I will add other (orderStatus, cancelOrder etc...) later. Thus I wrote the shipping-service.wsdl file, which describes my simple service:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:schema="http://codah.net/sm/ws/schemas"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://codah.net/sm/ws/services"
targetNamespace="http://codah.net/sm/ws/services">

<wsdl:types>

<xs:schema elementFormDefault="qualified"
xmlns:sm="http://codah.net/sm/ws/schemas"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://codah.net/sm/ws/schemas">

<xs:element name="ShippingOrderRequest">
<xs:complexType>
<xs:all>
<xs:element name="PartnerId" type="xs:string" />
<xs:element name="ShippingDestination" type="sm:AddressType" />
<xs:element name="Orders">
<xs:complexType>
<xs:sequence minOccurs="1" maxOccurs="unbounded">
<xs:element name="Product" type="sm:ProductOrderType" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="ShippingMethod">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="STANDARD"/>
<xs:enumeration value="EXPEDITED"/>
<xs:enumeration value="PRIORITY"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:all>
</xs:complexType>
</xs:element>

<xs:element name="ShippingOrderResponse">
<xs:complexType>
<xs:all>
<xs:element name="ShippingId" type="xs:string" />
<xs:element name="ShippingAgencyId" type="xs:string" />
<xs:element name="Cost" type="sm:AmmountType" />
</xs:all>
</xs:complexType>
</xs:element>

<xs:complexType name="AddressType">
<xs:sequence>
<xs:element name="FullName" type="xs:string" />
<xs:element name="City" type="xs:string"/>
<xs:element name="State" type="xs:string" minOccurs="0"/>
<xs:element name="ZipCode" type="xs:string"/>
<xs:element name="Country" type="xs:string"/>
<xs:element name="Phone" type="xs:string"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="ProductOrderType">
<xs:sequence>
<xs:element name="Id" type="xs:string" />
<xs:element name="Name" type="xs:string" />
<xs:element name="Quantity" type="xs:integer" />
<xs:element name="GiftWrap" default="n" >
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[yn]"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="GiftNote" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="AmmountType">
<xs:sequence>
<xs:element name="Ammount" type="xs:decimal" />
<xs:element name="CurrencyCode" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>

</wsdl:types>

<!-- messages -->
<wsdl:message name="ShippingOrderRequest">
<wsdl:part element="schema:ShippingOrderRequest" name="ShippingOrderRequest" />
</wsdl:message>

<wsdl:message name="ShippingOrderResponse">
<wsdl:part element="schema:ShippingOrderResponse" name="ShippingOrderResponse" />
</wsdl:message>

<!-- portType -->
<wsdl:portType name="ShippingServicesPortType">
<wsdl:operation name="processOrder">
<wsdl:input message="tns:ShippingOrderRequest" name="ShippingOrderRequest" />
<wsdl:output message="tns:ShippingOrderResponse" name="ShippingOrderResponse" />
</wsdl:operation>
</wsdl:portType>

<!-- bindings -->
<wsdl:binding name="ShippingServicesBinding" type="tns:ShippingServicesPortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="processOrder">
<soap:operation soapAction="http://codah.net/ShippingOrderRequest" />
<wsdl:input name="ShippingOrderRequest">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="ShippingOrderResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>

<!-- service -->
<wsdl:service name="ShippingService">
<wsdl:port binding="tns:ShippingServicesBinding" name="ShippingServicePort">
<soap:address location="http://localhost:8080/axis2/" />
</wsdl:port>
</wsdl:service>

</wsdl:definitions>
Above listing concludes this preparatory post, next one in series will be dealing with implementation of the above service with Axis2.

Until then....

četvrtak, 30. kolovoza 2007.

CXF, Spring and WS-Security putting it all togeather - part 1

Last few days I was playing with Apache CXF (an open source services framework). CXF is a continuation of the popular Codehaus project named: XFire and is considered to be XFire 2.0 (more on concerning this merge can be found here: XFire/Celtix merge FAQ). Anyway, although CXF is still in Apache incubator, project seems quite promising and has lots of very interesting features, so i gave it a try. Since the documentation concerning Ws-Security on the Cxf Web is very thin I have also deceided to share some of my experiences, so hence comes this post.

1. Defining a Goal
My goal was to create simple HelloWorld service, and secure it with WS-Security, all this of course done with CXF. Full specification of Ws-Security can be found at Oasis Web site but in short we can say that WS-Security provides following functionality to the WebServices:
  • Pass authentication tokens between services
  • Encrypt messages or parts of messages
  • Sign messages
  • Timestamp messages
WS-Security defines several authentication tokens that can be exchanged:
In this first part i will limit myself only to UsernameToken, other tokens will be focus of some of my next blogs.
Therefore, the goal of this excersise is to create HelloWorld WebService and protect it with WS-Secuirtiy, so that it accepts only SOAP calls which in their headers have timestamp, UsernameToken and are signed by known entity.

2. Service and Client Creation with Spring, CXF
For this excersise I took web service from here (Writing a Web Service with Spring) .
So after going through the steps describe above i had fully functional Web Service and Client.
Service has only one operation sayHi() which excepts String as an input param, and returns String as an result of the operation.
In order to be able test service and client I wrote one java main class: HelloClient.

package demo.spring;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloClient {

protected static final Log logger = LogFactory.getLog(HelloClient.class);
/**
*
* @param args
*/
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/clientAppContext.xml");
HelloWorld client = (HelloWorld) context.getBean("client");
logger.debug("Client invoking WS:");
String text = client.sayHi("Domagoj");
logger.debug("Response: " + text);
}

}
[HelloClient.java]

As can be seen from the above we also need clientApplicationContext.xml on classpath so here it is.

<beans xmlns="http://www.springframework.org/schema/beans" xsi="http://www.w3.org/2001/XMLSchema-instance" jaxws="http://cxf.apache.org/jaxws" schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schema/jaxws.xsd">
<bean id="client" class="demo.spring.HelloWorld" bean="clientFactory" method="create">
<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="demo.spring.HelloWorld">
<property name="address" value="http://localhost:8080/SoaLab/HelloWorld">
</property>
</bean>
</beans>
[clientApplicationContext.xml]

Ok now we have all set up and Ws-Security can be added.

3. Securing the Service
Ws-Security is in cxf implemented through Interceptors. In order to protect WebService one must register necessary Interceptors. The first step is to add SAAJInInterceptor and WSS4JInInterceptor to your web Service. Code snippet bellow shows configuration of the service with added interceptors.

<jaxws:endpoint id="helloWorld" implementor="demo.spring.HelloWorldImpl" address="/HelloWorld">
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature"/>
</jaxws:features><
<jaxws:ininterceptors>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor"/>
<ref bean="wss4jInConfiguration"/>
</jaxws:ininterceptors>
/jaxws:endpoint>

<bean id="wss4jInConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
</bean>
[beans.xml]

On the client side Out interceptors corresponding to the server side configuration must be added, here it is how now changed configuration of the client looks like.

<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="serviceClass" value="demo.spring.HelloWorld">
<property name="address" value="http://localhost:8080/SoaLab/HelloWorld">
<property name="outInterceptors">
<list>
<bean class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
<ref bean="wss4jOutConfiguration" />
</list>
</property>
</bean>

<bean id="wss4jOutConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
</bean>
[clientAppContext.xml]

Configuration above does nothing since there are no actions specified either for WSS4JInInterceptor or WSS4jOutInterceptor, so lets add timestamp to it.

3.1 Setting up Timestamp

Setting up timestamp is pretty easy, just add following lines to the both wss4jInConfiguration and wss4jOutConfiguration beans.

<property name="properties">
<map>
<entry key="action" value="Timestamp">
</entry></map>
</property>

So now WSS4JInInterceptor on the service side expect that request that he receives have timestamp in them, and on the client side WSS4JOutInterceptor is responsible for adding timestamp to every SOAP request.

3.2. Username Token

Second step is to add username token authentication to the service. UsernameToken contains credentials ( username and password) that client presents when he want s to invoke the service. On the service side modify configuration of the wss4jInConfiguration bean so that now it looks like this:

<bean id="wss4jInConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<property name="properties">
<map>
<entry key="action" value="UsernameToken Timestamp"/>
<entry key="passwordType" value="PasswordDigest" />
<entry>
<key>
<value>passwordCallbackRef</value>
</key>
<ref bean="passwordCallback"/>
</entry>
</map>
</property>
</bean>

<bean id="passwordCallback" class="demo.interceptors.server.PasswordCallbackHandler"/>

As can be seen key "action" now holds two values: UsernameToken and Timestamp! Rest is configuration necessary for the UsernameToken action.

<entry key="passwordType" value="PasswordDigest"/>

This specify's that password used in this token is not plain text but rather it should be hashed.
Finally the last peace of configuration is passwordCallback bean. PassworCallbackHandler class should implement javax.security.auth.callback.CallbackHandler interface, which specify only one method:

void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException;


This method is repsonsible to retrive password and return it so that Wss4JInterceptor can compare that password with one that came with token.
My PasswordCallbackHandler.class for this demo is very simple:


package demo.interceptors.server;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSPasswordCallback;

public class PasswordCallbackHandler implements CallbackHandler {

protected final Log logger = LogFactory.getLog(getClass());

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
logger.debug("identifier: " + pc.getIdentifer());

if (pc.getIdentifer().equals("ws-client")) {
// set the password on the callback. This will later be compared to the
// password which was sent from the client.
pc.setPassword("password");
}
}

}
In a real life case, this class should connect to some storage (LDAP, Database) and check for password there.
Now its time to configure the client to send all the necessary security information. Change clientAppContext.xml so it looks like this:

<bean id="wss4jOutConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<property name="properties">
<map>
<entry key="action" value="UsernameToken Timestamp"/>
<entry key="user" value="ws-client" />
<entry key="passwordType" value="PasswordDigest" />
<entry>
<key>
<value>passwordCallbackRef</value>
</key>
<ref bean="passwordCallback"/>
</entry>
</map>
</property>
</bean>
<bean id="passwordCallback" class="demo.interceptors.client.PasswordCallback" />

[cllientAppContext.xml]

As can be seen this configuration is pretty similar to that of the server, only exception is following line:

<entry key="user" value="ws-client" />

Which tells to the wss4jOutInterceptor that it should set username in UsernameToken to the "ws-client" value.
So far client side password handler is basically the same as that on the server.

3.3. Signing of the request

Finally its time to sign request. In order to sign requests private and public keys for the client must be created and client's public key imported into server's keystore.
To do this follow the instructions at the CXF Website and create client keys for alias "ws-client." Store both client keys in client-keystore.jks protected with password: "keyStorePassword" and also import client's public key into server_publicstore.jks also protected with password: "keyStorePassword."
Now put both client-keystore.jks and server_publicstore.jks in the classpath and confiogure service so that it expects signed requests.
Change beans.xml configuration file of the service so that it finally looks like this:

<bean id="wss4jInConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<property name="properties">
<map>
<entry key="action" value="UsernameToken Timestamp Signature"/>
<entry key="passwordType" value="PasswordDigest" />
<entry>
<key>
<value>passwordCallbackRef</value>
</key>
<ref bean="passwordCallback"/>
</entry>
<entry key="signaturePropFile" value="server_sign.properties"></entry>
</map>
</property>
</bean>
[beans.xml]

Configuration properties for Signature action are stored in a separate file server_sign.properties:

org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keyStorePassword
org.apache.ws.security.crypto.merlin.file=server_publicstore.jks
[server_sign.properties]

Similar changes needs to be done also on the client side. Here is how clientAppContext.xml should finally look like:

<bean id="wss4jOutConfiguration" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<property name="properties">
<map>
<entry key="action" value="UsernameToken Timestamp Signature"/>
<entry key="user" value="ws-client"/>
<entry key="passwordType" value="PasswordDigest"/>
<entry key="signaturePropFile" value="client_sign.properties"/>
<entry>
<key>
<value>passwordCallbackRef</value>
</key>
<ref bean="passwordCallback"/>
</entry>
</map>
</property>
</bean>
[clientAppContext.xml]
And here it is, content of the client_sign.properties file:
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.password=keyStorePassword
org.apache.ws.security.crypto.merlin.keystore.alias=ws-client
org.apache.ws.security.crypto.merlin.file=client_keystore.jks

But, one more thing needs to be done: demo.interceptors.client.PasswordCallback should be change. Since this class will be called both by Signature action and UsernameToken action. UsernameToken action calls it in order to get password for ws-client user and Signature action calls it in order to retrive the password that was used when client key's were generated. Here is source for the client PasswordCallback.java

package demo.interceptors.client;

import ... (ommited)

public class PasswordCallback implements CallbackHandler {

protected final Log logger = LogFactory.getLog(getClass());

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

for (int i=0; i< callbacks.length; i++) {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

int usage = pc.getUsage();
logger.debug("identifier: " + pc.getIdentifer());
logger.debug("usage: " + pc.getUsage());

if (usage == WSPasswordCallback.USERNAME_TOKEN) {
// username token pwd...
pc.setPassword("password");
} else if (usage == WSPasswordCallback.SIGNATURE) {
// set the password for client's keystore.keyPassword
pc.setPassword("keyPassword");
}
}
}

}
[PasswordCallback.java]

As can be seen based on the call to the getUsage() method PasswordCallback handler can determine which action invoked him and return the appropriate key.

4. Summary
So, in this post we have configured WSS4JxxxInterceptors to put/read timestamp, UsernameToken and Signatrue in/from Security header. On the Server side WSS4JInterceptor will performe following checks:
  • If Header contains UsernameToken, if one exist call PasswordCallback handler to retrieve password for username found in that token, and finaly hash that password and compare it to one from Token.
  • Check if message has timestamp
  • Check for valid signature

On the client side, follwing actions are performed by WSS4jOutInterceptor:
  • UsernameToken is constructed and put into Security Header
  • Message is Timestamped
  • Message is Signed

And thats all folks.

In the begining there was ...


After prolonged avoiding of Blogg phenomenon I have finally raised a white flag, and decided to start my own. As the saying goes if you cant beet them join them! That said, lets reveal the BIG mystery. No, not that one! Dan Brown did that in his stupid, excuse me, popular(!) book about some code and God's design and Catholic church and dolphins, hmm, or were the dolphins somewhere else... i do not remember any more. Anyway, the big mystery i am referring to is what is this blog going to be about?
I believe that it is best to be honest (at least at the beginning of any relationship, we can see for later), so same as all other blogs, at the first place, this one also aims at personal self promotion. On the second place comes its technical aspect, meaning, potential visitors would be able to find here my notes, tips and experiences concerning various IT technologies i am playing with at the moment. So enough talk... let the blogging begins.