I was really excited about the new release of Spring Web Services (M3) and its new client side API. I like the simplicity and the elegance of the template based approach which is already used in various parts of Spring. In this post, I will show you how I used XWSS to add WS-Security support to web services invocations.
I used XWSS 2.0 from jwsdp-2.0. I had to add xmlsec.jar from the jwsdp-shared/lib in order for the example to work.
Server Side
As a simple example, I will configure the web service in the Echo sample to require a wss user name token.To add WS-Security support, we need to add a XwsSecurityInterceptor to spring-ws-servlet.xml. The call back handler is a SimplePasswordValidationCallbackHandler that expects a user named "cafe" and "babe" as a password.
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="classpath:wss-server-config.xml" /> <property name="callbackHandlers"> <list> <bean id="passwordValidationHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="cafe">babe</prop> </props> </property> </bean> </list> </property> </bean>wsSecurityInterceptor has to be added to the list of interceptors of payloadMapping
<property name="interceptors"> <list> <ref local="loggingInterceptor" /> <ref local="wsSecurityInterceptor" /> <ref local="validatingInterceptor" /> </list> </property>Finally, the server side XWSS configuration is read from a file named wss-server-config.xml from the classpath. With this configuration, XWSS will expect a WS-Security header with a user name token
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/> </xwss:SecurityConfiguration>
Client Side
Adding WS-Security support to the client side is quite simple. Spring-WS client provides WebServiceMessageCallback in order to operate on the message before it is sent. We will use it to replace the message with a secured one. But first we have to configure XWSS on the client side. In the XML configuration file, we will instruct XWSS to include a user name token in the SOAP header.<xwss:SecurityConfiguration dumpMessages="true" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:UsernameToken name="cafe" password="babe" useNonce="false" digestPassword="false"/> </xwss:SecurityConfiguration>The dumpMessages=true will cause the secured message to be printed in the console which is useful for debugging. A full description of the available configuration options can be found here.
In EchoClient.java we need to use a com.sun.xml.wss.XWSSProcessor and a com.sun.xml.wss.ProcessingContext to process the message.
public class EchoClient extends WebServiceGatewaySupport { .... private static XWSSProcessor cprocessor; private static ProcessingContext context; .... public static void main(String[] args) throws IOException, XWSSecurityException { .... Resource xwssConfig = new ClassPathResource("wss-client-config.xml"); if (!xwssConfig.exists()) { throw new FileNotFoundException(); } XWSSProcessorFactory factory = XWSSProcessorFactory.newInstance(); cprocessor = factory.createProcessorForSecurityConfiguration(xwssConfig .getInputStream(), null); context = new ProcessingContext();The interesting part is in the echo() method. We will use the sendAndReceive that accepts a WebServiceMessageCallback. Please note that XWSS only works with SAAJ so SaajSoapMessageFactory have to be used.
getWebServiceTemplate().sendAndReceive(requestSource, new WebServiceMessageCallback() { public void doInMessage(WebServiceMessage message) throws IOException { // We can cast safley to SaajSoapMessage SaajSoapMessage saajSoapMessage = (SaajSoapMessage) message; SOAPMessage saajMessage = saajSoapMessage.getSaajMessage(); try { context.setSOAPMessage(saajMessage); SOAPMessage securedMessage = cprocessor .secureOutboundMessage(context); saajSoapMessage.setSaajMessage(securedMessage); } catch (XWSSecurityException e) { throw new XwsSecuritySecurementException(e.getMessage()); } } }, result);If every thing works ok, you should see this on your console (name spaces omitted for brevity):
INFO: ==== Sending Message Start ==== <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> <SOAP-ENV:Header> <wsse:Security xmlns:wsse="..." SOAP-ENV:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="..." wsu:Id="XWSSGID-1172860158358-935964365"> <wsse:Username>cafe</wsse:Username> <wsse:Password Type="...#PasswordText">****</wsse:Password> </wsse:UsernameToken> </wsse:Security> </SOAP-ENV:Header> <SOAP-ENV:Body> <echoRequest xmlns="...">Hello</echoRequest> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ==== Sending Message End ====