Implementing WS-Security with JSR 181 Annotations using WSS4J in XFire

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.

Advertisements

30 thoughts on “Implementing WS-Security with JSR 181 Annotations using WSS4J in XFire

  1. deno

    great article!

    what binding style are you using? are you doing code or schema first development? are you using org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping or org.codehaus.xfire.spring.ServiceBean to expose your services?

    BTW – some of your code is cut off on the right side.

    Reply
  2. arsenalist

    I am following what the Logemann Blog is doing so yes I’m using Jsr181HandlerMapping. I didn’t want to reproduce that blog entry here. You should refer to it.

    I am utilizing the stubs generated by the XFire’s WsGen command based on the WSDL.

    Reply
  3. deno

    When I add the WSS4JOutHandler to the client I’m getting a NamespaceURI cannot be null expection. What Namespace is the expection referring to?

    Reply
  4. deno

    i am following the XFire distro example.

    Service serviceModel = new ObjectServiceFactory().create(VehicleLookupService.class,”VehicleLookupService”, SERVICE_NAMESPACE, null);
    VehicleLookupService service = (VehicleLookupService) new XFireProxyFactory().create(serviceModel,_serviceUrl);

    Client client = ((XFireProxy) Proxy.getInvocationHandler(service)).getClient();
    client.addOutHandler(new DOMOutHandler());
    Properties properties = new Properties();
    configureOutProperties(properties);
    //client.addOutHandler(new WSS4JOutHandler(properties)); // this is the line that breaks

    LookupVehiclesRequest lookupRequest = RequestBuilder.buildLookupVehiclesRequest(cmd);
    LookupVehiclesResponse r = service.getVehicles(lookupRequest);

    protected void configureOutProperties(Properties properties) {
    properties.setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
    properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
    properties.setProperty(WSHandlerConstants.USER, “root”);
    properties.setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, PasswordHandler.class.getName());
    }

    Reply
  5. deno

    ?xml version=”1.0″ encoding=”UTF-8″?>
    wsdl:definitions targetNamespace=”http://oracle.autoreturn.com/vehiclelookupservice” xmlns:tns=”http://oracle.autoreturn.com/vehiclelookupservice” xmlns:wsdlsoap=”http://schemas.xmlsoap.org/wsdl/soap/” xmlns:soap12=”http://www.w3.org/2003/05/soap-envelope” xmlns:ns1=”http://model.service.autoreturn.com” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:soapenc11=”http://schemas.xmlsoap.org/soap/encoding/” xmlns:soapenc12=”http://www.w3.org/2003/05/soap-encoding” xmlns:soap11=”http://schemas.xmlsoap.org/soap/envelope/” xmlns:wsdl=”http://schemas.xmlsoap.org/wsdl/”>

    Reply
  6. deno

    it’s this line

    client.addOutHandler(new WSS4JOutHandler(properties));

    that throws the expection. the server side seems to be all good as i can all consume my service with a php client along with a xfire client (as long as i don’t add the WSS4JOutHandler to the client.

    Reply
  7. deno

    yeah, it does seem like a weird problem. here’s the stack trace

    2007-02-04 12:45:21,947 ERROR [org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/oracle].[spring]] – Servlet.service() for servlet spring threw exception
    org.codehaus.xfire.XFireRuntimeException: Could not invoke service.. Nested exception is org.codehaus.xfire.fault.XFireFault: NamespaceURI cannot be null
    org.codehaus.xfire.fault.XFireFault: NamespaceURI cannot be null
    at org.codehaus.xfire.fault.XFireFault.createFault(XFireFault.java:89)
    at org.codehaus.xfire.util.dom.DOMSerializer.writeMessage(DOMSerializer.java:47)
    at org.codehaus.xfire.transport.http.HttpChannel.writeWithoutAttachments(HttpChannel.java:56)
    at org.codehaus.xfire.transport.http.CommonsHttpMessageSender.getByteArrayRequestEntity(CommonsHttpMessageSender.java:388)
    at org.codehaus.xfire.transport.http.CommonsHttpMessageSender.send(CommonsHttpMessageSender.java:326)
    at org.codehaus.xfire.transport.http.HttpChannel.sendViaClient(HttpChannel.java:123)
    at org.codehaus.xfire.transport.http.HttpChannel.send(HttpChannel.java:48)
    at org.codehaus.xfire.handler.OutMessageSender.invoke(OutMessageSender.java:26)
    at org.codehaus.xfire.handler.HandlerPipeline.invoke(HandlerPipeline.java:131)
    at org.codehaus.xfire.client.Invocation.invoke(Invocation.java:75)
    at org.codehaus.xfire.client.Client.invoke(Client.java:335)
    at org.codehaus.xfire.client.XFireProxy.handleRequest(XFireProxy.java:77)
    at org.codehaus.xfire.client.XFireProxy.invoke(XFireProxy.java:57)
    at $Proxy19.getVehicles(Unknown Source)
    at com.autoreturn.service.VehicleLookupServiceController.handle(VehicleLookupServiceController.java:73)
    at org.springframework.web.servlet.mvc.AbstractCommandController.handleRequestInternal(AbstractCommandController.java:82)
    at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:153)
    at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:45)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:806)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:736)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:396)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:350)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
    at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
    at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
    at org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
    at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
    at java.lang.Thread.run(Thread.java:595)
    Caused by: javax.xml.stream.XMLStreamException: NamespaceURI cannot be null
    at com.sun.xml.stream.writers.XMLStreamWriterImpl.writeAttribute(XMLStreamWriterImpl.java:324)
    at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:366)
    at org.codehaus.xfire.util.STAXUtils.writeNode(STAXUtils.java:391)
    at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:380)
    at org.codehaus.xfire.util.STAXUtils.writeNode(STAXUtils.java:391)
    at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:380)
    at org.codehaus.xfire.util.STAXUtils.writeNode(STAXUtils.java:391)
    at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:380)
    at org.codehaus.xfire.util.STAXUtils.writeNode(STAXUtils.java:391)
    at org.codehaus.xfire.util.STAXUtils.writeElement(STAXUtils.java:380)
    at org.codehaus.xfire.util.STAXUtils.writeDocument(STAXUtils.java:285)
    at org.codehaus.xfire.util.dom.DOMSerializer.writeMessage(DOMSerializer.java:40)
    … 36 more

    Reply
  8. Pingback: Writing PHP clients for XFire, Axis and .NET Web Services « The Arsenalist Muses

  9. Marc

    Hi,

    I am having a problem compiling since @InHandler annotation import is missing. What do I use for the import?

    Reply
  10. michael

    Hi, I’ve got problem in finding classes: ValidateUserTokenHandler and PasswordHandler ; what should I import/install to get them?

    Reply
  11. michael

    Thx, got it 🙂 Now I have problems with adding other kinds of WS-Security elements, i.e. timestamp. Just adding another in/out handler doesn’t work :/ (action mismatch…) Where can I get more feedback on it?

    Reply
  12. Alex

    hi.. i’m getting the same problem deno got…
    can you please tell me what solution he found??

    thanks a lot!

    Reply
  13. arsenalist

    He was using JARs from different places. Some from XFire, some for JAXB, etc etc. Make sure ALL the jars you use in your client are from the XFire distribution.

    Reply
  14. Andoko

    Hi, i experienced exactly the same problem with deno, when i add wss4jouthandler, it turned out that my client gave xfire exception that says namespace uri cannot be null. i ve been looking for workaround for this problem with no avail, thanks very much, hope someone can help . . .

    Reply
  15. Andoko

    Thanks for your fast response could you mail me your working client please? i use library other than xfire just for WSConstants, WSHandlerConstants, and WSS4JHandler from org.apache.ws.security package, my other import is from xfire package. here is my import list :

    import java.util.Properties;
    import org.apache.ws.security.WSConstants;
    import org.apache.ws.security.handler.WSHandlerConstants;
    import org.apache.ws.security.handler.WSS4JHandler;
    import org.codehaus.xfire.client.Client;
    import org.codehaus.xfire.security.wss4j.WSS4JOutHandler;
    import org.codehaus.xfire.service.binding.ObjectServiceFactory;
    import org.codehaus.xfire.util.dom.DOMOutHandler;
    import org.codehaus.xfire.service.Service;
    import org.codehaus.xfire.client.XFireProxyFactory;
    import org.codehaus.xfire.service.ServiceFactory;
    //non – ws import
    import os3.bussiness.beans.*;
    import os3.services.assembled.intf.*;
    import os3.services.assembled.impl.*;
    import java.util.List;
    import os3.services.assembled.data.ReturnValue;
    import java.net.MalformedURLException;

    are those package correct? i use wss4j-1.5.2.jar, xfire-all-1.2.6.jar.

    thanks in advance
    regards,
    andoko

    Reply
  16. Lou

    Would there be any reason I couldn’t use this same approach if I’m using spring rather than services.xml?

    Thanks,
    Lou

    Reply
  17. hugh

    re: NamespaceURI cannot be null

    This stumped me for a while, then I added a DebugHandler that does this:

    OutMessage msg = (OutMessage) messageContext.getCurrentMessage();
    Document doc = (Document) msg.getProperty("dom.message");

    XMLSerializer ser = new XMLSerializer();
    ser.setOutputFormat(new OutputFormat("xml", null, true));
    ser.setOutputCharStream(new PrintWriter(System.out));
    ser.serialize(doc);

    Which revealed that the WSS4JOutHandler was not putting the correct NS prefix on the Password Type attribute.

    I got around this by then adding a PatchWSS4JOutHandler which did the following on invocation:

    OutMessage msg = (OutMessage) messageContext.getCurrentMessage();
    Document doc = (Document) msg.getProperty("dom.message");

    Node typeNode = XPathAPI.selectSingleNode(doc, "//@Type");
    Attr typeAttr = (Attr) typeNode;
    Element passwordElement = typeAttr.getOwnerElement();
    Attr replacement = doc.createAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Type");
    replacement.setPrefix("wsse");
    replacement.setTextContent(typeAttr.getValue());
    passwordElement.getAttributes().removeNamedItem("Type");
    passwordElement.getAttributes().setNamedItemNS(replacement);

    Which is ugly but it works.

    Reply
  18. Henrik

    Hi. I’m getting the same exception as Deno above.
    When I implemented the solution which “hugh” presented above everything works nicely (thanks).
    However, when I try to encrypt my WebServices as described in the xfire-1.2.6 distribution I still get “NamespaceURI can not be null”.

    I have replaced http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
    with
    http://www.w3.org/2001/04/xmlenc#

    and

    replacement.setPrefix(“wsse”); with replacement.setPrefix(“xenc”);

    but still no success.

    Has anyone seen this before?

    Reply
  19. Martina

    Hey! Do you know if they make any plugins to protect against hackers?
    I’m kinda paranoid about losing everything I’ve worked hard on.

    Any suggestions?

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s