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.
28 Comments
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.
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.
When I add the WSS4JOutHandler to the client I’m getting a NamespaceURI cannot be null expection. What Namespace is the expection referring to?
Are you following the example in the XFire distribution. Paste some code.
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());
}
What is the value of SERVICE_NAMESPACE and serviceURL?
SERVICE_NAMESPACE =
“http://oracle.autoreturn.com/vehiclelookupservice”
serviceUrl=
http://localhost:8080/oracle/services/VehicleLookupService
the service_namespace matches my WSDL
Post the relevant part of you WSDL.
?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/”>
Are you getting this error on the client side or the server side?
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.
Hmm. Post the stack trace that you are getting.
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
no clue?
deno, i just emailed you my working source code which is in production right now. good luck.
Hi,
I am having a problem compiling since @InHandler annotation import is missing. What do I use for the import?
The class is:
org.codehaus.xfire.annotations.InHandlers
You will need the xfire-java5 module. I’m using Maven, here’s the repository path:
http://repo1.maven.org/maven2/org/codehaus/xfire/xfire-java5/
Hi, I’ve got problem in finding classes: ValidateUserTokenHandler and PasswordHandler ; what should I import/install to get them?
ValidateUserTokenHandler and PasswordHandler are coming from the ws-security example distributed with XFire.
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?
hi.. i’m getting the same problem deno got…
can you please tell me what solution he found??
thanks a lot!
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.
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 . . .
Andoko, as I said in 23 he was using jars from different places. Make sure ALL your client side jars are from the XFire distribution.
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
Would there be any reason I couldn’t use this same approach if I’m using spring rather than services.xml?
Thanks,
Lou
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.
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?
One Trackback/Pingback
[...] because I want to illustrate a specific point. The XFire Java client is fairly easy to write and a previous blog entry covered a similar client. Both the Team and Mascot classes are POJO’s with one String [...]