Pages

Wednesday, March 28, 2007

Step by step tutorial to use Rampart with axis2 web service for implementing security.

Given below is a tutorial to use axis2 in implementing web services with security(This shows sync/Blocking call client along with Rampart security). This tutorial extends the previous tutorials to add security features to the web services using rampart module. For more details regarding any aspect of the tutorial, the axis2 or rampart documents will assist you.

My Configuration:(same as previous)
a) AXIS2 1.1.1
b) JDK 1.5
c) ANT 1.7.0
d) TOMCAT 5.5

Before proceeding, please read the installation doc of axis2 and rampart to do a proper installation.

Step 0: Set the following paths in your system.(This step is very important)
a) AXIS2_HOME = "your axis2 installation(unzipped) directory"
b) JAVA_HOME = "your jdk installation path"
c) PATH = "add ant, axis2/bin to this"

Step 1: Extract the rampart-1.1.zip. Before doing anything more copy the %AXIS2_HOME%/lib to %AXIS2_HOME%/lib.1. Run the ant task on build.xml present in extracted folder. Now rebuild the whole axis2(i.e run the ant task in %AXIS2_HOME%/webapp). Deploy this in your server. Rename the lib to lib.2, and lib.1 to lib.[This is to run WSDL2JAVA and JAVA2WSDL tool, other wise the tools doesn't run and the reason is that the path AXIS2_CLASS_PATH becomes too long(I am running on Win 2000)]

Step 2: Create your service interface and impl class. Generate the WSDL using java2wsdl, generate the skeleton and stubs using wsdl2java, code the skeleton. Build the "MyRampartService.aar" and deploy on the server. Test the service. Use the client service stub to code a basic client, run the client to see its working. This step has set up a web service and a client in a usable state. Next, step goes through the rampart part.
The service and skeleton are given below for reference.
--------------Interface-------------------
package axis2.adb.sync.rampart;
public interface MyRampartService {
public String getSecretText(String codeword);
}

-------------Impl class-------------------
package axis2.adb.sync.rampart;
public class MyRampartServiceImpl implements MyRampartService {
public String getSecretText(String codeword) {
if(codeword.equalsIgnoreCase("apache"))
return "You have done a good choice to build an application";
return "You have done a bad choice to build application";
}
}
-------------Skeleton Class----------------
/**
* MyRampartServiceSkeleton.java
*
* This file was auto-generated from WSDL
* by the Apache Axis2 version: 1.1.1 Jan 09, 2007 (06:20:51 LKT)
*/
package axis2.adb.sync.rampart;
import axis2.adb.sync.rampart.MyRampartService;
import axis2.adb.sync.rampart.MyRampartServiceImpl;
import axis2.adb.sync.rampart.xsd.GetSecretTextResponse;
public class MyRampartServiceSkeleton implements MyRampartServiceSkeletonInterface {
MyRampartService myService = new MyRampartServiceImpl();
public axis2.adb.sync.rampart.xsd.GetSecretTextResponse getSecretText(axis2.adb.sync.rampart.xsd.GetSecretText param2){
GetSecretTextResponse res = new GetSecretTextResponse();
res.set_return(myService.getSecretText(param2.getParam0()));
return res;
}
}

Step 3: This step uses the example from the rampart sample05. The intention is to encrypt the data in XML's which are exchanged between the client and the server. Copy %AXIS2_HOME%/repository to a convenient location. Copy %AXIS2_HOME%/conf/axis2.xml to %COPIED_LOCATION%/repository/modules/client.axis2.xml. Copy the rampart-1.1/samples/Keys to "resources" directory created on running the WSDL2JAVA on previous step. We will be using the same keystore which rampart has provided for encrypting the message. you use the "keytool" that is a part of JDK to create your own keystore. Add the rampart lib also to your classpath to compile and run the client. Copy from the sample05 directory org/apache/rampart/samples/sample05/PWCBHandler.java into your source directory(remember to change the package in PWCBHandler.java, if you put it in some other package). This is the callback handler that is called on the client and the server side for verifying the password.

Step 4: Modify the resources/services.xml and %COPIED_LOCATION%/repository/modules/client.axis2.xml to enter Rampart configuration.
Add the below into the services.xml.
<serviceGroup>
<service name="MyRampartService">
......
......
<module ref="rampart" />
<parameter name="InflowSecurity">
<action>
<items>Encrypt</items>
<passwordCallbackClass>axis2.adb.sync.rampart.callbackhandler.PWCBHandler</passwordCallbackClass>
<decryptionPropFile>service.properties</decryptionPropFile>
</action>
</parameter>

<parameter name="OutflowSecurity">
<action>
<items>Encrypt</items>
<encryptionUser>client</encryptionUser>
<encryptionPropFile>service.properties</encryptionPropFile>
</action>
</parameter>
</service>
</serviceGroup>

Add the below into the client.axis2.xml just before the "phases" definitions.
<module ref="rampart" />

<parameter name="OutflowSecurity">
<action>
<items>Encrypt</items>
<encryptionUser>service</encryptionUser>
<encryptionPropFile>client.properties</encryptionPropFile>
</action>
</parameter>

<parameter name="InflowSecurity">
<action>
<items>Encrypt</items>
<passwordCallbackClass>axis2.adb.sync.rampart.callbackhandler.PWCBHandler</passwordCallbackClass>
<decryptionPropFile>client.properties</decryptionPropFile>
</action>
</parameter>

Now, parameter OutFlowSecurity represents the action to be taken on the outgoing XML's. In our case it is to encrypt. The encryption property file client.properties is present in keys directory. It gives the details of the keystore file, the password to that keystore, the keystore type and the provider.

The parameter InFlowSecurity represents the action to be taken on the incoming XML's. It provides with the decryption property file which is same as the encryption property file in client.axis2.xml, the action item is same as the OutFlowSecurity action item and the password callbackhandler class. I am using the one which came with samples provided with apache rampart.Its given below.

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++) {
WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
String id = pwcb.getIdentifer();
if("client".equals(id)) {
pwcb.setPassword("apache");
} else if("service".equals(id)) {
pwcb.setPassword("apache");
}
}
}
}

Step 5: Edit build.xml.
Search for target "jar.all" and add the following beneath the first copy element.
<copy toDir="${classes}" failonerror="false">
<fileset dir="${resources}/keys">
<include name="*.jks"/>
<include name="*.properties"/>
</fileset>
</copy>

Step 6: Build and deploy service.

Run ant command on this build.xml and deploy the service on axis2 in tomcat. Do not forget to restart the tomcat server if hotupdate is off.

Step 7: Edit the client and run. The client code is given below with appropriate comments. We do not have to configure the client using the XML, it can be done through code also. The below client uses the code when useRampartThroughCode is set to true. Please note that when this is used you do not have to configure the inflowsecurity and outflow security in the client.axis2.xml. Add the "resource/keys" on to the classpath before you run the client.

package axis2.adb.sync.rampart.client;
import java.rmi.RemoteException;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.description.Parameter;
import org.apache.rampart.handler.WSSHandlerConstants;
import org.apache.rampart.handler.config.InflowConfiguration;
import org.apache.rampart.handler.config.OutflowConfiguration;
import axis2.adb.sync.rampart.MyRampartServiceStub;
import axis2.adb.sync.rampart.xsd.GetSecretText;
import axis2.adb.sync.rampart.xsd.GetSecretTextResponse;

public class MyRampartServiceServiceClient {
private static boolean useRampartThroughCode = false; // set this to true if you do not want to configure client using client.axis2.xml for using rampart.

public static void main(String[] args) {
MyRampartServiceStub stub = null;
ConfigurationContext configContext = null;
try {
// put your appropriate repository path in first arg, and path to client.axis2.xml in second arg.
configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem("Axis2WithRampart/repository", "Axis2WithRampart/repository/modules/client.axis2.xml");
//create the stub
stub = new MyRampartServiceStub(configContext,"http://localhost:8080/axis2/services/MyRampartService");
if(useRampartThroughCode){
//set the outflow security through code
stub._getServiceClient().getOptions().setProperty(WSSHandlerConstants.OUTFLOW_SECURITY, getOutflowConfiguration());
//set the inflow security through code
stub._getServiceClient().getOptions().setProperty(WSSHandlerConstants.INFLOW_SECURITY, getInflowConfiguration());
}
// invoke the service
String returnText = getSecretTextFromRampartService(stub,"apache");
// print it out
System.out.println("Return Text: " + returnText);
} catch (AxisFault e) {
e.printStackTrace();
} catch (RemoteException re){
re.printStackTrace();
}

}

private static String getSecretTextFromRampartService(MyRampartServiceStub stub, String string) throws RemoteException {
//create the request payload
GetSecretText req = new GetSecretText();
//set the parameter in the payload
req.setParam0(string);
//invoke service and get response
GetSecretTextResponse res = stub.getSecretText(req);
//return the response string.
return res.get_return();
}

private static Parameter getOutflowConfiguration() {

OutflowConfiguration ofc = new OutflowConfiguration();
//set the action item
ofc.setActionItems("Encrypt");
//set the encryption user
ofc.setEncryptionUser("service");
//set the property file; remember if the properties is not in classpath then it will not find this.
ofc.setEncryptionPropFile("client.properties");
// return the Parameter
return ofc.getProperty();
}

private static Parameter getInflowConfiguration() {
InflowConfiguration ifc = new InflowConfiguration();
//set the action item
ifc.setActionItems("Encrypt");
//set the password callback class
ifc.setPasswordCallbackClass("axis2.adb.sync.rampart.callbackhandler.PWCBHandler");
//set the property file; remember if the properties is not in classpath then it will not find this.
ifc.setDecryptionPropFile("client.properties");
//return the parameter
return ifc.getProperty();
}

}

Step 8: The output.
You will see the output:

Return Text: You have done a good choice to build an application

You can use the tcpmon tool to capture the XML exchanges. Then, you can observe the encrypted contents of the request and response messages.

NOTE: I am using the keystore's and the password callback handlers provided with rampart distribution for explaining this tutorial.

3 comments:

  1. Anonymous10:26 AM

    Nice tutorial, but any idea how to solve the error message:

    WSDoAllReceiver: Incoming message does not contain required Security header

    ?

    ReplyDelete
  2. Anonymous5:01 PM

    yeah that means the incoming request is not encrypted. you should implement the security in your client too.

    ReplyDelete
  3. Anonymous7:28 AM

    I tried rampart sample 5 example and got the error as
    [java] java.security.KeyStoreException: jks not found .....

    Can you help me know on this.

    ReplyDelete