Sunday, May 22, 2011

Enabling WS-Security Username Token Profile for Apache CXF service

This article describes how to create a code first webservice. I am going to extend the sample provided to support WS-Security Username Token profile. It is a way for the callers of the service to prove their identity by providing username and a password.

To recap the previous article, it is very simple to expose a code first webservice using Apache CXF with Spring. Apache CXF provides a custom Spring namespace to easily configure the endpoint:

<?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:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:jaxws="http://cxf.apache.org/jaxws"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

 <import resource="classpath:META-INF/cxf/cxf.xml"/>
 <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
 

 <bean id="memberendpoint" class="org.bk.memberservice.endpoint.DefaultMemberEndpoint"/>
 
 <jaxws:endpoint address="/memberservice" id="memberservicehttp" implementor="#memberendpoint" >
 </jaxws:endpoint>

</beans>

This exposes the memberendpoint bean above as a fully configured webservice.

To secure this service using usernametoken, first implement a callback for CXF to invoke to validate the credentials passed by the user:

package org.bk.memberservice.endpoint;

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.ws.security.WSPasswordCallback;

public class UsernameTokenCallback implements CallbackHandler {

 @Override
 public void handle(Callback[] callbacks) throws IOException,
   UnsupportedCallbackException {
  Callback callback = callbacks[0];
  WSPasswordCallback pc = (WSPasswordCallback) callback;
//              Retrieve and set the real password..which will be validated 
//              by CXF validator against the digest password
pc.setPassword("test");
  System.out.println("Received cred: " + pc.getIdentifier() + " : " + pc.getPassword());
//  validate the credentials
//              boolean isValid = true;
//              throw IO Exception if the credentials are not valid
//  if (!isValid) {
//   throw new IOException("Bad Credentials");
//  }
 }
}

To wire this Callback handler with the service endpoint, CXF uses a concept called the interceptor, basically the webservice call is handled by the interceptors before being handed over to the service.


<jaxws:endpoint address="/memberservice" id="memberservicehttp"
  implementor="#memberendpoint">
  <jaxws:inInterceptors>
   <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
    <constructor-arg>
     <map>
      <entry key="action" value="UsernameToken" />
      <entry key="passwordType" value="PasswordDigest" />
      <entry key="passwordCallbackRef">
       <ref bean="usernameTokenCallback" />
      </entry>
     </map>
    </constructor-arg>
   </bean>
  </jaxws:inInterceptors>
 </jaxws:endpoint>

That's it!! the endpoint is now secured using UsernameToken profile. To test this, bring up the endpoint, use SOAP UI to send a normal request, it will fail with the message that a ws-security header is required - this is the username token soap header that is expected as part of the request.

 Fix this by adding the username and password, select "digest" as the password type and the service invocation should just work.
Updated Sample available at: git://github.com/bijukunjummen/memberservice-codefirst.git
Reference:
1. WS-Security reference in Wikipedia: http://en.wikipedia.org/wiki/WS-Security
2. WS-Secuirty usernametoken profile specs at OASIS: http://www.oasis-open.org/committees/download.php/16782/wss-v1.1-spec-os-UsernameTokenProfile.pdf
3. Apache CXF reference: http://cxf.apache.org/docs/ws-security.html
4. New changes as part of WSS4J - http://coheigea.blogspot.com/2011/02/wspasswordcallback-changes-in-wss4j-16.html


2 comments:

  1. However, the WSDL doesn't contain the security policy. This make it difficult for client to know that WS-Security is to be used.

    ReplyDelete
  2. nicely & cleanly explained
    Raman

    ReplyDelete