Sunday, April 22, 2012

WS Security with Apache Rampart - Part 3/3 (Securing the Client)

Overview
This is part THREE and the last part of an article series that looks at how you can engage Apache Rampart to secure Web Service communication using Username Authentication. This post will focus on how we can access your secured Web Service using the secured client.

Also Refer to,
WS Security with Apache Rampart - Part 1/3 (Securing the Service) and,
WS Security with Apache Rampart - Part 2/3 (Generating Stub for the Service)

Sample code for this post can be found here.

This article applies to:
Version 1.6.0
Apache Axis2 1.6.0 and Apache Rampart 1.6.0

Version 1.6.1
Apache Axis2 1.6.1 and Apache Rampart 1.6.1

Project Structure: Following is the structure I have used for this post.


Step 1: Maven Dependencies (pom.xml)
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
   <modelVersion>4.0.0</modelVersion>  
   <groupId>org.fazlan</groupId>  
   <artifactId>org.fazlan.secureservice.client</artifactId>  
   <version>1.0.0</version>  
   <packaging>jar</packaging>  
   <name>org.fazlan.secureservice.client</name>  
   <url>http://maven.apache.org</url>  
   <properties>  
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
     <axis2.version>1.6.1</axis2.version>  
     <rampart.version>1.6.1</rampart.version>  
   </properties>  
   <dependencies>  
     <dependency>  
       <groupId>org.fazlan</groupId>  
       <artifactId>org.fazlan.secureservice.stub</artifactId>  
       <version>1.0.0</version>  
     </dependency>  
     <!--axis2 dependencies-->  
     <dependency>  
       <groupId>org.apache.axis2</groupId>  
       <artifactId>axis2</artifactId>  
       <version>${axis2.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>org.apache.axis2</groupId>  
       <artifactId>axis2-transport-local</artifactId>  
       <version>${axis2.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>org.apache.axis2</groupId>  
       <artifactId>axis2-transport-http</artifactId>  
       <version>${axis2.version}</version>  
     </dependency>  
     <!--rampart-->  
     <dependency>  
       <groupId>org.apache.rampart</groupId>  
       <artifactId>rampart-core</artifactId>  
       <version>${rampart.version}</version>  
     </dependency>  
     <!--rampart dependencies-->  
     <dependency>  
       <groupId>org.apache.neethi</groupId>  
       <artifactId>neethi</artifactId>  
       <version>3.0.1</version>  
     </dependency>  
     <dependency>  
       <groupId>junit</groupId>  
       <artifactId>junit</artifactId>  
       <version>3.8.1</version>  
       <scope>test</scope>  
     </dependency>  
   </dependencies>  
 </project>  


Step 2: Username Token Policy File
To communicate via HTTPS, we need to generate the SOAP security header at the client end using the policy used by the WS. This policy is extracted out from the service exposed via http://localhost:8080/axis2/services/EmployeeSecureService?wsdl
Lets save this policy as a resource in the project.
e.i: src/main/resources/policies/employeeservice.xml

 <wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"  
       xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"  
       wsu:Id="UTOverTransport">  
   <wsp:ExactlyOne>  
     <wsp:All>  
       <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">  
         <wsp:Policy>  
           <sp:TransportToken>  
             <wsp:Policy>  
               <sp:HttpsToken RequireClientCertificate="false"/>  
             </wsp:Policy>  
           </sp:TransportToken>  
           <sp:AlgorithmSuite>  
             <wsp:Policy>  
               <sp:Basic256/>  
             </wsp:Policy>  
           </sp:AlgorithmSuite>  
           <sp:Layout>  
             <wsp:Policy>  
               <sp:Lax/>  
             </wsp:Policy>  
           </sp:Layout>  
           <sp:IncludeTimestamp/>  
         </wsp:Policy>  
       </sp:TransportBinding>  
       <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">  
         <wsp:Policy>  
           <sp:UsernameToken  
               sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"/>  
         </wsp:Policy>  
       </sp:SignedSupportingTokens>  
     </wsp:All>  
   </wsp:ExactlyOne>  
 </wsp:Policy>  


Step 3: Writing the Password Callback Handler for the Client
Since we are going to use username token authentication, we need to write a password callback class that will be used by the client to set the authenticate username tokens.

More about writing callback handlers can be found in this article: "Password Callback Handlers Explained"

 package org.fazlan.secureservice.client.util;  
 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;  
 import java.util.Map;  
 public class ClientPWCBHandler implements CallbackHandler {  
   private String password;  
   private Map properties;  
   public ClientPWCBHandler() {  
   }  
   public ClientPWCBHandler(String password, Map properties) {  
     this.password = password;  
     this.properties = properties;  
   }  
   public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {  
     WSPasswordCallback pwcb = (WSPasswordCallback) callbacks[0];  
     int usage = pwcb.getUsage();  
     if (usage == WSPasswordCallback.USERNAME_TOKEN) {  
       // user has provided the username and the password  
       if (password != null) {  
         pwcb.setPassword(password);  
       } else {  
         // user has provided only the username, get password from LDAP  
         pwcb.setPassword(LDAPUtil.getPassword(pwcb.getIdentifier()));  
       }  
     } else if (usage == WSPasswordCallback.SIGNATURE || usage == WSPasswordCallback.DECRYPT) {  
       // handle signature and decrypt type  
     }  
   }  
 }  

Step 4: Writing a Security Handler Class to Enforce Security Policy
The following class associates the policy and the above implemented CallbackHandler implementation to the underlying Axis2 service client.

 package org.fazlan.secureservice.client.util;  
 import org.apache.axiom.om.impl.builder.StAXOMBuilder;  
 import org.apache.axis2.client.Options;  
 import org.apache.axis2.client.ServiceClient;  
 import org.apache.neethi.Policy;  
 import org.apache.neethi.PolicyEngine;  
 import org.apache.rampart.RampartMessageData;  
 import org.apache.rampart.policy.model.RampartConfig;  
 import org.apache.ws.security.handler.WSHandlerConstants;  
 import javax.security.auth.callback.CallbackHandler;  
 import java.io.InputStream;  
 public class SecurityHandler {  
   private String user;  
   private CallbackHandler callbackHandler;  
   private ServiceClient serviceClient;  
   private InputStream policyStream;  
   public SecurityHandler(String user, ServiceClient serviceClient, CallbackHandler callbackHandler, InputStream policy) {  
     this.user = user;  
     this.serviceClient = serviceClient;  
     this.callbackHandler = callbackHandler;  
     this.policyStream = policy;  
   }  
   public void engage() {  
     try {  
       RampartConfig rc = new RampartConfig();  
       rc.setUser(user);  
       Policy policy = PolicyEngine.getPolicy(new StAXOMBuilder(policyStream).getDocumentElement());  
       policy.addAssertion(rc);  
       serviceClient.engageModule("rampart");  
       Options options = serviceClient.getOptions();  
       options.setProperty(WSHandlerConstants.PW_CALLBACK_REF, callbackHandler);  
       options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, policy);  
     } catch (Exception e) {  
       throw new RuntimeException(e);  
     }  
   }  
 }  

Step 5: Writing the Secure Service Client
Now, lets write the client class that wraps the stub for accessing the web service. Also, we are engaging security for the client.

The Client has TWO constructors,
1st:
That accepts ONLY the username - Internally, the password is set against the username from LDAP or other storage.
2nd:
Client provides both username as well as the password.

 package org.fazlan.secureservice.client;  
 import org.apache.axis2.AxisFault;  
 import org.apache.axis2.context.ConfigurationContext;  
 import org.fazlan.secureservice.client.util.ClientPWCBHandler;  
 import org.fazlan.secureservice.client.util.SecurityHandler;  
 import org.fazlan.secureservice.service.beans.xsd.Employee;  
 import org.fazlan.secureservice.stub.EmployeeSecureService;  
 import org.fazlan.secureservice.stub.EmployeeSecureServiceStub;  
 import java.rmi.RemoteException;  
 //EmployeeSecureService interface is generated by the wsdl2java code by AXIS2  
 public class EmployeeSecureServiceClient implements EmployeeSecureService {  
   private EmployeeSecureServiceStub stub;  
   public EmployeeSecureServiceClient(ConfigurationContext ctx, String epr, String username) {  
     this(ctx, epr, username, null);  
   }  
   public EmployeeSecureServiceClient(ConfigurationContext ctx, String epr, String username, String password) {  
     try {  
       stub = new EmployeeSecureServiceStub(ctx, epr);  
       new SecurityHandler(username, stub._getServiceClient(),  
           new ClientPWCBHandler(password, null),  
           getClass().getResourceAsStream("/policies/employeeservice.xml")).engage();  
     } catch (AxisFault afe) {  
       throw new RuntimeException(afe);  
     }  
   }  
   public long add(Employee employee) throws RemoteException {  
     return stub.add(employee);  
   }  
   public boolean update(Employee employee) throws RemoteException {  
     return stub.update(employee);  
   }  
   public Employee get(long id) throws RemoteException {  
     return stub.get(id);  
   }  
   public boolean delete(long id) throws RemoteException {  
     return stub.delete(id);  
   }  
   public Employee[] getAll() throws RemoteException {  
     return stub.getAll();  
   }  
 }  

Step 6: Building the Project
 mvn clean install

Step 7: Writing an Application to Consume the Web Service the Project
Download the corresponding Apache Rampart version for your Apache Axis2 version.

 package org.fazlan.secureservice.client;  
 import org.apache.axis2.context.ConfigurationContext;  
 import org.apache.axis2.context.ConfigurationContextFactory;  
 import org.fazlan.secureservice.service.beans.xsd.Employee;  
 public class Application {  
   private final static String EMPLOYEE_SERVICE_EPR = "https://localhost:8443/axis2/services/EmployeeSecureService/";  
   public static void main(String[] args) throws Exception {  
     System.setProperty("javax.net.ssl.trustStore", "/home/fazlan/sw-installs/apache-tomcat-6.0.32/security/server.jks");  
     System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");  
     ConfigurationContext ctx = ConfigurationContextFactory.  
         createConfigurationContextFromFileSystem("/home/fazlan/sw-installs/client-repo", null);  
     EmployeeSecureServiceClient client;  
     // get password from LDAP  
 //    client = new EmployeeSecureServiceClient(ctx, EMPLOYEE_SERVICE_EPR, "apache");  
     // user provides both username and password  
     client = new EmployeeSecureServiceClient(ctx, EMPLOYEE_SERVICE_EPR, "apache", "password");  
     Employee employee = new Employee();  
     employee.setFirstName("FN");  
     employee.setLastName("LN");  
     long result = client.add(employee);  
     System.out.println("id = " + result);  
   }  
 }  


Making a Secured Handshake with the Server.
Since, we are using HTTPS, we need to enable SSL communication, the following two lines achieve this.
 System.setProperty("javax.net.ssl.trustStore", "/home/fazlan/sw-installs/apache-tomcat-6.0.32/security/wso2carbon.jks");  
 System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");  

Also, we need to provide the path to the repository configuration context for the client. This should contain the Rampart modules directory: rampart-1.6.1.mar and rahas-1.6.1.mar.

 ConfigurationContext ctx = ConfigurationContextFactory.  
         createConfigurationContextFromFileSystem("/home/fazlan/sw-installs/client-repo", null);  

e.i: /home/fazlan/sw-installs/client-repo
                                                          - modules
                                                               - rampart-1.6.1.mar
                                                               - rahas-1.6.1.mar

Now, everything is in place, and you can run the application program.

Summary:
This article looked at how you can securely access your secured web service with Apache Rampart using username authentication.


Also Refer to,
WS Security with Apache Rampart - Part 1/3 (Securing the Service) and,
WS Security with Apache Rampart - Part 2/3 (Generating Stub for the Service)

Sample code for this post can be found here.

1 comment:

  1. Hi Fazlan,
    Have you tried this with weblogic 11g?

    Thanks

    ReplyDelete