Friday, October 21, 2011

Apache CXF Web Services - tomcat - Basic Auth

Introduction

Web Services provide excellent means for communicating information between a provider of information and community of interest. With increasing popularity of SOA based implementations development and usage of web services has increased more than ever. However, web service implementers often ignore securing these communications. There are several ways to secure web service communication (for both SOAP and REST flavors). In this article, we describe usage of http basic auth for securing web services. For demonstration, we are utilizing Apache CXF framework, but the concepts can be applied over any other web service frameworks.  Additional mechanisms such as the WS-Security (SAML assertions, certificates, user id – password tokens) and application of SSL at the app server level will be discussed in a later article.

Basic Authentication

There are two ways to turn http style basic authentication on, only one is enough to secure web services:
1.       At application server level – Tomcat 6.0.32 will be used for this
2.       At End point level – Spring 3.0.5 / apache CXF 2.4.2 / Tomcat 6.0.32 will be used for this

Application Server Level

1.       Add the following to server.xml under <Engine> tag
a.       <Realm className="org.apache.catalina.realm.MemoryRealm"/>
2.       Add the following to tomcat-users.xml
a.       <role rolename="role1"/>
b.      <user username="role1" password="tomcat" roles="role1"/>
3.       In your webservice deployment descriptor (web.xml) file, add the following:
a.         <security-constraint>
b.          <web-resource-collection>
c.             <web-resource-name>
d.              EmployeeAuthService
e.            </web-resource-name>
f.         
g.             <url-pattern> /* </url-pattern>
h.            <http-method> GET </http-method>
i.               <http-method> POST </http-method>
j.            </web-resource-collection>
k.           <auth-constraint>
l.             <!-- the same like in your tomcat-users.conf file -->
m.          <role-name>role1</role-name>
n.          </auth-constraint>
o.        </security-constraint>
p.        
q.        
r.            <login-config>
s.          <auth-method>BASIC</auth-method>
t.         </login-config>
u.       
v.       <security-role>
w.         <description> Test role </description>
x.           <role-name> role1 </role-name>
y.         </security-role>

End Point Level

1.       Create a SoapHeaderInterceptor  in apache CXF as listed in the code below
package com.company.auth.authorizer.basicauth;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor;
import org.apache.cxf.common.util.Base64Exception;
import org.apache.cxf.common.util.Base64Utility;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.springframework.beans.factory.annotation.Required;

public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {
               
                private Map<String, String> users;

                @Required
                public void setUsers(Map<String, String> users) {
                                System.out.println("Inside setUsers Map");
                                this.users = users;
                }

                @Override public void handleMessage(Message message) throws Fault {
                               
                                Map<String, List<String>> requestHeaders;
        requestHeaders = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);
       
        List<String> authorizationLines = requestHeaders.get("authorization");
        List<String> aAuthorizationLines = requestHeaders.get("Authorization");
       
        if (authorizationLines != null)
        {
                System.out.println("Yay 1");
        }
                               
        if (aAuthorizationLines != null)
        {
                System.out.println("Yay 2");
        }

        // This is supposed to be set by CXF, but i had to set it explicitly, so don't
        // need this anymore
                                AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
       
        // If the policy is not set, the user did not specify credentials
        // A 401 is sent to the client to indicate that authentication is required
        if (policy == null) {
            System.out.println ("User attempted to log in with no credentials");
            sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
            return;
        }
       
                System.out.println ("Logging in use: " + policy.getUserName());
       
        // Verify the password
        String realPassword = users.get(policy.getUserName());
        if (realPassword == null || !realPassword.equals(policy.getPassword())) {
                System.out.println ("Invalid username or password for user: " + policy.getUserName());
            sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
        }
    }

                private void sendErrorResponse(Message message, int responseCode) {
        Message outMessage = getOutMessage(message);
        outMessage.put(Message.RESPONSE_CODE, responseCode);
       
        // Set the response headers
        Map<String, List<String>> responseHeaders =
            (Map<String, List<String>>)message.get(Message.PROTOCOL_HEADERS);
        if (responseHeaders != null) {
            responseHeaders.put("WWW-Authenticate", Arrays.asList(new String[]{"Basic realm=realm"}));
            responseHeaders.put("Content-Length", Arrays.asList(new String[]{"0"}));
        }
        message.getInterceptorChain().abort();
        try {
            getConduit(message).prepare(outMessage);
            close(outMessage);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


                private Message getOutMessage(Message inMessage) {
        Exchange exchange = inMessage.getExchange();
        Message outMessage = exchange.getOutMessage();
        if (outMessage == null) {
            Endpoint endpoint = exchange.get(Endpoint.class);
            outMessage = endpoint.getBinding().createMessage();
            exchange.setOutMessage(outMessage);
        }
        outMessage.putAll(inMessage);
        return outMessage;
    }


                private Conduit getConduit(Message inMessage) throws IOException {
                               
                                System.out.println("inmessage is: " + inMessage);
        Exchange exchange = inMessage.getExchange();
        if (exchange == null )
                System.out.println("Exchnage is null");
       
        EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
        if (target == null )
                System.out.println("target is null");
       
        Conduit conduit =
            exchange.getDestination().getBackChannel(inMessage, null, target);
        exchange.setConduit(conduit);

        return conduit;
    }
   
    private void close(Message outMessage) throws IOException {
        OutputStream os = outMessage.getContent(OutputStream.class);
        os.flush();
        os.close();
    }

}

2.       Create Spring application configuration that is the standard for instantiating CXF web services as shown below:
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jaxws="http://cxf.apache.org/jaxws"
      xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
      xmlns:sec="http://cxf.apache.org/configuration/security"
      xmlns:cxf="http://cxf.apache.org/core"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
     
http://www.springframework.org/schema/beans/spring-beans.xsd

http://cxf.apache.org/jaxws

                              http://cxf.apache.org/schemas/jaxws.xsd
                             
                              http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
                             
                                    http://cxf.apache.org/transports/http/configuration
     
           http://cxf.apache.org/schemas/configuration/http-conf.xsd
          
           http://cxf.apache.org/configuration/security
          
                http://cxf.apache.org/schemas/configuration/security.xsd"
                             
                              >

  <import resource="classpath:META-INF/cxf/cxf.xml" />
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
     
  <bean id="BasicAuthAuthorizationInterceptor" class="com.company.auth.authorizer.basicauth.BasicAuthAuthorizationInterceptor">
  <property name="users">
        <map>
          <entry key="synapseadmin" value="welcome1"/>
        </map>
      </property>
      </bean>         

  <jaxws:endpoint id="auth"
                  implementor="com.company.auth.service.AuthServiceImpl"
                  address="/cxfAuth">
                  <jaxws:inInterceptors>
        <ref bean="BasicAuthAuthorizationInterceptor"/>
      </jaxws:inInterceptors>
    </jaxws:endpoint>
 
 
 
</beans>

Client

In both cases the client code for calling web service can be written as below. Please note that client methods using both CXF jars and regular java net URL calls is used to demonstrate that basic auth parameters can be passed from various clients:
package com.company.auth.service.client;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.handler.MessageContext;

import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;


import com.company.auth.service.Employee;
import com.company.auth.service.AuthService;
import com.company.auth.service.EmployeeNotFoundException;
import com.company.auth.service.EmployeeNotFoundException_Exception;

public final class Client {

    private Client() {
    }

    public static void main(String args[]) throws Exception {

                method1();
                // method2();
               

    }
   
    public static void method1() {
                String ws_url = "http://localhost:8080/EmployeeAuthService/services/cxfAuth";
                JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();

                // factory.getInInterceptors().add(new BasicAuthAuthorizationInterceptor());
                // factory.getOutInterceptors().add(new BasicAuthAuthorizationInterceptor());
               
               
                factory.setServiceClass(AuthService.class);
                factory.setAddress(ws_url);
               
                // factory.setWsdlLocation("file:c://dev//junk//EmployeeAuthService.wsdl");
                // factory.setWsdlLocation(ws_url);
                AuthService client = (AuthService) factory.create();
                org.apache.cxf.endpoint.Client proxy = ClientProxy.getClient(client);
               
                //add username and password for container authentication
        BindingProvider bp = (BindingProvider) client;
        bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "synapseadmin");
        bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "welcome1");
           
        /*
         *
        AuthorizationPolicy policy = new AuthorizationPolicy ();
        policy.setUserName("tomcat");
        policy.setPassword("tomcat");       
        bp.getRequestContext().put(AuthorizationPolicy.class.getName(), policy);
         */
       
        HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
        AuthorizationPolicy auth = conduit.getAuthorization();
        if ( null == auth ) {
                System.out.println (" auth policy is null");
                auth = new AuthorizationPolicy();
        }
        auth.setUserName( "synapseadmin" );
        auth.setPassword( "welcome1" );
       
                com.company.auth.service.Employee employee;
                                                try {
                                                                employee = client.getEmployee("0223938");
                                                System.out.println("Server said: " + employee.getLastName() + ", " + employee.getFirstName());
                                                } catch (EmployeeNotFoundException_Exception e) {
                                                                // TODO Auto-generated catch block
                                                                e.printStackTrace();
                                                }
                System.exit(0);
               
    }
   
    public static void method2() {
                // String WS_URL = "http://localhost:8080/EmployeeAuthService/services/cxfAuth?wsdl";
                String WS_URL = "file:c://dev//junk//EmployeeAuthService.wsdl";
                 
    
                URL url = null;
                                try {
                                                url = new URL(WS_URL);
                                } catch (MalformedURLException e) {
                                                // TODO Auto-generated catch block
                                                e.printStackTrace();
                                }
                               
//                            {http://service.auth.company.com/}corporateAuthService

            QName qname = new QName("http://service.auth.company.com/", "corporateAuthService");
    
            Service service = Service.create(url, qname);
            AuthService port = service.getPort(AuthService.class);
    
            //add username and password for container authentication
            BindingProvider bp = (BindingProvider) port;
           bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "synapseadmin");
           bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "welcome1");
    
                                                                try {
                                                                                System.out.println(port.getEmployee("0223938").getFirstName());
                                                                } catch (EmployeeNotFoundException_Exception e) {
                                                                                // TODO Auto-generated catch block
                                                                                e.printStackTrace();
                                                                }
    }

}

Server

The web services are developed using apache CXF and are listed below:

AuthService.java

package com.company.auth.service;

import javax.jws.WebService;
import javax.jws.WebParam;
import com.company.auth.bean.Employee;
import com.company.auth.service.exceptions.EmployeeNotFoundException;

@WebService
public interface AuthService {
    Employee getEmployee(@WebParam(name="gid") String gid) throws EmployeeNotFoundException;
    boolean isEmployee(@WebParam(name="emp") Employee emp);
}

AuthServiceImpl.java

package com.company.auth.service;

import javax.jws.WebService;

import com.company.auth.bean.Employee;
import com.company.auth.dao.EmployeeDAO;
import com.company.auth.service.exceptions.EmployeeNotFoundException;

@WebService(endpointInterface = "com.company.auth.service.AuthService", serviceName = "corporateAuthService")
public class AuthServiceImpl implements AuthService {

                public Employee getEmployee(String gid) throws EmployeeNotFoundException {
                                EmployeeDAO dao = new EmployeeDAO();
                                return dao.getEmployee(gid);
                }

                public boolean isEmployee(Employee emp) {
                                EmployeeDAO dao = new EmployeeDAO();
                                Employee emp2 = new Employee();
                               
                                try {
                                                emp2 = dao.getEmployee(emp.getGid());
                                                if (emp2.getFirstName() == emp.getFirstName())
                                                                return true;

                                } catch (EmployeeNotFoundException e) {
                                                // TODO Auto-generated catch block
                                                e.printStackTrace();
                                }
                                                               
                                return false;
                }

}

EmployeeDAO.java

package com.company.auth.dao;

import java.util.HashSet;
import java.util.Set;

import com.company.auth.bean.Employee;
import com.company.auth.service.exceptions.EmployeeNotFoundException;

public class EmployeeDAO {

                private Employee employee = new Employee();

                public  EmployeeDAO() {
                                employee.setFirstName("john");
                                employee.setLastName("smith");
                                Set privileges = new HashSet();
                                privileges.add("tea boy");
                                employee.setPrivileges(privileges);
                                employee.setGid("0223938");
                }

                public Employee getEmployee(String gid) throws EmployeeNotFoundException {
                                if (gid.equals("0223938"))
                                                return employee;
                                else
                                                throw new EmployeeNotFoundException("Employee is not found: " + gid);
                }

}

Employee.java

package com.company.auth.bean;

import java.io.Serializable;
import java.util.Set;

public class Employee implements Serializable {

                private static final long serialVersionUID = 1L;
                private String gid;
                private String lastName;
                private String firstName;
                private Set<String> privileges;

                public Employee() {}

                public Set<String> getPrivileges() {
                                return privileges;
                }

                public void setPrivileges(Set<String> privileges) {
                                this.privileges = privileges;
                }             

                public String getFirstName() {
                                return firstName;
                }

                public void setFirstName(String firstName) {
                                this.firstName = firstName;
                }

                public String getGid() {
                                return gid;
                }

                public void setGid(String gid) {
                                this.gid = gid;
                }

                public String getLastName() {
                                return lastName;
                }

                public void setLastName(String lastName) {
                                this.lastName = lastName;
                }

                public boolean isUserInRole(String role) {
                                if(privileges == null) { return false; }
                                else { return privileges.contains(role); }
                }

}

EmployeeNotFoundException.java

package com.company.auth.service.exceptions;

public class EmployeeNotFoundException extends Exception {
     
      /**
       *
       */
      private static final long serialVersionUID = 1L;

      public EmployeeNotFoundException(String string) {
            super(string);
      }

}

Tuesday, October 18, 2011

SOA Service Cost Estimation Modeling and Approach


Introduction

Cost estimation in Service Oriented Architecture (SOA) based software development efforts is more complex than other traditional non-distributed and silo-ed (web, desktop based rich internet applications etc) applications. In SOA projects, services are generally built using well-defined, reusable, and loosely coupled set of components. Services also comprise of logically grouped functionality and they are exposed to the rest of the world via a contract (Web Service WSDL or JMS Message Queues for example).

The loose coupling brings complexity when it comes to cost estimation as the project teams have to understand and establish all the component level relationships and dependencies involved. Because of the resulting uncertainty, traditional models such as COCOMO or Function Point Analysis cannot necessarily be sufficient for estimating costs of building SOA services. Cost estimation for developing building blocks of services must be done at the component level in order to truly reflect the activities of development involved. For combined components or choreographed services costs must be re-composed in a hierarchical form.

In this study, we propose estimation of costs for SOA services in a recursive fashion. For each component, a detailed work breakdown structure (WBS) that includes analysis, design, development, testing, deployment, integration, support and maintenance activities will be developed. Finally, we propose to include runtime and service level agreement (SLA) management costs if hosting is involved. Further, based on the timeline involved uncertainty in the requirements may be induced because of scope creep once the costs are calculated. For SOA services with longer requirement gathering periods and in whose cases where requirements are subject to change, we propose an additional factor increase in the time and cost of developing the project. However, this factor increase shall be determined directly by the operational managers that are experienced in running the operations within the organization. 

Estimation and Modeling Approach

Classification of SOA services is vital to any SOA governance at organization level as this helps to establish the types of services that the management is interested in investing. Among other types, few examples include data services, infrastructure services, and business services. Other technical categories include web services, file transfer services, identity and access services. Once the categories are finalized they are also governed (managed for changes or version controlled) in a corporate registry. While these types are applicable for implementation and development, we propose to use a different classification model for developing cost estimates. These categories include:
  1. New Services – A service that will be developed from scratch.
  2. Migrated Services – A service that already exists in the organization or its partners, but needs to be elevated to a different standard or technology stack.
  3. Integrated or Choreographed Service – A service that is integrated with yet another service in order to implement larger set of functionality without having to develop from scratch.

Service development may involve one or more aforementioned categories. Project teams (architects and business analysts) must undertake a discovery effort and determine relevant categories before developing a WBS for estimating costs. Once the determination is made, an effort must be put in place for determining all significant components. Every organization adopts a methodology for deriving a list of components. These methodologies could be used for this. We used Service Component Architecture (Apache Tuscany, IBM and Oracle SCA for various projects) reference modeling very effectively for this. A high level approach for achieving this is described in the graphic below:

Figure 1: SOA Service Breakdown Structure for Cost Estimation

Once a detailed list of components (BPEL Human Tasks, Web Services, JMS Queues, XSL Transformations, Security Access Control Lists etc) is developed we propose that project teams develop a high level or detailed (depending on the phase of the project) WBS as described below. The costs are primarily divided into two cost categories:
  1. SDLC costs for software implementation
  2. Hardware runtime costs (In this example a 5 year time period is used for deriving runtime operating costs and this needs to be adjusted by senior executive stakeholders on the project)

Tasks listed below can be categorized into the following:
  1. service discovery tasks – break down of components
  2. component design and architecture tasks
  3. component development tasks
  4. component migration tasks
  5. service integration tasks
  6. Other SDLC activities
 Figure 2: Cost Estimation Spreadsheet

Significant terms in the WBS can be elaborated as detailed below:
  • Component – List of components and other Software Development Lifecycle (SDLC) items that are identified through modeling efforts
  • High level Categorization for cost components – Software for capturing SDLC costs and Hardware for operational runtime costs
  • Tasks – Detailed listing of tasks that are required to develop each component
  • LOE – Level of effort either simply estimated by subject matter experts or from models such as the PERT
  • Risk – A factor that must be derived by the architect or the project manager based on knowns and unknowns.
  • Total LOE – Adjusted LOE per risk factors
  • Cost per Hour – Determined by senior executive managed of responsible organization
  • CPU Hours – Determined by the architect and software vendor for a given hardware, estimated payloads and SLAs
  • Costs per CPU Hour – Determined by the hosting provider based on their infrastructure management costs
  • Total Cost – Determined primarily by (Total LOE * Cost per Hour) for software SDLC and (CPU Hours * Costs per CPU Hour)
  • Overall Total Cost – Sum of all costs involved

Conclusion and Extended Analysis

Cost estimation of SOA services is complex because of the nature of loose coupling and complexity involved. We developed an approach to address the complexity by breaking down the list of activities involved that will incur significant costs. This breakdown approach should be inline with SOA itself without which significant deviation of costs may occur in estimation. Service component architecture and modeling is a prominent approach that is used by SOA practitioners for deriving components in a SOA service. In this study we propose to use this approach, however this can be supplemented and tailored using other approaches if any are adopted by the organization. In particular, tailoring should be applied for incorporating specific risks at the organization or group level if any. In addition to the methodology and development of work breakdown structure described above, we propose that a schedule with timeline and resources loaded be developed. This effort will produce a more reliable cost overall as it will include the resource management (matrix or vertical) aspects into the overall cost model.