Sunday, April 22, 2012

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

Overview
This is part ONE 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 secure the Web Service.

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

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.service</artifactId>  
   <version>1.0.0</version>  
   <packaging>jar</packaging>  
   <name>org.fazlan.secureservice.service</name>  
   <url>http://maven.apache.org</url>  
   <properties>  
     <org.apache.axis2.version>1.6.1</org.apache.axis2.version>  
   </properties>  
   <build>  
     <plugins>  
       <plugin>  
         <groupId>org.apache.axis2</groupId>  
         <artifactId>axis2-aar-maven-plugin</artifactId>  
         <version>${org.apache.axis2.version}</version>  
         <extensions>true</extensions>  
         <configuration>  
           <aarName>org.fazlan.secureservice.service</aarName>  
         </configuration>  
       </plugin>  
     </plugins>  
   </build>  
   <dependencies>  
     <dependency>  
       <groupId>org.apache.ws.security</groupId>  
       <artifactId>wss4j</artifactId>  
       <version>1.6.4</version>  
     </dependency>  
     <dependency>  
       <groupId>junit</groupId>  
       <artifactId>junit</artifactId>  
       <version>4.8.1</version>  
     </dependency>  
   </dependencies>  
 </project>  

Step 2: Defining the Service Implementation
Employee.java
 package org.fazlan.secureservice.service.beans;  
 public class Employee {  
   private long id;  
   private String firstName;  
   private String lastName;  
   public long getId() {  
     return id;  
   }  
   public void setId(long id) {  
     this.id = id;  
   }  
   public String getFirstName() {  
     return firstName;  
   }  
   public void setFirstName(String firstName) {  
     this.firstName = firstName;  
   }  
   public String getLastName() {  
     return lastName;  
   }  
   public void setLastName(String lastName) {  
     this.lastName = lastName;  
   }  
 }  

IService.java
 package org.fazlan.secureservice.service;  
 import java.util.List;  
 public interface IService<T> {  
   Long add(T t);  
   boolean update(T t);  
   boolean delete(Long id);  
   T get(Long id);  
   List<T> getAll();  
 }  

EmployeeService.java
 package org.fazlan.secureservice.service;  
 import org.fazlan.secureservice.service.beans.Employee;  
 import java.util.ArrayList;  
 import java.util.HashMap;  
 import java.util.List;  
 import java.util.Map;  
 public class EmployeeService implements IService<Employee> {  
   private static Long ID = 0L;  
   private final static Map<Long, Employee> EMPLOYEE_MAP = new HashMap<Long,Employee>();  
   public Long add(Employee employee) {  
     employee.setId(++ID);  
     update(employee);  
     return ID;  
   }  
   public boolean update(Employee employee) {  
     EMPLOYEE_MAP.put(employee.getId(), employee);  
     return true;  
   }  
   public boolean delete(Long id) {  
     return EMPLOYEE_MAP.remove(id) instanceof Employee;  
   }  
   public Employee get(Long id) {  
     return EMPLOYEE_MAP.get(id);  
   }  
   public List<Employee> getAll() {  
     return new ArrayList<Employee>(EMPLOYEE_MAP.values());  
   }  
 }  

Step 3: Writing the Password Callback Handler for the Service
Since we are going to use username token authentication, we need to write a password callback class that will be used by the Web service to authenticate username tokens. Although the passwords are hard coded in this code snippet, passwords can be retrieved from a database, LDAP server or any other medium by implementing the relevant password retrieval logic in the password callback class.

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

 package org.fazlan.secureservice.service.utils;  
 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(Callback cb : callbacks) {  
       WSPasswordCallback wscb = (WSPasswordCallback) cb;  
       if("apache".equals(wscb.getIdentifier())  
         && "password".equals(wscb.getPassword())) {  
         //hit LDAP here for authentication.  
         return;  
       }  
     }  
     throw new UnsupportedCallbackException(null, "Authentication check failed.");  
   }  
 }  

Step 4: The Security Policy
Following is the Security Policy we'll be using for this article. The policy contains two main security assertions,

Transport binding assertion        -  defines the requirement of using a SSL transport using the HTTPS transport token assertion.

Signed supporting token assertion  -  defines the requirement of a username token that should be integrity protected at the transport level.
You can find more information on constructing a security policy in this article: "Understanding WS-Security Policy Language"

 <wsp:Policy xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" 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>  
    <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">  
     <!--specifying the password callback handler class-->  
     <ramp:passwordCallbackClass>org.fazlan.secureservice.service.utils.PWCBHandler</ramp:passwordCallbackClass>  
    </ramp:RampartConfig>  
   </wsp:All>  
  </wsp:ExactlyOne>  
 </wsp:Policy>  

Step 5: Engaging Apache Rampart and Security Policy to the Service (WEB-INF/services.xml)
Now, we need to edit the service.xml to attach the above shown policy file. You can find more information on constructing a security policy in this article: "Applying policies at binding hierarchy in Apache Axis2"

 <serviceGroup>  
   <service name="EmployeeSecureService">  
     <description>  
       Weather Spring POJO Axis2 AAR deployment  
     </description>  
     <!--engaging security with rampart-->  
     <module ref="rampart"/>  
     <parameter name="ServiceClass" locked="false">org.fazlan.secureservice.service.EmployeeService</parameter>  
     <messageReceivers>  
       <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"  
                class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>  
       <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"  
                class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>  
     </messageReceivers>  
     <!--attaching the policy to the service-->  
     <wsp:PolicyAttachment xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">  
       <wsp:AppliesTo>  
         <policy-subject identifier="binding:soap11"/>  
         <policy-subject identifier="binding:soap12"/>  
       </wsp:AppliesTo>  
       <wsp:Policy xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"  
             xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" wsu:Id="UTOverTransport">  
          . . .  
       </wsp:Policy>  
     </wsp:PolicyAttachment>  
   </service>  
 </serviceGroup>  

Step 6: Building the Project
 mvn clean install

Before we can deploy the service on the Axis2 Engine, we need to do the followings.

Step 7: Deploying Rampart
Download the corresponding Apache Rampart version for your Apache Axis2 version.

Copying Rampart Modules
Rampart distribution contains two module files, rampart-1.3 and rahas-1.3.mar. Copy these module files to the "modules" directory of Axis2, that can be found in <TOMCAT_HOME>/webapps/axis2/WEB-INF/modules.

Copying Rampart Jars
Copy all the Apache Rampart dependency jars found under <RAMPART_HOME>/libs directory of the Rampart distribution to the "lib" directory of Axis2, which can found under <TOMCAT_HOME>/webapps/axis2/WEB-INF/lib.

You can verify whether Apache Rampart is successfully deployed by logging in to Axis2 as admin and using 'system components/available modules' option in admin Web console. Both "rampart" and "rahas" should be listed under available modules, after successfully deployed Rampart and Rahas modules.

Step 8: Configuring SSL in Tomcat (<TOMCAT_HOME>/conf/server.xml)
We need to enable HTTPS transport in Apache Tomcat to secure the transport. Lets modify the <TOMCAT_HOME>/conf/server.xml of Apache Tomcat to enable SSL configuration.
You can use the server.jks keytore which can be found in the source code zip file, as the keystore for this tutorial. Key store password is "wso2carbon". SSL configuration in the server should look like the one given below:

  <!-- Define a SSL HTTP/1.1 Connector on port 8443  
      This connector uses the JSSE configuration, when using APR, the   
      connector should be using the OpenSSL style configuration  
      described in the APR documentation -->  
   <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
         maxThreads="150" scheme="https" secure="true"  
         clientAuth="false" sslProtocol="TLS"   
         keystoreFile = "/home/fazlan/sw-installs/apache-tomcat-6.0.32/security/server.jks"  
         keystorePass="wso2carbon"/>  

Step 9: Configure HTTPS in Axis2 (<TOMCAT_HOME>/webapps/axis2/WEB-INF/conf/axis2.xml)
By default the HTTPS transport receiver on axis2 is disabled. We need to enable HTTPS transport receiver in Apache Axis2 to send messages in the HTTPS transport. Lets modify the <TOMCAT_HOME>/webapps/axis2/WEB-INF/conf/axis2.xml of Apache Axis2 deployment to enable HTTPS. Add the following entry:

 <transportReceiver name="https"  
             class="org.apache.axis2.transport.http.SimpleHTTPServer">  
     <parameter name="port">8443</parameter>  
 </transportReceiver>  

NOTE: The port is the HTTPS port of the TOMCAT server we configured previously.

Step 10: Deploying the Service 
Now, we have to deploy this service in the Axis2 server. Create a service archive org.fazlan.secureservice.service.aar and drop it into the services directory which can found at: TOMCAT_HOME/webapps/axis2/WEB-INF/services You can access the service via http://localhost:8080/axis2/services/EmployeeSecureService?wsdl

And you should see the policy attached to the WSDL.

Summary:
This article looked at how you can secure your web service using Apache Rampart using username authentication.

UPDATED SECTION - Upgrading the example to run with Apache Axis2 1.6.2

pom.xml - the plugin configuration
 <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.service</artifactId>  
   <version>1.0.0</version>  
   <packaging>jar</packaging>  
   <name>org.fazlan.secureservice.service</name>  
   <url>http://maven.apache.org</url>  
   <properties>  
     <org.apache.axis2.version>1.6.2</org.apache.axis2.version>  
   </properties>  
   <build>  
     <plugins>  
       <plugin>  
         <groupId>org.apache.axis2</groupId>  
         <artifactId>axis2-aar-maven-plugin</artifactId>  
         <version>${org.apache.axis2.version}</version>  
         <extensions>true</extensions>  
         <configuration>  
         <!-- Set true if you want Depending Jar to be included into AAR file-->  
         <includeDependencies>true</includeDependencies>  
           <aarName>${pom.name}</aarName>  
         </configuration>  
         <executions>  
           <execution>  
             <phase>prepare-package</phase>  
             <goals>  
               <goal>aar</goal>  
             </goals>  
           </execution>  
         </executions>  
       </plugin>  
     </plugins>  
   </build>  
   <dependencies>  
     <dependency>  
       <groupId>org.apache.ws.security</groupId>  
       <artifactId>wss4j</artifactId>  
       <version>1.6.4</version>  
     </dependency>  
     <dependency>  
       <groupId>junit</groupId>  
       <artifactId>junit</artifactId>  
       <version>4.8.1</version>  
     </dependency>  
   </dependencies>  
 </project>  

Step 9: Axis2 Receiver transport configuration


  <transportReceiver name="http"  
             class="org.apache.axis2.transport.http.AxisServletListener"/>  
   <transportReceiver name="https" class="org.apache.axis2.transport.http.AxisServletListener">  
            <parameter name="port">8443</parameter>  
    </transportReceiver>  

With Axis2 1.6.2, the transportReceiver configuration has changed compared to versions < 1.6.2. You need to update the  <TOMCAT_HOME>/webapps/axis2/WEB-INF/conf/axis2.xml as above.

Please refer to: http://axis.apache.org/axis2/java/core/docs/servlet-transport.html for more details.

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

Sample code for this post can be found here.

3 comments:

  1. Hi I am having trouble getting this to work with Tomcat as when I add the HTTPS transport receiver to axis2.xml, Tomcat fails to launch with a JVM_Bind exception. The only difference I can see is that I am using version 1.6.2 not 1.6.1. Do you happpen to have any ideas?

    Many thanks,

    Nick

    ReplyDelete
  2. Hi Nick, how's it going?

    Well, yes! there have been some slight configuration changes in 1.6.2 compared to 1.6.1.

    I have updated the post by adding a section, which highlights the steps you require to get the example working on Axis2 1.6.2. I hope this would help you.

    Enjoy! let me know if you run into trouble.

    Cheers,
    Fazlan

    ReplyDelete
  3. Hi, Howabout calling multiple webservices? Don't we need to use some security token instead of authenticating each time? Thanks/Francis

    ReplyDelete