The Logemann Blog really helped me out when I was trying to use JSR181 Annotations in favor of the services.xml configuration in XFire. Reading that blog entry is most likely a prerequisite for this one. Once you’ve configured your Spring beans and web.xml to use JSR181 Annotations, the next step is to implement security for your web services. The example shown implements WS-Security as specified by OASIS Web Services Security and implemented by WSS4J in XFire .
Looking at the ws-security example packaged with XFire, for a service to be security enabled, you need the following configuration in your services.xml. We are dealing with the “User Token with Hashed Password” scheme.
<service> <name>BookServiceUTHP</name> <namespace> http://xfire.codehaus.org/BookService </namespace> <serviceClass> org.codehaus.xfire.demo.BookService </serviceClass> <inHandlers> <handler handlerClass= "org.codehaus.xfire.util.dom.DOMInHandler" /> <bean class= "org.codehaus.xfire.security.wss4j.WSS4JInHandler"> <property name="properties"> <props> <prop key="action">UsernameToken</prop> <prop key="passwordCallbackClass"> org.codehaus.xfire.demo.PasswordHandler </prop> </props> </property> </bean> <handler handlerClass= "org.codehaus.xfire.demo.ValidateUserTokenHandler"/> </inHandlers> </service>
However, we want to substitute this configuration with annotations. There are three InHandlers being applied which we must specify via annotations. The problem is that when specifying handlers using annotations you may not supply any parameters to them, as we do above in the case of WSS4JInHandler. The solution is to create a custom handler which wraps the other handlers and passes in the parameters to WSS4JInHandler programmatically. Here’s a custom handler which does just that:
public class WSSecurityHandler extends AbstractHandler { List<Handler> inHandlers; public WSSecurityHandler() { WSS4JInHandler wss4jInHandler; ValidateUserTokenHandler userTokenHandler; Properties props = new Properties(); props.put("action", "UsernameToken"); props.put("passwordCallbackClass", PasswordHandler.class.getName()); wss4jInHandler = new WSS4JInHandler(props); userTokenHandler = new ValidateUserTokenHandler(); inHandlers = new ArrayList<Handler>(); inHandlers.add(wss4jInHandler); inHandlers.add(userTokenHandler); } public QName[] getUnderstoodHeaders() { return new QName[] { new QName("http://docs.oasis-open.org/wss/2004/01/" + "oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security") }; } public void invoke(MessageContext messageContext) throws Exception { for (Handler handler : inHandlers) { handler.invoke(messageContext); } // User should be set by ValidateUserTokenHandler // You can also choose to do all your security checks in // ValidateUserTokenHandler which does a lot of the work for you if (messageContext.getProperty( WSHandlerConstants.ENCRYPTION_USER) == null) { throw new Exception("Principal not found"); } } }
The implementation is fairly straightforward. The one thing to note is the getUnderstoodHeaders() method which returns a QName. The reason this is important is that Java clients send this as part of the SOAP header and it is mandatory for the web service to recognize this as a valid header. I encountered this problem and reported it on the mailing list with no response.
The last piece of the puzzle is that we must annotate our web service class with our custom handler. Here’s an example:
@WebService @InHandlers(handlers={ "my.package.WSSecurityHandler", "org.codehaus.xfire.util.dom.DOMInHandler" }) public class BookService { // methods go here }
The DOMInHandler must be configured via an annotation. The client code for calling a web service configured using services.xml is the same as when configured via annotations:
Service serviceModel = new ObjectServiceFactory(). create(IBook.class, "BookService", SERVICE_NAMESPACE, null); IBook service = (IBook) new XFireProxyFactory(). create(serviceModel, SERVICE_URL); Client client = ((XFireProxy) Proxy.getInvocationHandler(service)).getClient(); client.addOutHandler(new DOMOutHandler()); Properties p = new Properties(); // Action to perform : user token p.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN); // Set password type to hashed p.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST); // Username in keystore p.setProperty(WSHandlerConstants.USER, "serveralias"); // Used do retrive password for given user name p.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, PasswordHandler.class.getName()); client.addOutHandler(new WSS4JOutHandler(p)); Book b = service.findBook("0123456789");
That’s all it takes.