Johan Danforth's Blog


Carpe Diem

January 2005 - Posts

Some old articles from CodeProject
I moved or copied a couple of old ASP.NET articles I wrote from CodeProject to my blog, mainly to keep track of them. They're available under my Articles section here. Old ones, but pretty good primers for new ASP.NET developers.
Must-have tools for .NET developers

This is a pretty old one, but still, the tools on this list are just great. I still have to look at CodeSmith and Regulator.

The tools on the list are these:

  • NUnit to write unit tests
  • NDoc to create code documentation
  • NAnt to build your solutions
  • CodeSmith to generate code
  • FxCop to police your code
  • Snippet Compiler to compile small bits of code
  • Two different switcher tools, the ASP.NET Version Switcher and the Visual Studio .NET Project Converter
  • Regulator to build regular expressions
  • .NET Reflector to examine assemblies

I would like to add ReSharper (from the JetBrain people) to the list, a quite good refactoring tool.

[Java] Setting WSS4J properties for Username Token at run-time

I managed to dynamically set the username and the other properties that normally goes into the client WSDD file. Again, many thanks to the nice people in the WSS4J mailing list.

First you have to modify the client WSDD file from the tutorial and comment out the properties. If you leave them in there, I believe those values will be used. So, the client_deploy.wsdd looks something like this:

<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" >
    <!-- parameters removed -->
    </handler>
   </requestFlow >
  </globalConfiguration >
</deployment>

Then you have to modify the StockServiceClient.java code somewhat to be able to set the handler properties

package samples.stock.client;

import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.configuration.FileProvider;
import org.apache.axis.client.Stub;
import java.rmi.Remote;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.message.token.UsernameToken;

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;
        }
        
        //modify the path to the client_deploy.wsdd
        EngineConfiguration config = new FileProvider("client_deploy.wsdd");
        StockQuoteServiceService locator = new StockQuoteServiceServiceLocator(config);
       
        Remote remote = locator.getPort(StockQuoteService.class);
        Stub axisPort = (Stub)remote;
        axisPort._setProperty(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        axisPort._setProperty(UsernameToken.PASSWORD_TYPE, WSConstants.PASSWORD_DIGEST);
        axisPort._setProperty(WSHandlerConstants.USER, "wss4j");
        axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_CLASS, "samples.stock.client.PWCallback");

        //possible to pass a callback instance by ref instead of a class as above
        //PWCallback pwCallback = new PWCallback();
        //axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_REF, pwCallback);

        //Need to cast 
        StockQuoteService service = (StockWss01SoapBindingStub)axisPort;

        System.out.println("Calling service...");
        float quote = service.getQuote(args[0]);
        System.out.println("stock quote service returned " + args[0] + ": "
                + quote);
    }
}

Note that there are several ways to read the WSDD file dynamically, you can also define "axis.ClientConfigFile" as a system property.

In this sample you're still using a client WSDD file, specifying the handler (WSDoAllSender) Axis should be calling when the SOAP request is sent. It's possible to code so that you don't need the WSDD file at all, but I'll try that at a later state.

I'm going to be quite busy travelling the next week, but I'll try to make a simple Java Axis/Wss4j client call a ASP.NET service with a UsernameToken added. It'll be fun.

Posted: Jan 22 2005, 11:30 PM by jdanforth | with 1 comment(s)
Filed under:
[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.

 

[Java][Interop] Testing WSS4J

Long time since I wrote anything here, but I've actively started to look at what AXIS and WSS4J has to offer when it comes to do WS-Security in Java and how it interops with web services in .NET using WSE 2.0. It's taken me a while to get the Java environment up and running at home because I had to reinstall the whole box. What I've done now is I've installed Virtual PC 2004 and created an XP image with a complete Java environment

I've downloaded and installed (so far):

j2sdk 1.4.2_06
tomcat
ant
axis
maven (to checkout and built wss4j)
wss4j
cvs
junit
eclipse (java IDE)
lomboz (j2ee plugin for eclipse)

It takes a while to configure maven and all the stuff you need for it (cvs, junit and so on) and get it running and in the end I had to go into the ant script and remove the junit testing :( Biggest problem when working with Axis and Wss4j is the lack of documentation. What I'm following now when I do my experiments are some basic documentation about how to do usernametokens with wss4j written by a kind fellow in the wss4j maillinglist.

I've just got the basic stuff working with a Java client using Axis calling a web service in tomcat using Axis too. That's the easy part, I'll now try to plug in wss4j and do usernametoken... I'll blog the results here before I go to bed :)

Posted: Jan 15 2005, 08:51 PM by jdanforth | with no comments
Filed under: ,
More Posts