Wednesday, December 14, 2011

Ws security and Jax WS

I had some annoying problems with the WS security examples found on the net. I had an application which simply refused to pass along the header data as they should. I followed every tutorial available and did like this;

bp.getRequestContext().put(javax.xml.ws.BindingProvider.USERNAME_PROPERTY, "admin");
bp.getRequestContext().put(javax.xml.ws.BindingProvider.PASSWORD_PROPERTY,"adminpwd")

So I had to make the header of the request myself. This is how I did it. As always, even though it doesn't matter I made it in Eclipse building it in Maven.

Server files

On the server side a made on class, call it Service.

@WebService

@HandlerChain(file="MyChain.xml")

public class Service {

@WebMethod

public String getName(){

return "King Kong";

}

}

Note that the file path is relative so for simplicity I just put the file (MyChain.xml) in the same directory as this file, i.e. in the source package.

Thexml file just contain a pointer towards a handler.








com.aja.util.MySOAPHandler




Now we only lack the handler class.

*****************************************************

package com.aja.util;

/**

import java.io.PrintStream;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;
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;

import java.io.PrintStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


public class MySOAPHandler implements SOAPHandler
{
private static final String WS_SEC = "wsse:Security";
private static final String WSSE_USERNAME = "wsse:Username";
private static final String WSSE_PASSWORD = "wsse:Password";

protected PrintStream out = System.out;

protected String HandlerName = "Handler";

@PostConstruct
public void init()
{
}

@PreDestroy
public void destroy()
{
}

/**
* {@inheritDoc}
*/
public void close(@SuppressWarnings("unused")
MessageContext context)
{
}

/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public boolean handleMessage(SOAPMessageContext messagecontext)
{

try
{
SOAPMessage message = messagecontext.getMessage();
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPHeader header = envelope.getHeader();
String username;
String password;

Element sec = null;

for (Iterator k = header.getChildElements(); k.hasNext();)
{
Element element = k.next();
if (element.getNodeName().equals(WS_SEC))
{
sec = element;

NodeList e = sec.getElementsByTagName(WSSE_USERNAME);

Node tok = e.item(0);

NodeList child = tok.getChildNodes();
if (child != null)
{
username = child.item(0).getNodeValue();
if (!(username.equals("admin")))
{
return false;
}
}

NodeList f = sec.getElementsByTagName(WSSE_PASSWORD);

Node t = f.item(0);
NodeList c = t.getChildNodes();
if (c != null)
{
password = c.item(0).getNodeValue();
if (!(password.equals("adminpwd")))
{
return false;
}
}

}
}

}
catch (Exception e)
{
return false;
}

return true;

}

/**
* Subclassing children of this method will determine if to process the
* request.
*
* @return True if we are to continue with authentication, false otherwise.
*/
protected boolean doAuthentication()
{
return true;
}

/**
* When the userid and password is extracted from the request it will call
* this method and return the value returned by this method.
*
* @param userName
* The username extracted from the request.
* @param password
* The password extracted from the request.
* @return See method description on this method.
*/
protected boolean isValidAuthentication(String userName, String password)
{
return true;
}

/**
* {@inheritDoc}
*/
public Set getHeaders()
{
// TODO Auto-generated method stub
return null;
}

/**
* {@inheritDoc}
*/
public boolean handleFault(SOAPMessageContext arg0)
{
// TODO Auto-generated method stub
return false;
}
}
***************************************************

This method can be tested by SOAP UI by sending this request, try to change the pwd and you will be rejected.

*****************************************************





admin
adminpwd






********************************************************

Client files

Make a similar handler file, I don't use the same sice the client is separate from the server. Call it ClientHandler.xml

****************








com.aja.client.Handler




******************************************

Make the handler file, Handler.java.

*******************************************

package com.aja.client;

import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class Handler implements SOAPHandler
{

private static final String WSSE = "wsse";
private static final String WS_SEC = "Security";
private static final String USERNAME_TOKEN = "UsernameToken";
private static final String USERNAME = "Username";
private static final String PASSWORD = "Password";
private static final String NAME_SPACE_URI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

public SecurityHandler()
{
}

public Set getHeaders()
{
return null;
}

public boolean handleFault(SOAPMessageContext messageContext)
{
return true;
}

public boolean handleMessage(SOAPMessageContext messageContext)
{
secureClient(messageContext);
return true;
}

public void close(MessageContext messageContext)
{
}

private void secureClient(SOAPMessageContext messageContext)
{
try
{
SOAPMessage message = messageContext.getMessage();
SOAPPart part = message.getSOAPPart();
SOAPEnvelope envelope = part.getEnvelope();
SOAPHeader header = envelope.getHeader();

if (header == null)
{
header = envelope.addHeader();
}

SOAPFactory factory = SOAPFactory.newInstance();

Name security = factory.createName(WS_SEC, WSSE, NAME_SPACE_URI);
SOAPHeaderElement orderHeader = header.addHeaderElement(security);

Name userNameToken = factory.createName(USERNAME_TOKEN, WSSE,
NAME_SPACE_URI);
Name user = factory.createName(USERNAME, WSSE, NAME_SPACE_URI);
Name pwd = factory.createName(PASSWORD, WSSE, NAME_SPACE_URI);

SOAPElement elem = orderHeader.addChildElement(userNameToken);
SOAPElement userElement = elem.addChildElement(user);
userElement.addTextNode("admin");
SOAPElement pwdElement = elem.addChildElement(pwd);
pwdElement.addTextNode("adminpwd");
}
catch (Exception e)
{
System.out.println("Exec " + e.getMessage());

}

}
}

****************************************************************

And finally hook up to the file every time a web service request is called. I added this where I receive my web service client.

********************************************

@HandlerChain(file="ClientHandler.xml")

public class MyClient {

}

************************************************

In the example above the manager is an instance of my web service cleint part. This is autogenerated by maven with the wsgen and wsimport commands.

0 comments:

Post a Comment