jaxws client and ws security

 

Wikipedia defines WS - Security as :

WS-Security (Web Services Security, short WSS) is a flexible and feature-rich extension to SOAP to apply security to web services. It is a member of the WS-* family of web service specifications and was published by OASIS.

Setting up web service security can be very tricky. I am going to document the steps I have taken in the past to solve this problem.

I would be documenting the use of  a Username Token solution in this post. Username Token involves applying a SOAP handler.

The SOAP handler acts as an interceptor by intercepting the out going message and inserting a SOAP header containing the Username Token  attribute into the SOAP envelope.

The Security handler class below intercepts an out going SOAP message and inserts a WS-Security element in the middle of the SOAP header. It uses XWSSProcessor interface to accomplish this task. XWSSProcessor has been included in  metro's  webservice-rt.jar  file. SecurityHandler.java

import javax.xml.soap.SOAPMessage;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import com.sun.xml.wss.ProcessingContext;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSecurityException;

public class SecurityHandler 
             extends LogHandler {
    public boolean handleMessage(SOAPMessageContext soapMessageContext) {
	try {
	   super.handleMessage(soapMessageContext);
	   boolean outMessageIndicator = 
                  (Boolean) soapMessageContext
			   .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
	     if (outMessageIndicator) {
		XWSSProcessor xwssProcessor = 
                            SecurityConfigProcessorFactory
					.getXWSSProcessor();
		SOAPMessage message = soapMessageContext.getMessage();
		ProcessingContext context = xwssProcessor
				   .createProcessingContext(message);
		context.setSOAPMessage(message);
		SOAPMessage securedMsg = xwssProcessor
					.secureOutboundMessage(context);
		soapMessageContext.setMessage(securedMsg);
			}
	} catch (XWSSecurityException ex) {
	   throw new WebServiceException(ex);
	}
	 return true;
	}
}

 

 

LogHandler.java logs both incoming and outgoing messages. LogHandler.java

 

import java.io.IOException; 
import java.util.HashSet; 
import java.util.Set; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.xml.namespace.QName; 
import javax.xml.soap.SOAPException; 
import javax.xml.soap.SOAPMessage; 
import javax.xml.ws.handler.MessageContext; 
import javax.xml.ws.handler.soap.SOAPHandler; 
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class LogHandler 
         implements SOAPHandler<SOAPMessageContext>{ 
    @Override 
    public boolean handleMessage(SOAPMessageContext soapMessageContext) { 
	log(soapMessageContext); 
	return true; 
    } 

    @Override 
    public boolean handleFault(SOAPMessageContext context) { 
	return true; 
    }
    
    @Override 
    public void close(MessageContext context) {
		
    } 
	
    @Override 
    public Set<QName> getHeaders() { 
	return new HashSet<QName>(); 
    } 
	
    private void log(SOAPMessageContext messageContext) { 
	SOAPMessage msg = messageContext.getMessage(); 
	try {
	    msg.writeTo(System.out); 
	} catch (SOAPException ex) { 
	    Logger.
            getLogger(LogHandler.class.getName()).
                      log(Level.SEVERE, null, ex); 
	} catch (IOException ex) { 
	    Logger.
            getLogger(LogHandler.class.getName()).
                      log(Level.SEVERE, null, ex); 
	}
    } 
	  
	
}

 

 

The SecurityConfigProcessorFactory class creates the  XWSSProcessor implementation by reading in the security-config.xml file from the class path. SecurityConfigProcessorFactory.java

 

import java.io.InputStream;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSProcessorFactory;
import com.sun.xml.wss.XWSSecurityException;

public class SecurityConfigProcessorFactory {
  protected static XWSSProcessor xwssProcessor;

  public static XWSSProcessor getXWSSProcessor() throws XWSSecurityException {
     if (xwssProcessor == null) {
	 InputStream serverConfig = 
               SecurityConfigProcessorFactory.class
		.getResourceAsStream("security-config.xml");
	 XWSSProcessorFactory factory = XWSSProcessorFactory.newInstance();
	 xwssProcessor = factory.
                createProcessorForSecurityConfiguration(
		  serverConfig, new SecurityCallbackHandler());
		}
	 return xwssProcessor;
     }
}

 

The SecurityCallbackHandler class is the class that actually inserts the authentication credentials to the Username and password callbacks. SecurityCallbackHandler.java

 

 

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.callback.CallbackHandler;
import com.sun.xml.wss.impl.callback.PasswordCallback;
import com.sun.xml.wss.impl.callback.UsernameCallback;

public class SecurityCallbackHandler implements CallbackHandler {
   public void handle(Callback[] callbacks) 
           throws IOException,UnsupportedCallbackException {
	   for (Callback callback : callbacks) {
		if (callback instanceof UsernameCallback) {
		    ((UsernameCallback) callback).setUsername("username");
		} else if (callback instanceof PasswordCallback) {
		    ((PasswordCallback) callback).setPassword("password");
		} else {
		    throw new UnsupportedCallbackException(callback);
		}
	   }
   }
}

 

security-config.xml

 

 <?xml version="1.0" encoding="UTF-8"?>
<xwss:SecurityConfiguration 
        xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"
	dumpMessages="true">
  <xwss:UsernameToken digestPassword="false" useNonce="false" />
</xwss:SecurityConfiguration>

 

Add this dependency to your maven pom file or just download the jar file and put it on you class path

 

<dependency>
	<groupId>com.sun.xml.ws</groupId>
	<artifactId>webservices-rt</artifactId>
	<version>2.0</version>
</dependency>

 

Now that you have all the major players, you would need to either generate you client stubs by using wsimport or access the web service endpoint dynamically by using the dispatch interface.

You can also avoid using XWSSProcessor interface by manually creating the SOAP header in the SOAPHandler. In your SOAPHanlder's  handleMessage method.

 

public boolean handleMessage
     (SOAPMessageContext soapMessageContext) {
     try {
	boolean outMessageIndicator = 
           (Boolean) soapMessageContext
		    .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
			
       if (outMessageIndicator) {
	  SOAPEnvelope envelope = soapMessageContext.
                                  getMessage()
				  .getSOAPPart().getEnvelope();
				
          SOAPHeader header = envelope.addHeader();
	  SOAPElement security = header
			      .addChildElement(
          "Security",
	  "wsse",
	  "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
				
         SOAPElement usernameToken = 
               security.addChildElement("UsernameToken", "wsse");
				
         usernameToken.addAttribute(
		       new QName("xmlns:wsu"),
	"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
				
        SOAPElement username = usernameToken.
                              addChildElement("Username", "wsse");
        username.addTextNode("TestUser");
				
        SOAPElement password = usernameToken.   
                               addChildElement("Password", "wsse");
	password.setAttribute(
			 "Type",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
       password.addTextNode("TestPassword");
	}
  
    } catch (Exception ex) {
	throw new WebServiceException(ex);
    }
    return true;
}

You can avoid including the webservice-rt.jar in your project if you choose to include the SOAPHeader and UsernameToken manually. This is a simple example of how you can connect to a web service endpoint secured with WS - Security. I hope you find this code snippets included in this post helpful.