[Java] Simple WSS4J with Axis Tutorial

This is a simple tutorial for getting started with WSS4J. It’s based on a tutorial posted on the WSS4J mailing list by Rami Jaamour, but I’ve added a few clarifications and variants of the code samples. Rami should get all credits for this tutorial; I’m just a newbie trying to learn how to use this tool!

Updates

2006-03-29 - If you get an exception like this one below, it is recommended to look at which Axis version your are using and consider Axis version 1.2:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method org.apache.axis.SOAPPart.setCurrentMessage(Ljava/lang/Object;I)V from class org.apache.ws.axis.security.WSDoAllSender
at org.apache.ws.axis.security.WSDoAllSender.invoke(WSDoAllSender.java:365)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:71)

2005-01-29 - This "article" has been updated a couple of times now. Both with information regarding the xalan.jar problems, and how to set the UserName Token dynamically.

Credits

As I wrote above, all cred should go to Rami Jaamour because most of the stuff below is written by him. My thanks to the nice guys in the WSS4J mailing list - Ashok Shah, Werner Dittmann, Yves Langisch and others.

The Future

I've added a few things myself to this tutorial, and I'll keep adding things as I learn more. I'll also connect this tutorial with a Username Token service written in ASP.NET as soon as possible. After that we'll see what happens. I'd like to encrypt and sign the stuff too in the future...

Introduction

WSS4J can be used for securing web services deployed in virtually any application server, but it includes special support for Axis. WSS4J ships with handlers that can be used in Axis-based web services for an easy integration. These handlers can be added to the service deployment descriptor (wsdd file) to add a WS-Security layer to the web service. This is a step by step tutorial for deploying a simple service with Username Token.

Prereqs

To run this tutorial, you must install a JDK (of course). I suggest JDK 1.4.2_04 or 1.5.0. Then you need an application server. I’ve personally used version jakarta-tomcat-4.1.31. Then you need to download and install Axis (version 1.2) and WSS4J. Getting hold of WSS4J and the other jars you may need can be quite tricky. One way is to download Maven and checkout and build WSS4J through it. That’s what I did (not without problems though).

If you have problems getting the needed jar files let me know and I'll try to add them to this space for download. I've compiled the wss4j.jar package and made it available for download here.

You don’t really need a Java code editor, but it helps. Personally I use Eclipse and Lomboz (a J2EE plug-in for Eclipse).

Installing WSS4J

  1. Download the WSS4J binaries or build it from sources
  2. Copy the contents (the jar files) of the WSS4J lib directory to your Axis WEB-INF/lib directory. Many jar files will already exist. Most of them will already exist there but you can just overwrite them all.
  3. You may need to restart Tomcat unless you have automatic deployment/class loading turned on. Check the Axis Happiness Page (typically at http://localhost:8080/axis), make sure that the XML Security (xmlsec.jar) is listed under the "Optional Components" section.

Creating the service

  1. This tutorial will secure the StockQuoteService which ships with the sample code with Axis. If you deploy the sample web apps that ships with Axis you don’t need to do anything more. Look at the Axis docs on how to install it properly. Unless you have one already, create a deployment descriptor (deploy.wsdd) file with the following contents:


<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <service name="stock-wss-01" provider="java:RPC" style="document" use="literal">
  <parameter name="className" value="samples.stock.StockQuoteService"/>
  <parameter name="allowedMethods" value="getQuote"/>
  <parameter name="scope" value="application"/>
 </service>
</deployment>

It doesn’t matter where you put this file.

  1. deploy the service (using AxisAdmin):

java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService deploy.wsdd

The AdminClient class depends on a load of jar-files, so to deploy this I created a bat-file that looked like this:

setlocal

set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;

java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService test-deploy.wsdd

endlocal

You have to change the bat-file to reflect where you’ve put your axis jar files naturally.

Creating the Client

  1. Use WSDL2Java to generate the client service bindings (a number of soap client classes):

    java org.apache.axis.wsdl.WSDL2Java -o . -Nhttp://fox:8080/axis/services/stock-wss-01 samples.stock.client http://fox:8080/axis/services/stock-wss-01?wsdl

    Again, the wsdl2java needs a number of jar files to work properly, so I created a new bat-file to help out with that. The bat-file looks like this:

    setlocal

    set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;C:\axis-1_2RC2\lib\wsdl4j.jar;

    java org.apache.axis.wsdl.WSDL2Java -o . -Nhttp://localhost:8080/axis/services/stock-wss-01 samples.stock.client http://localhost:8080/axis/services/stock-wss-01?wsdl

    endlocal

    A bunch of java classes will be created under samples/stock/client, including the StockQuoteServiceServiceLocator.
  2. Write a simple java console application that uses the generated service locator. For example:

    package samples.stock.client;

    import java.rmi.RemoteException;
    import javax.xml.rpc.ServiceException;

    public class StockServiceClient {
        public StockServiceClient() {
        }
        public static void main(String[] args) throws ServiceException, RemoteException {
            if (args.length == 0) {
                System.out.println("Usage:\njava StockServiceClient [symbol]");
                return;
            }
            StockQuoteServiceService locator = new StockQuoteServiceServiceLocator();
            StockQuoteService service = locator.getStockWss01();
            float quote = service.getQuote(args[0]);
            System.out.println("stock quote service returned " + args[0] + ": " + quote);
        }
    }
  3. run the client:

    java samples.stock.client.StockServiceClient XXX

    If all went well, you should get the result:

    stock quote service returned IBM: 55.25

When using "XXX" as parameter, the service won't try to go out on the Internet to get the real quotes, but just returns a float with the value of 55.25.

What you’ve created so far is a very simple web service with a simple client that calls it. WSS4J has not been used yet, so this web service call is unprotected. Now it’s time to add a Username Token to the soap call.

Configuring the Service for Username Token

  1. Modify the deployment descriptor you created above to look like this:

    <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
     <service name="stock-wss-01" provider="java:RPC" style="document" use="literal">
      <requestFlow>
       <handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
        <parameter name="passwordCallbackClass" value="PWCallback"/>
        <parameter name="action" value="UsernameToken"/>
       </handler>
      </requestFlow>

      <parameter name="className" value="samples.stock.StockQuoteService"/>
      <parameter name="allowedMethods" value="getQuote"/>
      <parameter name="scope" value="application"/>
     </service>
    </deployment>

    WSDoAllReceiver is an Axis handler located in wss4j.jar package. This is the standard way to deploy an Axis handler. For more details please refer to the Axis handler for WSS4J documentation.
  2. Create a class named PWCallback.java and compile it and put the resulting PWCallback.class file into your Axis WEB-INF/classes directory. In this example I used the default package for simplicity, but you might need to use the fully qualified class name (be consistent with the deployment descriptor).

    The following code snippet shows a simple password callback class:

    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 PWCallback implements CallbackHandler {
        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
            for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof WSPasswordCallback) {
                    WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
                    // set the password given a username
                    if ("wss4j".equals(pc.getIdentifer())) {
                        pc.setPassword("security");
                    }
                } else {
                    throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
                }
            }
        }
    }


  3. Redeploy the service using the bat file you created earlier. Your service should now be expecting a WSS Username Token in the incoming soap request, and clients should send the username "wss4j" and password "security" to get through.

Configuring the Client for Username Token

  1. run the client we created again:

    java samples.stock.client.StockServiceClient IBM

    You should now get an error:

    Exception in thread "main" AxisFault
     faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.generalException
     faultSubcode:
     faultString: WSDoAllReceiver: Request does not contain required Security header

    This is because your client is not configured to send a Username Token yet, so the service is rejecting the request. To fix this, you need to create a callback class in the client, which adds the Username Token to the outgoing soap request.
  2. Create a deployment descriptor file (client_deploy.wsdd) for the client:

    <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
     <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
      <globalConfiguration >
       <requestFlow >
        <handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
         <parameter name="action" value="UsernameToken"/>
         <parameter name="user" value="wss4j"/>
         <parameter name="passwordCallbackClass" value="samples.stock.client.PWCallback"/>
         <parameter name="passwordType" value="PasswordDigest"/>
        </handler>
       </requestFlow >
      </globalConfiguration >
    </deployment>
  3. Create the samples.stock.client.PWCallback class:

    package samples.stock.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;

    /**
     * PWCallback for the Client
     */
    public class PWCallback implements CallbackHandler {

        /**
         * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
         */
        public void handle(Callback[] callbacks) throws IOException,
                        UnsupportedCallbackException {
            for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof WSPasswordCallback) {
                    WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
                    // set the password given a username
                    if ("wss4j".equals(pc.getIdentifer())) {
                        pc.setPassword("security");
                    }
                } else {
                    throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
                }
            }
        }
    }
  4. Define the system property axis.ClientConfigFile for your client:

    java -Daxis.ClientConfigFile=client_deploy.wsdd -classpath $AXISCLASSPATH samples.stock.client.StockServiceClient

    Make sure that your CLASSPATH includes the jar files under WEB-INF/lib.

    Another way to do this is to specify the wsdd file in your StockServiceClient to the service locator programmatically:

    ...
    import org.apache.axis.EngineConfiguration;
    import org.apache.axis.configuration.FileProvider;
    ...

    EngineConfiguration config = new FileProvider("client_deploy.wsdd");
    StockQuoteServiceService locator = new StockQuoteServiceServiceLocator(config);
    ...
  5. Run the client, you should get no errors:

    stock quote service returned XXX: 55.25

    Your client is now sending a Username Token in the wsse request header with the username "wss4j" (see client_deploy.wsdd) and password "security" (see the PWCallback implementation).

    Another way to do this is to have the client application set the username and CallbackHandler implementation programmatically instead of using the client_deploy.wsdd file:

    ...
    import org.apache.axis.client.Stub;
    ...

    Remote remote = locator.getPort(StockQuoteService.class);
    Stub axisPort = (Stub)remote;
    axisPort._setProperty(UsernameToken.PASSWORD_TYPE, WSConstants.PASSWORD_DIGEST);
    axisPort._setProperty(WSHandlerConstants.USER, "wss4j");
    axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_REF, pwCallback);

    where "pwCallback" is a reference to a PWCallback implementation. See the Axis handler for WSS4J documentation for more details on this.

    UPDATE: I've tried to set the callback using the techinque above, but without much success. I'll continue trying, and when I get it working I'll update this section again :)


    UPDATE 2: After some testing and teaking and good ideas from people, I got the thing above working. It's all explained in another blog post.
  6. Try modifying your client's PWCallback to return the wrong password, or send the wrong username. The service should reject your requests.

Known problems

When I first ran this tutorial myself, I got a stacktrace error that didn’t interrupt the program, but printed out a warning about the xalan.jar package. It looks something like this:

- Unable to patch xalan function table.
       java.lang.NoSuchFieldException: m_functions
              at java.lang.Class.getField(Unknown Source)
              at org.apache.xml.security.Init.registerHereFunction(Init.java:429)
              at org.apache.xml.security.Init.init(Init.java:124)… (and so on)

This may have to do with how the xalan.jar package is deployed on your system and what version of xalan you use and the version of JDK. I got the tip from Ashok Shah to make sure I use Java version 1.4.2_04 instead of 1.4.2_06 that I used. I’ve not tried this yet, but I will do.

UPDATE: I tried to put the xalan.jar in the JAVA_HOME/lib/endorsed/ directory, but it didn't work much better. So I updated to JDK 5.0 and made sure that the xalan.jar package from the WSS4J distribution was available to tomcat and to my client, and behold - it works :)

UPDATE 2: I got a tip from Martin Stemplinger that the xalan.jar should probably go into the JAVA_HOME/jre/lib/endorsed/ directory. I've not tested it myself, but it sounds right.

 

10 Comments

  • Hello Johan.

    I've found your article very interesting, but I still have some question. Maybe you win be able to help me.



    I use Axis connecting to web-service over NTLM authorization by HTTPS. I couldn't find any ways to make Axis use custom autorization mechanism. I know that Appach-common-httpclient 2.0 implements NTLM authorization. But I couldn't find a point where I would insert this initialization.



    I would appreciate you give me an advice.

  • Oh sorry, one more thing.

    I've forgoten to put my email.

    kkasatkin@estylesoft.com

    If it isn't hard for you, send your responce directly on my email.

  • After I had put the xalan.jar both in JAVA_HOME\lib\endorsed and JAVA_HOME\jre\lib\endorsed it worked. I think only the latter is necessary.

  • How can I pass username and password without using a client side callback class?

  • Not sure exactly what you want to do, but if you want to use wss4j, you MUST use a callback class. Another way of doing it without using wss4j is to manually construct the SOAP request (XML) so that it contains the correct wsse headers for a UserName Token.

  • Hi,



    This is nice hands-on. Do we have any to sign similiar tutorials on how to sign and verify SOAP Messages ?





    Thanks &amp; Regards,

    Kumar.

  • Sorry, I don't have any totiroals on signing SOAP messages with Java. I'm normally a Microsoft guy ;)



    I suggest joining the WSS4J mailing list and go from there.

  • Hi Folks,



    We are facing this issue when testing wss4j.jar.Can anyone let us know how to resolve this?



    Rgds,

    Pavan



    C:\axis-1_1\samples\stock&gt;java -Daxis.ClientConfigFile=client_deploy.wsdd sample

    s.stock.client.StockServiceClient XXX

    Exception in thread &quot;main&quot; java.lang.IllegalAccessError: tried to access method

    org.apache.axis.SOAPPart.setCurrentMessage(Ljava/lang/Object;I)V from class org.

    apache.ws.axis.security.WSDoAllSender

    at org.apache.ws.axis.security.WSDoAllSender.invoke(WSDoAllSender.java:3

    65)

    at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrateg

    y.java:71)

    at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:150)

    at org.apache.axis.SimpleChain.invoke(SimpleChain.java:120)

    at org.apache.axis.client.AxisClient.invoke(AxisClient.java:167)

    at org.apache.axis.client.Call.invokeEngine(Call.java:2564)

    at org.apache.axis.client.Call.invoke(Call.java:2553)

    at org.apache.axis.client.Call.invoke(Call.java:2248)

    at org.apache.axis.client.Call.invoke(Call.java:2171)

    at org.apache.axis.client.Call.invoke(Call.java:1691)

    at samples.stock.client.StockWss01SoapBindingStub.getQuote(StockWss01Soa

    pBindingStub.java:100)

    at samples.stock.client.StockServiceClient.main(StockServiceClient.java:

    16)

  • Hi Johan Danforth ,

    U said that
    Not sure exactly what you want to do, but if you want to use wss4j, you MUST use a callback class. Another way of doing it without using wss4j is to manually construct the SOAP request (XML) so that it contains the correct wsse headers for a UserName Token.


    Could u please give me the detailed information to construct the SOAP request (XML).

    Thanks in advance,
    Kiran.

  • hi,
    this tutorial gives us information only to use username token of wss4j but not giving any idea about how to encrypt different elements of SOAP message and sign them .
    if anyone can help ,please send me info on my mail account
    amit_vyas_1981@yahoo.co.in

    thanks in advance

    amit

Comments have been disabled for this content.