Contents tagged with Java

  • [Java] Testing SOAP Headers with a Simple Axis Handler

    This is not a tutorial, but just some sharing of my brief learnings when testing a simple Axis Handler (somewhat similar to ASP.NET Soap Extensions). I wrote 2 programs - one simple web service client as a Java console program and one web service. The client calls a web service method called test() but adds a simple SOAP header containing a username and a password. The web service has a BasicHandler, defined in the deployment descriptor as a request handler, which checks each request and makes sure it contains the correct SOAP header with the correct username and password. The handler also sets the validated username in a ThreadLocal variable, which is then picked up by the web service method. If the request does not contain the correct SOAP header, username and password, a fault is thrown.

    There are several ways for the handler class to set data which can be picked up by the web service method, and I know there is a debate going on around the use of ThreadLocals, but I thought it could be useful in this particular case.

    I don't think it matters much which version of Axis you use, but I went for Axis version 1.3. Note that this is test-code, not tested for production. If you find something smelly in the code, I would love to hear about it. Another note is the way I build up the SOAP header and my somewhat sloppy usage of XML namespace. I know it can be done much better, but the intention was to test the handler code :)

    Note that the web service client code is auto-generated from the WSDL and I've not bothered to show the code for it here.

    First, the client code:

    package test;

    import org.apache.axis.message.*;

    public class MyClient {

     public static void main(String[] args) {
     
      System.out.println("Hello, this is the client!");
     
      MyServiceServiceLocator wsloc = new MyServiceServiceLocator();
     
      SOAPHeaderElement oHeaderElement;
      javax.xml.soap.SOAPElement oElement;
      MyServiceSoapBindingStub ws;
     
       try {
        ws = (MyServiceSoapBindingStub)wsloc.getMyService();

        oHeaderElement = new SOAPHeaderElement("http://test",
         "securityHeader");
        oHeaderElement.setPrefix("sec");
        oHeaderElement.setMustUnderstand(false);
        oElement = oHeaderElement.addChildElement("username");
        oElement.addTextNode("johan");
        oElement = oHeaderElement.addChildElement("password");
        oElement.addTextNode("secret");
         
        ws.setHeader(oHeaderElement);
        System.out.println(ws.test());
       } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
       }
     
     }

    }


    Then, the web service code (most comments and javadoc removed):

    package test.service;

    import test.service.handler.MyHandler;

    public class MyService
    {
     public String test()
     {
      String username = MyHandler.getUsername();
      MyHandler.setUsername(null); //clean up
     
      return "Hello from Service! User is = " + username;
     }
    }

    Finally, the Handler code looks something like this:

    package test.service.handler;

    import org.apache.axis.AxisFault;
    import org.apache.axis.Message;
    import org.apache.axis.MessageContext;
    import org.apache.axis.handlers.BasicHandler;

    import java.util.Iterator;

    import javax.xml.soap.SOAPException;
    import javax.xml.soap.SOAPHeader;
    import javax.xml.soap.SOAPHeaderElement;

    import org.apache.axis.message.SOAPEnvelope;

    /**
     * Simple Axis handler which looks for a SOAP header
     * containing two elements - username and password. Invoke() method
     * checks these...
     *
     * @author Johan Danforth
     */
    public class MyHandler extends BasicHandler {

     private static final long serialVersionUID = 1L;
     private static ThreadLocal _username = new ThreadLocal();

        public static String getUsername() {
            return ((String) (_username.get())).toString();
        }
       
        public static void setUsername(String value) {
            _username.set(value);
        }
     
     /**
      * Method called by Axis handler, checks SOAP header with
      * username and password.
      *
      * @throws AxisFault if header is missing or invalid or wrong username or password
      */
     public void invoke(MessageContext msgContext) throws AxisFault {

      boolean processedHeader = false;
     
      try {
       Message msg = msgContext.getRequestMessage();
       SOAPEnvelope envelope = msg.getSOAPEnvelope();
       SOAPHeader header = envelope.getHeader();
       Iterator it = header.examineAllHeaderElements();
       SOAPHeaderElement hel;
      
       while (it.hasNext()) {
        hel = (SOAPHeaderElement) it.next();
        String headerName = hel.getNodeName();
        if(headerName.equals("sec:securityHeader"))
        {
         checkUsername(hel);
         processedHeader = true;
        }
       }
      } catch (SOAPException e) {
       //capture and wrap any exception.
       throw new AxisFault("Failed to retrieve the SOAP Header or it's details properly.", e);
      }

      if(!processedHeader)
       throw new AxisFault("Failed to retrieve the SOAP Header");
     
     }

     private void checkUsername(SOAPHeaderElement hel) throws AxisFault {
      String username = getUsername(hel);
      String password = getPassword(hel);
     
      if(!(username.equals("johan") && password.equals("secret")))
      {
       throw new AxisFault("Access Denied");
      }
      else
      {
       //set username as threadlocal variable
       _username.set(username);
      }
     }

     private String getPassword(SOAPHeaderElement hel) throws AxisFault {
      org.w3c.dom.Node passwordNode = hel.getLastChild();
      String nodename = passwordNode.getNodeName();
      if(!nodename.equals("sec:password"))
       throw new AxisFault("Missing password element in SOAP header.");
      String password = passwordNode.getFirstChild().getNodeValue();
      System.out.println("password = " + password);
      return password;
     }

     private String getUsername(SOAPHeaderElement hel) throws AxisFault {
      org.w3c.dom.Node usernameNode = hel.getFirstChild();
      String nodename = usernameNode.getNodeName();
      if(!nodename.equals("sec:username"))
       throw new AxisFault("Missing username element in SOAP header.");
      String username = usernameNode.getFirstChild().getNodeValue();
      System.out.println("username = " + username);
      return username;
     }
    }


    I need to share with you the deployment descriptors and the web.xml as well. I run the Axis Servlets in the same web app as my web service code. It's up to you how you prefer to do it really. I thought it was easier this way when I used the latest version of Eclipse. That's why the web.xml is full of Axis stuff and I'm using a separate server-config.wsdd instead of registering the service through the Axis admin tool pointing at a separate deploy.wsdd file. I'm attaching a sample deploy.wsdd file as well, just in case someone is unfamiliar with the server-config file.

    My web.xml looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_ID" version="2.4" xmlns="
    http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
     <display-name>
     HelloWorld2</display-name>
     <servlet>
      <display-name>
      Apache-Axis Servlet</display-name>
      <servlet-name>AxisServlet</servlet-name>
      <servlet-class>
      org.apache.axis.transport.http.AxisServlet</servlet-class>
     </servlet>
     <servlet>
      <display-name>
      Axis Admin Servlet</display-name>
      <servlet-name>AdminServlet</servlet-name>
      <servlet-class>
      org.apache.axis.transport.http.AdminServlet</servlet-class>
      <load-on-startup>100</load-on-startup>
     </servlet>
     <servlet-mapping>
      <servlet-name>AxisServlet</servlet-name>
      <url-pattern>/servlet/AxisServlet</url-pattern>
     </servlet-mapping>
     <servlet-mapping>
      <servlet-name>AxisServlet</servlet-name>
      <url-pattern>*.jws</url-pattern>
     </servlet-mapping>
     <servlet-mapping>
      <servlet-name>AxisServlet</servlet-name>
      <url-pattern>/services/*</url-pattern>
     </servlet-mapping>
     <servlet-mapping>
      <servlet-name>AdminServlet</servlet-name>
      <url-pattern>/servlet/AdminServlet</url-pattern>
     </servlet-mapping>
     <welcome-file-list>
      <welcome-file>index.html</welcome-file>
      <welcome-file>index.htm</welcome-file>
      <welcome-file>index.jsp</welcome-file>
      <welcome-file>default.html</welcome-file>
      <welcome-file>default.htm</welcome-file>
      <welcome-file>default.jsp</welcome-file>
     </welcome-file-list>
    </web-app>


    My server-config.wsdd looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <deployment xmlns="
    http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
     <globalConfiguration>
      <parameter name="sendMultiRefs" value="true"/>
      <parameter name="disablePrettyXML" value="true"/>
      <parameter name="adminPassword" value="admin"/>
      <parameter name="attachments.Directory" value="C:\Java\eclipse\workspace\HelloWorld2\.deployables\HelloWorld2\WEB-INF\attachments"/>
      <parameter name="dotNetSoapEncFix" value="true"/>
      <parameter name="enableNamespacePrefixOptimization" value="true"/>
      <parameter name="sendXMLDeclaration" value="true"/>
      <parameter name="sendXsiTypes" value="true"/>
      <parameter name="attachments.implementation" value="org.apache.axis.attachments.AttachmentsImpl"/>
      <requestFlow>
       <handler type="java:org.apache.axis.handlers.JWSHandler">
        <parameter name="scope" value="session"/>
       </handler>
       <handler type="java:org.apache.axis.handlers.JWSHandler">
        <parameter name="scope" value="request"/>
        <parameter name="extension" value=".jwr"/>
       </handler>
      </requestFlow>
     </globalConfiguration>
     <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/>
     <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>
     <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>
     <service name="AdminService" provider="java:MSG">
      <parameter name="allowedMethods" value="AdminService"/>
      <parameter name="enableRemoteAdmin" value="true"/>
      <parameter name="className" value="org.apache.axis.utils.Admin"/>
      <namespace>http://xml.apache.org/axis/wsdd/</namespace>
     </service>
     <service name="Version" provider="java:RPC">
      <parameter name="allowedMethods" value="getVersion"/>
      <parameter name="className" value="org.apache.axis.Version"/>
     </service>
     <service name="MyService" provider="java:RPC" style="wrapped" use="literal">
      <operation name="test" qname="ns3:test" returnQName="ns3:testReturn" returnType="xsd:string" soapAction="" xmlns:ns3="
    http://test" xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>
      <parameter name="allowedMethods" value="test"/>
      <requestFlow>
       <handler type="java:test.service.handler.MyHandler">
        </handler>
      </requestFlow>
      <parameter name="typeMappingVersion" value="1.2"/>
      <parameter name="wsdlPortType" value="MyService"/>
      <parameter name="className" value="test.service.MyService"/>
      <parameter name="wsdlServicePort" value="MyService"/>
      <parameter name="schemaQualified" value="
    http://test"/>
      <parameter name="wsdlTargetNamespace" value="
    http://test"/>
      <parameter name="wsdlServiceElement" value="MyServiceService"/>
     </service>
     <transport name="http">
      <requestFlow>
       <handler type="URLMapper"/>
       <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
      </requestFlow>
      <parameter name="qs:list" value="org.apache.axis.transport.http.QSListHandler"/>
      <parameter name="qs:wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
      <parameter name="qs.list" value="org.apache.axis.transport.http.QSListHandler"/>
      <parameter name="qs.method" value="org.apache.axis.transport.http.QSMethodHandler"/>
      <parameter name="qs:method" value="org.apache.axis.transport.http.QSMethodHandler"/>
      <parameter name="qs.wsdl" value="org.apache.axis.transport.http.QSWSDLHandler"/>
     </transport>
     <transport name="local">
      <responseFlow>
       <handler type="LocalResponder"/>
      </responseFlow>
     </transport>
    </deployment>


    A sample deploy.wsdd could look like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Use this file to deploy some handlers/chains and services      -->
    <!-- Two ways to do this:                                           -->
    <!--   java org.apache.axis.client.AdminClient deploy.wsdd          -->
    <!--      after the axis server is running                          -->
    <!-- or                                                             -->
    <!--   java org.apache.axis.utils.Admin client|server deploy.wsdd   -->
    <!--      from the same directory that the Axis engine runs         -->
    <deployment xmlns="
    http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">

      <service name="MyService" provider="java:RPC" style="wrapped" use="literal">
          <parameter name="wsdlTargetNamespace" value="
    http://test"/>
          <parameter name="wsdlServiceElement" value="MyServiceService"/>
          <parameter name="schemaQualified" value="
    http://test"/>
          <parameter name="wsdlServicePort" value="MyService"/>
          <parameter name="className" value="test.service.MyService"/>
          <parameter name="wsdlPortType" value="MyService"/>
          <parameter name="typeMappingVersion" value="1.2"/>
          <operation xmlns:operNS="
    http://test" xmlns:retNS="http://test" xmlns:rtns="http://www.w3.org/2001/XMLSchema" name="test" qname="operNS:test" returnQName="retNS:testReturn" returnType="rtns:string" soapAction="">
          </operation>
          <parameter name="allowedMethods" value="test"/>
      <requestFlow>
       <handler type="java:test.service.handler.MyHandler">
        </handler>
      </requestFlow>
      </service>
    </deployment>

     

  • [Java] Lomboz Stable Build: S-3.1RC2

    From the ObjectWeb Lomboz site. Seems that ObjectWeb has created a better Lomboz install package:

    Lomboz Complete (All-in-one) Installation
    The Lomboz Project focuses on a prodiving an easy install J2EE IDE tool that is prepackaged with all the necessary plugins that you can start using immediately in one package. Due to its content, this package can take a long time to download. It includes eclipse sdk 3.1, emf, gef, jem, wtp and lomboz all together. Dowaload it and you can get started right away.

    http://lomboz.objectweb.org/downloads/drops/S-3.1RC2-200508311616/

  • [Java] WSS4J version 1.0

    Sorry for all the Java spamming today, but if anyone interested in the Apache Axis and Web Service Security area, the Apache WSS4J people got a version 1.0 ready for download and use now.

    You can get to their site here. They got some pretty good documentation available now it seems. I believe you should be able to learn from my wss4j tutorial, based on the earlier version of wss4j. I posted a follow-up to that tutorial here.

    I'll download the latest version of wssj4 myself and give it a try now, because we're most probably going to use it in the near future. Jeez, I need to get more time to look at the ASP.NET 2.0 stuff too... Argh...

  • [Java] More about cookies in Vignette Application Portal (VAP)

    Another blog post about cookies in VAP...

    Other ways of setting cookies from a portlet?

    I got this message from Sascha about setting cookies from a VAP portlet:

    Hi, I've read you log about setting cookie in a portlet. First, I thought so too, but there actually _is_ a way to set a cookie from a VAP Portlet: you have to get the original HttpServletRequest from the HttpServletRequestWrapper instance in your JSP:

    public static HttpServletResponse getOriginalResponse(HttpServletResponse response) {
       HttpServletResponse result = response;
       if (response instanceof HttpServletResponseWrapper) {
          response = (HttpServletResponse)((HttpServletResponseWrapper) response).getResponse();
          result = getOriginalResponse( response );
       }
       return result;
    }

    I tried this, but from a JSP page in a JSR168 solution it doesn't seem to work. I think maybe Sascha meant a VAP specific portlet/module and not a JSR168 portlet. As far as I understand it, the HTTP headers have already been written when the portlet starts to execute, so it's too late.

    I got another obvious way of setting cookies from a portlet - do it from a client-side javascript:

    Setting cookie through javascript:

    <script>

    document.cookie = "jscriptcookie=testvalue";

    </script>

    This obviously works fine and you can also get cookies this way, but again, what I'm really after is a way to both get and set cookies from within a portlat class scope, say for example in the processAction() method. Below I have a way of getting a cookie from there, but setting it still seems to be impossible.

    A better way of getting cookies from within a portlet class

    Earlier we've had to grab cookies from a JSP page in a JSR168 portlet solution, but a friend of mine found out that VAP actually adds the "cookie" http header(s) to the PortletRequest properties collection, according to the JSR168 spec, section PLT.11.1.4. So from within a JSR168 portlet it's quite simple to get the cookies from the RenderRequest.getProperties("cookie"). Thank you Vignette, now make sure all future versions of VAP does the same please :)

  • [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.

  • [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 :)

  • [Java] jcifs problem fixed in 1.1.5

    I got a message from Mike of the jcifs team that the problem with ArrayIndexOutOfBoundsException should be fixed in version 1.1.5. From http://jcifs.samba.org/:

    jcifs-1.1.5 released / ArrayIndexOutOfBoundsException
    posted by Santa, Dec 17, 2004
    It was discovered that an ArrayIndexOutOfBoundsException could occur if the list of domain controllers returned by NbtAddress.getAllByName was shorter than the list returned in the previous call (possibly because the WINS query timed out and switched to an alternate WINS server). All NTLM HTTP Authentication Filter users should upgrade to prevent this error. Also, the value of jcifs.netbios.cachePolicy set by the NTLM HTTP Filter if it is not specified has been doubled to 20 minutes. Finally, some log levels have been increased such that running with jcifs.util.loglevel = 3 temporarily is actually reasonable in a production environment (must use loglevel > 3 to see individual SMB messages and loglevel > 5 to get hexdumps).

    Will test the new version in January I think.

  • [Java] Crash in the jcifs 1.1.3 version

    UPDATE: I got a message from Mike of the jcifs team that the problem with ArrayIndexOutOfBoundsException should be fixed in version 1.1.5.

    I though I would just warn users of the jcifs 1.1.3 and maybe also 1.1.4 package.

    We got a pretty serious crash in our portal application this morning. We released a new version of the portal utilizing jcifs version 1.1.3 for NTML authentication. When we reached a higher number of concurrent users (perhaps some 50+ sessions) we got internal server error:

    Error 500--Internal Server Error
    java.lang.ArrayIndexOutOfBoundsException: 25
    at jcifs.smb.SmbSession.getChallengeForDomain(SmbSession.java:94)
    at jcifs.http.NtlmHttpFilter.doFilter(NtlmHttpFilter.java:123)
    at weblogic.servlet.internal.FilterChainImpl.doFilter
    (FilterChainImpl.java:27)
    at
    weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run
    (WebAppServletContext.java:6354)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs
    (AuthenticatedSubject.java:317)
    at weblogic.security.service.SecurityManager.runAs
    (SecurityManager.java:118)
    at weblogic.servlet.internal.WebAppServletContext.invokeServlet
    (WebAppServletContext.java:3635)
    at weblogic.servlet.internal.ServletRequestImpl.execute
    (ServletRequestImpl.java:2585)
    at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:197)
    at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:170)

    We had to go back to an older version of jcifs now.