RSS Entries RSS
RSS Subscribe by Email

Apache CXF Tutorial – WS-Security with Spring

This tutorial will cover adding an authentication component to your web service though WS-Security. If you need an overview of how to setup CXF then you may find our previous tutorial helpful. Another helpful resource is CXF’s own WS-Security tutorial. However, it does not include information on how to setup the client through Spring.

To begin with, make sure you have at least the following .jars in addition to the required base CXF .jars:

spring-beans-2.0.6.jar
spring-context-2.0.6.jar
spring-core-2.0.6.jar
spring-web-2.0.6.jar
wss4j-1.5.1.jar
xmlsec-1.3.0.jar

Now we will add a security interceptor to the server’s Spring configuration file, which we named cxf.xml in the last tutorial in order to match the CXF documentation.

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://cxf.apache.org/jaxws

                          http://cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

  <jaxws:endpoint id="auth"
                  implementor="com.company.auth.service.AuthServiceImpl"
                  address="/corporateAuth">

    <jaxws:inInterceptors>
      <bean class="org.apache.cxf.binding.soap.saaj.SAAJInInterceptor" />
      <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
          <map>
            <entry key="action" value="UsernameToken" />
            <entry key="passwordType" value="PasswordText" />
            <entry key="passwordCallbackClass" value="com.company.auth.service.ServerPasswordCallback" />
          </map>
        </constructor-arg>
      </bean>
    </jaxws:inInterceptors>

  </jaxws:endpoint>

</beans>

You can change the action and passwordType to do more advanced authentication. In this example, we will simply require all authenticating clients to know a single password specified by the server. If you’d like each client to have it’s own password you can specify that in the callback, which is the next thing we must implement:

package com.company.auth.service;

import java.io.IOException;
import java.util.ResourceBundle;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class ServerPasswordCallback implements CallbackHandler {

    private static final String BUNDLE_LOCATION = "com.company.auth.authServer";
    private static final String PASSWORD_PROPERTY_NAME = "auth.manager.password";

    private static String password;
    static {
        final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_LOCATION);
        password = bundle.getString(PASSWORD_PROPERTY_NAME);
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        // Set the password on the callback. This will be compared to the
        //     password which was sent from the client.
        // We can call pc.getIdentifer() right here to check the username
        //     if we want each client to have it's own password.
        pc.setPassword(password);
    }

}

The server is now setup to require a password. The password we are requiring is one that we specified in a properties file and then read in through a ResourceBundle. You may find it easier to simply hard code the password on the initial run and then replace it with your own means of authentication once the service is up and running.

If you are running on WebLogic 9, as I was, then you will get an error “java.lang.UnsupportedOperationException: This class does not support SAAJ 1.1“. In order to correct that, make sure your version of the SAAJ classes are being used by adding the following to your weblogic.xml descriptor file:

<container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

You WebLogic folks must also then set two properties in your WebLogic JDK:

-Djavax.xml.soap.MessageFactory=com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl
-Djavax.xml.soap.SOAPConnectionFactory=weblogic.wsee.saaj.SOAPConnectionFactoryImpl

We now have to setup the client to supply a password. Firstly, we will create another Spring file at com/company/auth/service/cxfClient.xml to setup the application context for the client:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jaxws="http://cxf.apache.org/jaxws"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                      http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

  <bean id="proxyFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
    <property name="serviceClass" value="com.company.auth.service.AuthService"/>
    <property name="address" value="http://localhost:7001/authManager/services/corporateAuth"/>
    <property name="inInterceptors">
      <list>
        <ref bean="logIn" />
      </list>
    </property>
    <property name="outInterceptors">
      <list>
        <ref bean="logOut" />
        <ref bean="saajOut" />
        <ref bean="wss4jOut" />
      </list>
    </property>
  </bean>

  <bean id="client" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean" factory-bean="proxyFactory" factory-method="create" />

  <bean id="logIn" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
  <bean id="logOut" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
  <bean id="saajOut" class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor" />
  <bean id="wss4jOut" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
    <constructor-arg>
      <map>
        <entry key="action" value="UsernameToken" />
        <entry key="user" value="ws-client" />
        <entry key="passwordType" value="PasswordText" />
        <entry key="passwordCallbackClass" value="com.company.auth.service.ClientPasswordCallback" />
      </map>
    </constructor-arg>
  </bean>    

</beans>

We then need to set the password for our message:

package com.company.auth.service;

import java.io.IOException;
import java.util.ResourceBundle;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.ws.security.WSPasswordCallback;

public class ClientPasswordCallback implements CallbackHandler {

    private static final String BUNDLE_LOCATION = "com.company.auth.authClient";
    private static final String PASSWORD_PROPERTY_NAME = "auth.manager.password";	

    private static String password;
    static {
        final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_LOCATION);
        password = bundle.getString(PASSWORD_PROPERTY_NAME);
    }	

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        // set the password for our message.
        pc.setPassword(password);
    }

}

Finally, we create the service factory, which is extremely easy since all the work was done in the Spring file:

package com.company.auth.service;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class AuthServiceFactory {

    private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {
                "com/company/auth/service/cxfClient.xml"
            });

    public AuthServiceFactory() {
    }

    public AuthService getService() {
        return (AuthService) context.getBean("client");
    }
}

Congratulations. Your web service now utilizes a basic implementation of WS-Security. Hopefully, that will be enough background to get you on your way.

Share and Enjoy:
  • Add to favorites
  • HackerNews
  • DZone
  • Reddit
  • del.icio.us
  • StumbleUpon
  • Slashdot
  • Digg
  • Google Bookmarks
  • Facebook

Tags: , , , , , ,

40 Comments »

  1. Web Services Tutorial with Apache CXF | Lumidant said,

    February 19, 2008 at 5:23 pm

    [...] you are looking for something to learn next then may I suggest our tutorial on adding security to your web service.  That tutorial will also show you how to setup the client using Spring, which you may find [...]

  2. Irshad said,

    February 23, 2008 at 8:19 am

    Thanks,

    Can you help us using acegi with CXF with an example.

    –Irshad.

  3. Ben said,

    February 25, 2008 at 4:10 pm

    Hi Irshad,
    I would love to, but unfortunately I haven’t gotten a chance to use Acegi yet. Maybe in the near future.

    -Ben

  4. Ryan said,

    March 4, 2008 at 8:54 pm

    Do we need to implement this class: com.company.auth.service.ClientPasswordCallback or is it generated by CXF or Spring somehow? Also, did you re-use the client class from the first example (with a modified target address)?

    My attempt at this example is close but the WS-Security part isn’t working from the client side. For what it’s worth, here is my error information as my implementation stands:

    INFO: Inbound Message
    —————————-
    Encoding: UTF-8
    Headers: {content-type=[text/xml;charset=UTF-8], connection=[close], Date=[Wed, 05 Mar 2008 00:59:24 GMT], Content-Length=[245],
    Server=[Apache-Coyote/1.1]}
    Messages:
    Message:

    Payload: soap:ClientRequest does not contain required Security header.
    ————————————–
    Exception in thread “main” javax.xml.ws.soap.SOAPFaultException: Request does not contain required Security header.
    at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:175)
    at $Proxy23.getEmployee(Unknown Source)
    at com.company.auth.client.Client.main(Client.java:25)
    Caused by: org.apache.cxf.binding.soap.SoapFault: Request does not contain required Security header.
    at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:70)
    at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:35)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:208)
    at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:96)

  5. Ben said,

    March 10, 2008 at 12:34 am

    Ah, yes. You are quite right, Ryan. I’m sorry for leaving that out of the tutorial the first go around. It’s been updated now.
    I did not re-use the client class from the first example, but instead used the cxfClient.xml file to setup the client through Spring in this example.

  6. patrick van amstel said,

    April 16, 2008 at 1:32 am

    Lovely tutorial.

    Can you add a zip file with all classes and configuration included? That would be very helpful.

    grt Patrick

  7. Glen said,

    May 7, 2008 at 9:45 am

    Ben, I’m confused on something–where did you tie the AuthServiceFactory class you created at the bottom of your tutorial to the creation of client proxies? I.e., how does Spring know to go to that class in particular to generate the SOAP client? The proxyConfig bean in the app context config file (below) is not explicitly referencing AuthServiceFactory, so I don’t know how Spring realizes it needs to call AuthServiceFactory to generate the client proxies.

    ….

    Thanks,
    Glen

  8. Ben said,

    May 7, 2008 at 12:36 pm

    Hi Glen,
    I am not instantiating the AuthServiceFactory through Spring in this example, but rather am using it only to instantiate the AuthService itself. For example, the following should work:
    final AuthServiceFactory f = new AuthServiceFactory():
    final AuthService service = f.getService();

  9. Tony Murphy said,

    May 14, 2008 at 3:52 am

    Hi,

    I’ve being looking at spring and cxf and then noticed this presentation on parleys, interesting. suggests that restful webservices are the way to go

    http://www.parleys.com/display/PARLEYS/REST%20-%20The%20Better%20Web%20Services%20Model

  10. Ckarthi said,

    May 14, 2008 at 10:52 pm

    Really very good tutorial. [The CXF overview tutorial was too good that using Tomcat i was able to try it out in just 5 minutes.]

  11. Reut said,

    June 25, 2008 at 8:11 am

    It seems to me that the code for the ServerPasswordCallback is displayed twice, and the code where the client sets the password before caliing the srvice doesn’t appear at all.
    am i correct?

  12. Ben said,

    July 1, 2008 at 1:42 pm

    Hi Reut,
    I only see the ServerPasswordCallback displayed once in the article. ClientPasswordCallback is also displayed, which is where the client sets the password.

    -Ben

  13. Wes said,

    July 7, 2008 at 1:07 pm

    Hello Reut,
    I have a question for you… I’m running CXF with WSS4J (currently in Jetty6 for testing). I can create the service and validate the password text with username token profile successfully using SOAPUI. However, when I create the client (I’m using Struts2 also tested from a jetty container) I get the following error…

    javax.xml.soap.SOAPException: Unable to create message factory for SOAP: Provider org.apache.axis.soap.MessageFactoryImpl not found

    The strange part is that if I call the service directly with a test case (even through an action test case) everything works. It’s only when I actually call it inside the jetty container that it fails. Notice… I’m not using axis at all either. That has me confused. Do you have any ideas?
    W

  14. Ben said,

    July 7, 2008 at 3:46 pm

    Hi Wes,
    Your problem sounds like a classpath issue. I’m not familiar with Jetty, but I would suggest looking into whether there is some way that you can print out the classpath and make sure that all the .jars that you need are present.

    -Ben

  15. Wes said,

    July 8, 2008 at 11:17 pm

    Hi Ben,
    The same thing drove me crazy. I’m using maven and with its transitive dependency management I wasn’t sure if I really was getting a jar from axis in my classpath (even tho I’m using cxf). I ultimately did figure it out thanks to another guy on our team (thx JR). It goes back to the same “WebLogic” issue above…
    -Djavax.xml.soap.MessageFactory=com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl
    -Djavax.xml.soap.SOAPConnectionFactory=weblogic.wsee.saaj.SOAPConnectionFactoryImpl

    I had to define the com.sun factory for my implementation. Otherwise, it apparently defaults to axis.

    As for jetty, it’s create with the maven plugin. Just build and type mvn jetty6:run and you have a container to test your web app in. Very, very nice. Glassfish v3 will be able to do the same.

    Thank you for responding. I spend hours chasing this issue.
    Wes

  16. Tom said,

    July 10, 2008 at 7:56 am

    Hi Ben and thx for this tutorial.

    For me the web service works and returns the HelloWorld string but doesn’t care of WS Security. I fixed different users on server and client without visible effect. And println insert in both PasswordCallback are not displayed.
    That’s strange because it works fine with CXF native frontend, but I cannot find what’s wrong with Spring and WAS.

    Did you encounter such problem?

  17. Tom said,

    July 10, 2008 at 8:14 am

    Forget my previous post, I made a noob mistake in my xml client file. :p

  18. Tom said,

    July 10, 2008 at 9:32 am

    Re,

    This time I’m really stucked… My WS works fine with Spring and WAS 6.1, but when I try to add WS-Security authentication I get this error:

    org.w3c.dom.DOMException: HIERARCHY_REQUEST_ERR: An attempt was made to insert a node where it is not permitted.
    at org.apache.xerces.dom.CoreDocumentImpl.insertBefore(Unknown Source)
    at org.apache.xerces.dom.NodeImpl.appendChild(Unknown Source)
    at com.ibm.ws.webservices.engine.xmlsoap.SOAPPart.appendChild(SOAPPart.java:244)
    at org.apache.cxf.staxutils.W3CDOMStreamWriter.newChild(W3CDOMStreamWriter.java:81)
    at org.apache.cxf.staxutils.W3CDOMStreamWriter.writeStartElement(W3CDOMStreamWriter.java:98)

    I dont understand in that WSS works with CXF native frontend. May it be a conflict between CXF and Websphere libraries?

  19. Ben said,

    July 10, 2008 at 1:44 pm

    Hey Tom,
    Check out this link for some WS-specific solutions: http://cwiki.apache.org/CXF20DOC/appserverguide.html#AppServerGuide-Websphere

    -Ben

  20. Mike Lou said,

    July 31, 2008 at 1:56 pm

    Hi Ben,

    This is a great tutorial. I have a question on server/client config. If I want to config multiple endpoints in the same config files, is there any way to secure some endpoints and leave the others public (not secure)?

    -mike

  21. Ben said,

    August 4, 2008 at 2:29 pm

    Hi Mike,
    The interceptors are setup on a per endpoint basis, so you shouldn’t have any trouble doing that. Simply copy everything between the jaxws:endpoint tags to create a new endpoint and leave out the wss4j interceptor for the endpoints you don’t want to secure.

    -Ben

  22. Av said,

    August 21, 2008 at 3:13 pm

    Nice tutorial.
    Do you know if there is a preferred method of authorizing a web service using cxf? I mean, after authentication, one needs to ensure that the specified user is actually authorized to execute the web service method in question. Does cxf offer any support for this, do you know?

    Thanks.

  23. Blair said,

    August 29, 2008 at 10:29 am

    Hello,

    This is a great tutorial. Does anyone know if a tutorial like this one exists for a JAXRS (RESTful) service. In my service I pass simple XML back and forth. I try using what is shown here in this tutorial and get a bunch of SOAP errors.

    Thanks.

  24. Manisha said,

    September 15, 2008 at 1:14 pm

    It’s really a great tutorial, I could make it work on my local. Here I am trying to understand the client part, which uses org.apache.ws.security.WSPasswordCallback in ClientPasswordCallback() class. Would really appreciate if you could let me know one thing, if my client is .Net – what changes shall I make ?

  25. Dave said,

    November 26, 2008 at 5:04 pm

    This is an excellent article! It’s what I’ve been looking for for hours. I’ve followed everything but don’t see how to actually make use of the protected service… Am I supposed to be visiting the address for the protected service (/corporateAuth) or something else?

    I’m somewhat familiar with CXF, but am really having trouble understanding how to implement WS-Security. Thank you for your time!

  26. Ben said,

    November 26, 2008 at 5:15 pm

    Hey Dave,
    I agree CXF is really tricky. I think it took me a week or two to get a sample app up and running.
    You should read the other CXF tutorial on this site first. It will show you how to create a client to access your service. Definitely do that and create an unprotected service first. Then come back to this writeup and add in the security portions.

    -Ben

  27. Dave said,

    November 27, 2008 at 11:39 am

    Hi Ben,

    Thank you for your help! Following your examples definitely cleared up a lot of my questions.

  28. Dipesh said,

    December 3, 2008 at 10:17 pm

    Very good security tutorial, I was able to set it up quickly and test both swAuth and corporateAuth webservice endpoints.

    One thing which I did not realize and got confused is when ‘passwordType’ is ‘PasswordText’, responsibility to validate password lies with the CallbackHandler.

    I was trying to send wrong password from client and expecting the call to be rejected by WebService. But it was not happening.

    After some google found information on CXF wiki page

    http://cwiki.apache.org/CXF20DOC/ws-security.html

    This note is present under ‘Username Token Authentication’ section, “Note that for the special case of a plain-text password (or any other yet unknown password type), the password validation is delegated to the callback class”.

    After reading this I modified ServerPasswordCallback.handle () method, so that the code looks something like:

        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    
            WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
            // Set the password on the callback. This will be compared to the
            //     password which was sent from the client.
            // We can call pc.getIdentifer() right here to check the username
            //     if we want each client to have it's own password.
    
            if (!pc.getPassword().equals(password)) {
    			throw new SecurityException ("Password is invalid");
    		}
        }

    After this modification in ServerPasswordCallback, service started responding in correct way. Failed for wrong password and get valid response with correct password.

  29. DeKapx said,

    April 26, 2009 at 10:24 pm

    Hi Ben,

    This is really a very good tutorial. Sometimes back I explore WS-Security using Metro – WSIT. There ‘re multiple types of WS-Security can be created using Metro for example:
    1. Message Security using Mutual Certificate
    2. Message Security using Symmetric Keys.

    and few more. Is there any possibility to create such types of WS-Security using Apache CXF. Any information on this will be a great help.

    Thanks in advance.
    DeKapx

  30. DeKapx said,

    April 26, 2009 at 10:38 pm

    Hi Ben,

    In addition to my previous comment, there is one scenario in our application. We have Metro based WS-Security enabled web service and its implementation is based on WS-Security using Symmetric Keys. I am trying to hit the service using Apache CXF client but its not working. Is there any compatibility issues with these technologies or is there any way out for this.

    DeKapx

  31. Manoj said,

    September 22, 2009 at 1:38 am

    Hi Ben,

    Can you tell me where from this “cxf-extension-soap.xml” and ‘cxf-servlet.xml” have come as you have not mentioned any where about these files.

    What should i write in these files?

    Manoj

  32. Ben said,

    September 22, 2009 at 10:46 am

    Hi Manoj,
    cxf-extension-soap.xml and cxf-servlet.xml come from the jar files. You do not need to create them yourself

  33. david said,

    October 31, 2009 at 6:44 pm

    Hi Ben,
    Is it possible to have the completed project in a zip file. I could not run the client with my limited spring background.
    Also when I use the web services explorer, no matter what password I provide, I get the service executed.

  34. Ben said,

    November 2, 2009 at 9:36 am

    Hi David, I’m sorry, but I don’t have the code for this tutorial still.

  35. jerome bulanadi said,

    November 12, 2009 at 2:30 pm

    Hi Ben,

    Thanks a lot for the previous tutorial, and this security tutorial. It got me up and running CXF on Tomcat in no time.

    Hi Dipesh,

    Thanks too, for your handling of wrong client password. It saved me a lot of time searching over CXF documentation.

    You dudes rock!

  36. Stalin Mohapatra said,

    February 4, 2010 at 11:47 pm

    Hi Ben…

    This tutorial was of immense help. I did learn a few new things. Keep up the good work and help beginners like us.

  37. Bart Ottenkamp said,

    February 9, 2010 at 4:30 am

    Hi Ben..

    great writing and I got almost everything working…
    One thing though: if I set uo de thes ecurity like you explain, I get while calling the service:

    com.company.auth.service.ServerPasswordCallback cannot be cast to java.lang.String

    And I don’t see what’s happening…
    Do you have a clue?

  38. dev said,

    July 25, 2010 at 6:57 pm

    Hi ,
    I try above example to implement the web service security but i am getting this exception

    —————————-
    ID: 1
    Response-Code: 500
    Encoding: UTF-8
    Content-Type: text/xml;charset=UTF-8
    Headers: {Content-Length=[361], connection=[close], Date=[Mon, 26 Jul 2010 01:54:38 GMT], Server=[Apache-Coyote/1.1], content-type=[text/xml;charset=UTF-8]}
    Payload: ns1:InvalidSecurityAn error was discovered processing the <wsse:Security> header
    ————————————–
    Exception in thread “main” javax.xml.ws.soap.SOAPFaultException: An error was discovered processing the header

    can any one help me if , if any one having zip file for running source code ,please provide me

    Thanks..

  39. louis gueye said,

    August 17, 2010 at 1:08 am

    Thanks a lot this saved me much time.
    The only regret I have is that WSS4*Interceptor interceptors don’t reference an instance but a class name so I could not inject any Spring managed bean to make some more elaborate password checks.
    I’ll keep looking for an interceptor that will suit my needs … or create one :) .

    Anyway thanks again very much.

  40. Viki said,

    August 31, 2010 at 7:16 am

    Hi ..
    I’ve been struggling with this for a while now .. Is there a new client class that we would have to use …
    Could someone please post that ..

    Thanks

RSS feed for comments on this post · TrackBack URL

Leave a Comment