Wednesday, December 8, 2010

Securing Web Services using Apache Rampart - Transport Level Security

Note that you will find many articles about this topic, but I'm going to show you how to do this in WebSphere Application Server 6.1.0.27.

Apache Rampart
Apache Rampart acts as the security module for Axis2. You can use Rampart for securing web services by enforcing authentication, integrity and confidentiality.
You can download it from here.
Download it and copy it's JAR file to the WEB-INF/libs  directory of your web application and copy the modules directory in the Rampart distribution to WEB-INF directory of you web application.
We will create a simple Web Service and secure it using Apache Rampart. Let's write the service implemetation class.
service class
package com.test;
public class Calculator {
 public int addService(final int a, final int b) {
  return a + b;
 }
}
Now, Let's write the services.xml, which tells Axis what is your service class and what are the methods you are going to expose through the web service.

services.xml

    
        com.test.Calculator
    
    
 



Password callback class 

Since we are going to use password callback class to verify the client provided username and the password, we should implement a password callback class.

package com.test;
import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
public class PWCBHandler implements CallbackHandler {
    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            //When the server side need to authenticate the user
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            // For plain text password scenarios
            if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) {
                if (pwcb.getIdentifier().equals("kushanxp")
                        && pwcb.getPassword().equals("kushan"))
                {
                    //If authentication successful, simply return
                    return;
                } else {
                    throw new UnsupportedCallbackException(callbacks[i], "check failed");
                }
            // For digested password scenarios
            } else if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN ) {
                if (pwcb.getIdentifier().equals("kushanxp"))
                {
                    //We simply set the password, Username Token processor verifies the digest
                    pwcb.setPassword("kushan");
                    return;
                }
            }
        }
    }
}
Now we have to add policy based information to the services.xml,
services.xml



    
    com.ws.service.Calculator
    
    
       
    
    
        
            
                
                    
                        
                            
                                
                            
                        
                       
                            
                                
                            
                        
                        
                            
                                
                            
                        
                        
                    
                

                
                    
                       
                    
                

                
                     com.test.PWCBHandler
                    
                
            
        
    









Note that you can find the policy based configurations from    <wsp:Policy> to </wsp:Policy> and you have to mention your password callback class in the                  <ramp:passwordCallbackClass>      com.test.PWCBHandler </ramp:passwordCallbackClass> Engaging Rampart You may notice that I have added <module ref="rampart"/> in top of the services.xml file, This will tell Axis2 that we going to engage Rampart with our web service. Note that this is local to this service, if you want to engage Rampart globally to all of your services, then you have to add <module ref="rampart"/> this line to axis2.xml file. Now you are almost complete, Let's create a WAR file with our service and deploy in on WebSphere Application Server. Your WAR file directory structure should be as follows, (Assume your WAR file name is calculator.war) calculator.war  Now we have done all the coding for the service level, now it's time to move to client end. Client Implementation  

client.java
 

package com.ws.client;

import java.io.FileNotFoundException;
import java.rmi.RemoteException;

import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axis2.AxisFault;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.rampart.RampartMessageData;

public class Client {

    /** * @param args */
    public static void main(String[] args) {
 try {
     System.setProperty("javax.net.ssl.trustStore", "keys/service.jks");
     System.setProperty("javax.net.ssl.trustStorePassword", "kushan");
     ConfigurationContext ctx = ConfigurationContextFactory
      .createConfigurationContextFromFileSystem("repo", null);
     CalculatorStub stub = new CalculatorStub(ctx,
      "https://localhost:8443/axis2/services/Calculator");
     ServiceClient client = stub._getServiceClient();
     client.engageModule("rampart");
     Options options = client.getOptions();
     options.setUserName("kushanxp");
     options.setPassword("kushan");

     options.setProperty(RampartMessageData.KEY_RAMPART_POLICY,
      loadPolicy("resources/policy.xml"));

     int a = 3;
     int b = 4;
     int result = stub.add(a, b);
     System.out.println(a + " + " + b + " = " + result);
 } catch (AxisFault e) {
     e.printStackTrace();
 } catch (RemoteException e) {
     e.printStackTrace();
 }
    }

    /**
     *
     * Loads the policy.
     *
     *
     *
     * @param xmlPath
     *
     * @return
     */

    private static Policy loadPolicy(String xmlPath) {

 StAXOMBuilder builder = null;

 try {

     builder = new StAXOMBuilder(xmlPath);

 } catch (FileNotFoundException e) {

     e.printStackTrace();

 } catch (XMLStreamException e) {

     e.printStackTrace();

 }

 return PolicyEngine.getPolicy(builder.getDocumentElement());

    }
}

Tuesday, December 7, 2010

Embedding Axis2 into an Existing Application

Many of you may came up with implementing Web Services for existing application, here I have done it using Apache Axis 2, So here what you need to do,

You should edit your web.xml file found in WEB-INF directory, and there you have to configure Axis specific information, (You must configure AxisServlet there, so when the application deploying server will identify that Axis2 is plugged in to this application)

web.xml

    
        Apache-Axis Servlet
        AxisServlet
        
            org.apache.axis2.transport.http.AxisServlet
        
        1
    

Now add servlet mapping,
    
        AxisServlet
        /services/*
    

This will tell the Axis Servlet that all your Web Services will include in a directory called services, So the service URL's must have services text within their URL's

If you do not wish to add your web services in a directory called services, you can change this to something else,

i.e :
    <servlet-mapping>
        <servlet-name>AxisServlet</servlet-name>
        <url-pattern>/serviceapps/*</url-pattern>
    </servlet-mapping>


And you need to tell axis2.xml that where are your services,

axis2.xml
serviceapps
note

Axis2 assumes that all your service implementations are in a directory called services, in WEB-INF folder. so if you are not changing that, you don't have to modify axis2.xml file.

Thats all you have to do, Remember to copy all the JAR files in Axis2 distribution to your WEB-INF/lib directory :)

So when the application deploying in the server, it will initialize the AxisServlet and loads the web service implementations from default locations,

Default Service Location

WEB-INF/services

Default Module Location

WEB-INF/modules

Default Axis2 XML Location

WEB-INF/conf

Directory structure.