CXF WS-Security using JSR 181 + Interceptor Annotations (XFire Migration)

I had blogged about how to setup XFire with WS-Security a while ago and since then the XFire 1.x series as we know it is dead, instead Apache’s CXF can be considered XFire 2.0. CXF improves over XFire in many areas including better architecture and more importantly easier message handling and manipulation. In this entry, we’ll setup a CXF application that secures its services using CXF’s WS-Security features.

Before I get to the example I want to mention some of the major changes that I noticed in CXF:

  • Interceptors instead of handlers: Handlers are out and are replaced with a much more common concept of interceptors (if you’re from the Struts2/Webwork world you know what they are). Interceptors are created by extending the AbstractPhaseInterceptor class which forces you to implement handleMessage(Message). Note that you must specify the phase where you want each interceptor executed.
  • Access to MessageContext: In XFire, the MessageContext was always available in your handlers. In CXF you don’t have access to it in the interceptor but you can get contextual properties using the message.getContextualProperty(String) methods. Access to the MessageContext is also available using the @Resource annotation as described here but this only works in service implementations.
  • JAXWS Endpoints: Spring service classes must be exposed using the <jaxws:endpoint> tag. You can also do this programmatically but why would you.
  • Message and Exchange: The Message and Exchange objects have changed dramatically from XFire. CXF’s Message and Exchange objects are devoid of all those helpful methods that were present in XFire but they make up for it by having get(Class) and get(String) methods which can be used to retrieve valuable information. It’s probably a good idea to look through Exchange.keySet() and Message.keySet() and see whats available to you in your interceptors. For example when a Fault occurs, the message.get(Exception.class) returns the exception that was thrown to cause the fault. Also note that the information present in these maps varies depending on what Phase you’re in.

Now to the WS-Security example. CXF just added support for configuring interceptors using annotations which means configuring WS-Security for our web services just got easier.

Here’s the service interface and implementation.

@WebService
public interface SportsService {
    public String getTeam();
        return "Arsenal";
    }
}

@WebService(
    serviceName="SportsService",
    endpointInterface="ca.utoronto.sis.cxfapp.SportsService")
@InInterceptors(interceptors={
        "com.arsenalist.cxfapp.WSSecurityInterceptor"
})
public class SportsServiceImpl implements SportsService {

    public String getTeam() {
        return "Arsenal";
    }
}

I’ve added a single in interceptor called WSSecurityInterceptor which is a class that we’ll write. In XFire we also needed a DomInHandler and a DomOutHandler for each service implementation, none of that is required here. WSSecurityInterceptor will just wrap the WSS4JInInterceptor class, the reason we can’t just specify WSS4JInInterceptor as an annotation is because we need to set custom properties on it such as using UsernameToken authentication.

The other thing WSSecurityInterceptor does is add a ValidateUserTokenInterceptor which is similar to ValidateUserTokenHandler in the XFire examples. Since WSS4J validates a UsernameToken only if it finds a security header we need to cover the case where no security header is specified. ValidateUserTokenInterceptor just makes sure the username, password, nonce and timestamp are specified before even considering it a valid request. Here’s the WSSecurityInterceptor and the PasswordHandler class:

public class WSSecurityInterceptor extends AbstractPhaseInterceptor {

    public WSSecurityInterceptor() {
        super(Phase.PRE_PROTOCOL);
    }
    public WSSecurityInterceptor(String s) {
        super(Phase.PRE_PROTOCOL);
    }

    public void handleMessage(SoapMessage message) throws Fault {

        Map props = new HashMap();
        props.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        props.put(WSHandlerConstants.PW_CALLBACK_REF, new PasswordHandler());

        WSS4JInInterceptor wss4jInHandler = new WSS4JInInterceptor(props);
        ValidateUserTokenInterceptor userTokenInterceptor = new ValidateUserTokenInterceptor(Phase.POST_PROTOCOL);

        message.getInterceptorChain().add(wss4jInHandler);
        message.getInterceptorChain().add(new SAAJInInterceptor());
        message.getInterceptorChain().add(userTokenInterceptor);
    }
}

public class PasswordHandler implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
        if (pc.getIdentifer().equals("arsenal")) {
            pc.setPassword("gunners");
        }
    }
}

Note that the ValidateUserTokenInterceptor is invoked in a later phase than WSS4JInterceptor. Here’s the ValidateUsertokenInterceptor class:

public class ValidateUserTokenInterceptor extends AbstractPhaseInterceptor {

    public ValidateUserTokenInterceptor(String s) {
        super(s);
    }

    public void handleMessage(SoapMessage message) throws Fault {
        boolean userTokenValidated = false;
        Vector result = (Vector) message.getContextualProperty(WSHandlerConstants.RECV_RESULTS);
        for (int i = 0; i &lt; result.size(); i++) {
            WSHandlerResult res = (WSHandlerResult) result.get(i);
            for (int j = 0; j &lt; res.getResults().size(); j++) {
                   WSSecurityEngineResult secRes = (WSSecurityEngineResult) res.getResults().get(j);
                    WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) secRes
                            .getPrincipal();

                    if (!principal.isPasswordDigest() ||
                            principal.getNonce() == null ||
                            principal.getPassword() == null ||
                            principal.getCreatedTime() == null) {
                        throw new RuntimeException("Invalid Security Header");
                    } else {
                        userTokenValidated = true;
                    }
                }
            }
        }
        if (!userTokenValidated) {
            throw new RuntimeException("Security processing failed");
        }
    }
}

Now we have a service implementation annotated in a way where it is WS-Security enabled and is also registered as a web service. The final step remaining is to expose it as a consumable endpoint. That can be achieved by either of the following ways:

Using the fully qualified class name:

<jaxws:endpoint
   id="helloWorld"
   implementor="com.arsenalist.cfxapp.SportsServiceImpl"
   address="/SportsService" />

Or by referring to a Spring bean corresponding to the @WebService annotated class. This would be more prudent if you’re using Dependency Injection in your service implementations.

<bean id="sportsServiceImpl" class="com.arsenalist.cfxapp.SportsServiceImpl"/>

<jaxws:endpoint
   id="sportsService"
   implementor="#sportsServiceImpl"
   address="/SportsService" />

Finally you could also just use good ‘ol fashioned Spring beans. This comes in handy if you wan to specify a different binding like Aegis:

<bean id="aegisBean" class="org.apache.cxf.aegis.databinding.AegisDatabinding"/>
<bean class="org.apache.cxf.frontend.ServerFactoryBean" init-method="create">
	<property name="serviceBean" ref="registrationSoapService"/>
	<property name="address" value="/services/1_0_0/Registration"/>
	<property name="dataBinding" ref="aegisBean"/>
</bean>

The CXF documentation which shows how to configure the web.xml is pretty straightforward.

If you’re using Maven, the dependencies section of the pom.xml might look something like this. Remember that support for configuring interceptors via annotations was just added so you have to access the SNAPSHOT repositories instead of the main one.

<repositories>
    <repository>
        <id>apache-snapshots</id>
        <name>Apache SNAPSHOT Repository</name>
        <url>http://people.apache.org/repo/m2-snapshot-repository/</url>
        <snapshots>
        <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>apache-incubating</id>
        <name>Apache Incubating Repository</name>
        <url>http://people.apache.org/repo/m2-incubating-repository/</url>
    </repository>
</repositories>
<dependencies>
    ...
    <!-- spring beans, core, context, web 2.0.6 -->
    ...
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>2.1-incubator-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-core</artifactId>
        <version>2.1-incubator-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>2.1-incubator-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-ws-security</artifactId>
        <version>2.1-incubator-SNAPSHOT</version>
    </dependency>
    ...
<dependencies>

Thanks for reading.

31 thoughts on “CXF WS-Security using JSR 181 + Interceptor Annotations (XFire Migration)

  1. Durga

    Could you please include client side configuration of WSS4J handlers as well. Also can we use spring dependency injection to populate Hashmap properties? Please include spring configuration sample. Thanks.

    Reply
  2. Pingback: Apache CXF Artikel

  3. Kevin

    Great, thanks!
    There are typos in your example, class ValidateUserTokenInterceptor.
    for (int j = 0; j 0) // j< ?
    secRes.getPrincipal(); // what is secRes ?

    Thanks again.

    Reply
  4. girish

    > hi,
    > i am using cxf 2.1 and generate wsdl using jax-ws 2.0 as code first
    > aproach.
    > here my SEI throws a custom exception say MyException but when i
    generate
    > the stubs from wsdl .the implementation class throws
    > MyExceptionException_Exception so it gives me an error.
    > also this MyExceptionException_Exception and MyExceptionExceptionBean
    > class generated in the jaxws.
    > it is one error.
    >
    > when i call service from jsp i could not catch my exception and gives
    me
    > runtime error.
    >
    > but in server side it come with the proper error .
    > how can i display the error massage in jsp page.
    >
    > thanks and regards,
    > girish

    Reply
  5. Shawn

    For those who are interested in the code on the client side to make this example work, here are some of the details. Hope it would also work in your test environment and CXF settings.

    1) Run Apache CXF wsdl2java command to generate the client side stub;

    2) Code the WS Client as below:
    package test.security.client;

    import java.util.HashMap;
    import java.util.Map;

    import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
    import org.apache.cxf.endpoint.Client;
    import org.apache.cxf.endpoint.Endpoint;
    import org.apache.cxf.frontend.ClientProxy;
    import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
    import org.apache.ws.security.WSConstants;
    import org.apache.ws.security.handler.WSHandlerConstants;

    public class WsClient {

    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    SportsService_Service service = new SportsService_Service();
    SportsService port = service.getPort(SportsService.class);

    Client client = ClientProxy.getClient(port);
    Endpoint cxfEndpoint = client.getEndpoint();

    Map outProps = new HashMap();

    WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
    cxfEndpoint.getOutInterceptors().add(wssOut);
    cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());

    outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
    // Specify our username
    outProps.put(WSHandlerConstants.USER, “ws-client”);
    // Password type : plain text
    //outProps.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);

    // for hashed password use:
    //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
    outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);

    // Callback used to retrive password for given user.
    outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordHandler.class.getName());

    String result = port.getTeam();
    System.out.println(“Result1: ” + result);

    result = port.getTeam();
    System.out.println(“Result2: ” + result);

    result = port.getTeam();
    System.out.println(“Result3: ” + result);

    }

    }

    3) Code the client side ClientPasswordHandler as below:
    package test.security.client;

    import java.io.IOException;

    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 ClientPasswordHandler implements CallbackHandler {

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

    WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

    System.out.println(“Client Password for User: ” + pc.getIdentifer());

    // set the password for the outbound message.
    pc.setPassword(“password”);
    }

    }

    That is all I would need in order to see the SOAP message sent out from the client side as shown below:

    ws-clientyLOK86TGqE5NYmGx2vkpipfFwH4=nqDwJUBIicVkZA7gw1GASg==2007-10-04T16:49:34.375Z

    Reply
  6. Shawn

    import java.util.HashMap;
    import java.util.Map;

    import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
    import org.apache.cxf.endpoint.Client;
    import org.apache.cxf.endpoint.Endpoint;
    import org.apache.cxf.frontend.ClientProxy;
    import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
    import org.apache.ws.security.WSConstants;
    import org.apache.ws.security.handler.WSHandlerConstants;

    public class WsClient {

    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    SportsService_Service service = new SportsService_Service();
    SportsService port = service.getPort(SportsService.class);

    Client client = ClientProxy.getClient(port);
    Endpoint cxfEndpoint = client.getEndpoint();

    Map outProps = new HashMap();

    WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
    cxfEndpoint.getOutInterceptors().add(wssOut);
    cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());

    //outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
    outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN + ” ” + WSHandlerConstants.TIMESTAMP);
    // How long ( in seconds ) message is valid since send
    //outProps.put(WSHandlerConstants.TTL_TIMESTAMP, “100”);
    // if you want to use millisecond precision use this
    //outProps.put(WSHandlerConstants.TIMESTAMP_PRECISION, “true”);

    // Specify our username
    outProps.put(WSHandlerConstants.USER, “ws-client”);
    // Password type : plain text
    //outProps.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);

    // for hashed password use:
    //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
    //outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
    outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);

    // Callback used to retrive password for given user.
    outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordHandler.class.getName());

    String result = port.getTeam();
    System.out.println(“Result1: ” + result);

    result = port.getTeam();
    System.out.println(“Result2: ” + result);

    result = port.getTeam();
    System.out.println(“Result3: ” + result);

    }

    }

    Reply
  7. Feng

    Shawn:
    I have trouble compile
    SportsService_Service service = new SportsService_Service();
    May I ask for the wsdl and the command that you use to generate the SportsService_Service class ?
    Thanks
    Feng

    Reply
  8. Matt Madhavan

    Hi,
    I am doing java first development. And I obtain my client proxy from the client-beans.xml file.

    How do I code my client now when I obtain my client proxy from using spring?

    Thanks
    Matt

    Reply
  9. yulinxp

    You mention:
    Since WSS4J validates a UsernameToken only if it finds a security header we need to cover the case where no security header is specified.

    In the following snippet from WSS4JInInterceptor.java
    public void handleMessage(SoapMessage msg) throws Fault

    if (wsResult == null) { // no security header found
    if (doAction == WSConstants.NO_SECURITY) {
    return;
    } else {
    LOG.warning(“Request does not contain required Security header”);
    throw new SoapFault(new Message(“NO_SECURITY”, LOG), version.getSender());
    }
    }

    If no security header found, a SoapFault is thrown. In my test, I set WS Security in server but not in client. And my client side does receive that SoapFault. So do we still need ValidateUserTokenInterceptor?

    Reply
  10. Arsenalist

    I see what you’re saying and maybe they’ve changed the implementation on the server side however ValidateUserTokenInterceptor was present in the XFire samples and its use was recommended by the committers. I think you also need to look at the source code for the WSS4J class UsernameTokenProcessor to learn more.

    Have you tried the case where you specify an invalid username/password or a valid username but an invalid password? Do you still receive the SoapFault?

    Reply
  11. yulinxp

    It will handle PasswordDigest automatically.
    For PasswordText, I have to do the comparison and throw exception.

    public class ServerPasswordCallback implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

    WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

    String strClientPwd = pc.getPassword();
    int usage = pc.getUsage();

    if(pc.getIdentifer().equals(“joe”)) {

    String strServerPwd = “password”;

    if(usage == WSPasswordCallback.USERNAME_TOKEN) { //PasswordDigest
    pc.setPassword(strServerPwd);

    }else if(usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) { //PasswordText
    pc.setPassword(strServerPwd);

    if(!strClientPwd.equalsIgnoreCase(strServerPwd)){ //DIY compare
    throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
    }
    }
    }
    }
    }

    Reply
  12. Sai C

    I am calling a .Net web service in Java (client)and used the JAXWsProxyFactoryBean by enabling the WSAddressFeature, however, my soap header does not contain a though it prints an element. Could somebody let me know how do I get wsa:action in soap header using CXF API.

    Appreciate your help.

    Regards,
    Sai

    Reply
  13. Bhoga Pappu

    I have implmented ClientPasswordCallback, ServerPasswordCallback and accordingly attached them with client side code and server side code.
    However, when I run the client, it fails with the error ‘Request does not contain Security Header’
    I don’t know what I am doing wrong.

    Reply
  14. morg r

    This is an excellent example. Thank you! But in trying this out, I’m wondering what the point of the PasswordHandler is. I integrated this example with Spring Security, and got it work but only while passing PasswordHandler to WSS4JInInterceptor. Even stranger, I couldn’t create an empty handle() method. So my PasswordHandler now looks like this:

    public class PasswordHandler implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
    pc.setPassword(pc.getPassword());
    }
    }

    Do you know why this is even needed? It’s not doing anything useful. And all calls fail when I leave out this line from WSSecurityInterceptor:
    props.put(WSHandlerConstants.PW_CALLBACK_REF, new PasswordHandler());

    It works for me. But I’d prefer not to have useless code in place.

    Thanks!

    Reply
  15. Arsenalist Post author

    The PasswordHandler is where you verify whether the password passed in through WS-Security is in fact the correct one. Only if its correct do you make a call to pc.setPassword(…)… Here’s an example which checks against a datbase:


    WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
    String id = pc.getIdentifer();
    username.set(id);
    User user = userManager.getUserByUsername(id);
    if (user != null) {
        pc.setPassword(user.getPassword());
        userData.set(user);
    } else {
        throw new RuntimeException("Invalid Credentials");
    }

    Reply
  16. morg r

    Thanks for the fast response. That’s what I thought. So, by calling setPassword() it looks like I’m essentially telling WS-Security “this user is authenticated”, even though it’s not. That’s not a problem for me since I’m using Spring Security. So I don’t need to look up a password in the database in the interceptor – Spring will do that for me once the request goes through.

    I’ve implemented everything you show above, plus I added this to ValidateUserTokenInterceptor, which I found at http://www.jroller.com/wookets/entry/cxf_acegi_ws_security_acegi

    if (userTokenValidated) {
    HttpServletRequest request = (HttpServletRequest) message.get(“HTTP.REQUEST”);
    request.getSession(true).getId(); // necessary hack

    // authenticate with acegi
    final UsernamePasswordAuthenticationToken authReq =
    new UsernamePasswordAuthenticationToken(principal.getName(), principal
    .getPassword());

    // message.HTTP_REQUEST_METHOD
    authReq.setDetails(new WebAuthenticationDetails(request));

    SecurityContextHolder.getContext().setAuthentication(authReq);
    }

    It looks like I’m just using WS-Security to pass through a username and password then, since I don’t see any more graceful way of telling WS-Security or CXF to use our Spring’s security configuration. Do you see any limitations or concerns with my approach?

    Thanks

    Reply
  17. Senthilnath.V

    while running my WSClient i am getting the error like as follows:
    The error line is while creating instance for the WSOutinterceptor
    Exception in thread “main” java.lang.NoSuchMethodError: org.apache.xml.security.transforms.Transform.init()V
    at org.apache.ws.security.WSSConfig.(WSSConfig.java:81)
    at org.apache.ws.security.WSSConfig.getNewInstance(WSSConfig.java:95)
    at org.apache.ws.security.WSSConfig.(WSSConfig.java:47)
    at org.apache.ws.security.WSSecurityEngine.(WSSecurityEngine.java:51)
    at org.apache.ws.security.handler.WSHandler.(WSHandler.java:61)
    at com.security.client.WsClient.main(WsClient.java:33)

    Reply
  18. Senthilnath.V

    my client code is like this:

    import java.util.HashMap;
    import java.util.Map;

    import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
    import org.apache.cxf.endpoint.Client;
    import org.apache.cxf.endpoint.Endpoint;
    import org.apache.cxf.frontend.ClientProxy;
    import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

    import org.apache.ws.security.WSConstants;
    import org.apache.ws.security.handler.WSHandlerConstants;

    import ca.utoronto.sis.cxfapp.SportsService;
    import ca.utoronto.sis.cxfapp.SportsService_Service;

    public class WsClient {

    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    SportsService_Service service = new SportsService_Service();
    SportsService port = service.getPort(SportsService.class);

    Client client = ClientProxy.getClient(port);
    Endpoint cxfEndpoint = client.getEndpoint();

    Map outProps = new HashMap();
    System.out.println(“here @ 1”);
    WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor();
    //wssOut.setProperties(outProps);
    System.out.println(“here @ 2”);
    //cxfEndpoint.getOutInterceptors().add(wssOut);
    cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor());

    //outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
    // Specify our username
    //outProps.put(WSHandlerConstants.USER, “arsenel”);
    // Password type : plain text
    //outProps.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);

    // for hashed password use:
    //properties.setProperty(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);
    //outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);

    // Callback used to retrive password for given user.
    //outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordHandler.class.getName());

    String result = port.getTeam();
    System.out.println(“Result1: ” + result);

    result = port.getTeam();
    System.out.println(“Result2: ” + result);

    result = port.getTeam();
    System.out.println(“Result3: ” + result);

    }

    }

    Reply

Leave a comment