Friday, August 12, 2011

Webservice Spring WS-Security

My previous blog I explain some SOA concept. Now  In this blog I am jumping to some practical and explaining how to setup some basic web services with Spring framework and how to implement some security with web Services. To run this example  you need JDK 1.5+ and spring framework 3.0+

I created some basic configuration. Here are list.
1. applicationContext-service.xml -- It has some basic configuration of web service in spring
2. HelloWorldWS.java -- This class is exposing webservice
3. HelloWorldServiceHandler.java -- This class is monitoring incoming request and outgoing message. Here we implement WS-security.
4. HelloWorldManager.java -- This class  is interface for business implementation.
5. HelloWorldManagerImpl.java -- This class has business implementation.

Now lets start how I implemented this web service. here are codes.

1. applicationContext-service.xml --

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:wss="http://jax-ws.dev.java.net/spring/servlet"
       xmlns:ws="http://jax-ws.dev.java.net/spring/core"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://jax-ws.dev.java.net/spring/core
            http://jax-ws.dev.java.net/spring/core.xsd
            http://jax-ws.dev.java.net/spring/servlet
            http://jax-ws.dev.java.net/spring/servlet.xsd">

    <bean id="constantMap" class="java.util.HashMap" />

    <ws:service id="SOAPservice" bean="#helloWorldWS">
            <ws:handlers>
                <ref bean="helloWorldHandler" />
            </ws:handlers>
        </ws:service>

        <wss:bindings id="jaxWs">
            <wss:bindings>
                <wss:binding url="/webservices/HelloWorldService">
                    <wss:service>
                        <ref bean="SOAPservice"/>
                    </wss:service>
                </wss:binding>
            </wss:bindings>
        </wss:bindings>

        <bean id="helloWorldHandler" class="com.worldofsoa.service.handler.HelloWorldServiceHandler">
            <property name="constantMap" ref="constantMap" />
        </bean>

        <!-- Injecting DAO Object -->
        <bean id="helloWorldManager">
            <property name="target">
             <bean class="com.worldofsoa.service.impl.HelloWorldManagerImpl">
                <property name="userDAO"><ref bean="userDAO"/></property>
             </bean>
            </property>
        </bean>
        <bean id="helloWorldWS" class="com.worldofsoa.service.impl.HelloWorldWS">
            <property name="helloWorldManager"><ref bean="helloWorldManager"/></property>
    </bean>
  </beans>

2. HelloWorldWS.java

package com.worldofsoa.service;

import javax.annotation.Resource;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.ws.WebServiceContext;

import com.worldofsoa.service.HelloWorldManager;

import com.worldofsoa.xml.schema.PeopleInfoRequest;
import com.worldofsoa.xml.schema.PeopleInfoResponse;

@WebService (targetNamespace="http://www.worldofsoa.com/helloWorldService",serviceName = "HelloWorldService")
@SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL, parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)
public class HelloWorldWS {

    private HelloWorldManager helloWorldManager;

     @Resource
        WebServiceContext context;

    @WebMethod(exclude=true)
    public void setHelloWorldManager(HelloWorldManager helloWorldManager) {
        this.helloWorldManager = helloWorldManager;
    }

    @WebMethod(operationName = "getPeopleInfo")
    @WebResult(name = "PeopleInfo", partName = "PeopleInfo")
    public PeopleInfoResponse getPeopleInfo(@WebParam(name = "PeopleInfoRequest", partName = "PeopleInfoRequest",targetNamespace="http://www.worldofsoa.com/helloWorldService") PeopleInfoRequest peopleInfoRequest) throws Exception {
        return helloWorldManager.getPeopleInfo(peopleInfoRequest);
    }

}


3. HelloWorldServiceHandler.java


package com.worldofsoa.service.handler;

import java.io.ByteArrayOutputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.soap.Name;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HelloWorldServiceHandler implements SOAPHandler<SOAPMessageContext> {

    private static final Log log = LogFactory.getLog(HelloWorldServiceHandler.class);

    /** The Constant USERNAME_TOKEN_STRING. */
    private static final String USERNAME_TOKEN_STRING = "UsernameToken";

    /** The Constant USERNAME_STRING. */
    private static final String USERNAME_STRING = "Username";


    /** The Constant PASSWORD_STRING. */
    private static final String PASSWORD_STRING = "Password";

    private Map<String, String> constantMap;

    public Set<QName> getHeaders() {
        return null;
    }

    public void close(MessageContext context) {
    }

    public boolean handleFault(SOAPMessageContext context) {

        logToSystemOut(context);
        return true;
    }

    public boolean handleMessage(SOAPMessageContext context) {
        Boolean outboundProperty = (Boolean) context
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        boolean isSoapRequestHandle = false;

        if (outboundProperty.booleanValue()) {
            isSoapRequestHandle = true;

            /* ************************************************************************
             *    If you are manupulating outgoing header then you need to add this code
             *
             **************************************************************************
             * try { SOAPMessage message = context.getMessage();
             *
             * SOAPPart sp = message.getSOAPPart();
             *
             * SOAPEnvelope envelope = sp.getEnvelope();
             *
             * SOAPHeader header = envelope.addHeader();
             *
             * SOAPElement security = header.addChildElement("Security", "wsse",
             * "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
             * );
             *
             * SOAPElement usernameToken =
             * security.addChildElement("UsernameToken", "wsse");
             * usernameToken.addAttribute(new QName("xmlns:wsu"),
             * "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
             * );
             *
             * SOAPElement username = usernameToken.addChildElement("Username",
             * "wsse"); username.addTextNode("TestUser");
             *
             * SOAPElement password = usernameToken.addChildElement("Password",
             * "wsse"); password.setAttribute("Type",
             * "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"
             * ); password.addTextNode("TestPassword");
             *
             * //Print out the outbound SOAP message to System.out
             * message.writeTo(System.out); System.out.println("");
             *
             *
             *
             * }catch (Exception e) { e.printStackTrace();
             *
             * }
             */
        } else {
            try {

                SOAPMessage message = context.getMessage();
                SOAPPart sp = message.getSOAPPart();

                SOAPEnvelope envelope = sp.getEnvelope();

                SOAPHeader sh = envelope.getHeader();
                isSoapRequestHandle = processSOAPHeader(sh);

                message.writeTo(System.out);
                System.out.println("");

                if (!isSoapRequestHandle) {

                    SOAPElement errorMessage = sh.addChildElement(
                            "errorMessage", "error",
                            "http://worldofsoa.com/helloService/error");
                    SOAPElement error = errorMessage.addChildElement("error");
                    error.addTextNode("Authentication Failed !!!");

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        logToSystemOut(context);
        return isSoapRequestHandle;
    }

    private void logToSystemOut(SOAPMessageContext smc) {
        Boolean outboundProperty = (Boolean) smc
                .get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty.booleanValue()) {
            log.debug("\nOutgoing message:");
        } else {
            log.debug("\nIncoming message:");
        }

        SOAPMessage message = smc.getMessage();
        try {
            log.debug(handleRequestAndResponse(message));
        } catch (Exception e) {
            System.out.println("Exception in handler: " + e);
        }
    }

    private String handleRequestAndResponse(SOAPMessage msg) {
        ByteArrayOutputStream obj = new ByteArrayOutputStream();
        try {
            msg.writeTo(obj);
            return obj.toString();
        } catch (Exception ex) {
            obj = null;
            ex.printStackTrace();
        }
        return "";
    }


    private boolean processSOAPHeader(SOAPHeader sh) {
        boolean authenticated = false;

        // look for authentication header element inside the HEADER block
        Iterator childElems = sh.getChildElements();
        SOAPElement child = extractUserNameInfo(childElems);
        if (child != null) {
            // call method to perform authentication
            authenticated = authenticateRequest(child);
        }
        return authenticated;
    }

    private SOAPElement extractUserNameInfo(Iterator childElems) {
        SOAPElement child = null;
        Name sName;
        // iterate through child elements
        while (childElems.hasNext()) {
            Object elem = childElems.next();

            if (elem instanceof SOAPElement) {
                // Get child element and its name
                child = (SOAPElement) elem;
                sName = child.getElementName();
                // Check whether there is a UserNameToken element
                if (!USERNAME_TOKEN_STRING.equalsIgnoreCase(sName
                        .getLocalName())) {

                    if (child.getChildElements().hasNext()) { // TODO check
                                                                // logic
                        return extractUserNameInfo(child.getChildElements());
                    }
                }
            }
        }

        return child;
    }

    private boolean authenticateRequest(SOAPElement element) {

        boolean authenticated = false;

        // variable for user name and password
        String userName = null;
        String password = null;
        Name sName;

        // get an iterator on child elements of SOAP element
        Iterator childElems = element.getChildElements();

        SOAPElement child;
        // loop through child elements

        while (childElems.hasNext()) {
            // get next child element
            Object elem = childElems.next();

            if (elem instanceof SOAPElement) {
                child = (SOAPElement) elem;

                // get the name of SOAP element
                sName = child.getElementName();

                // get the value of username element
                if (USERNAME_STRING.equalsIgnoreCase(sName.getLocalName())) {
                    userName = child.getValue();
                } else if (PASSWORD_STRING.equalsIgnoreCase(sName
                        .getLocalName())) {
                    // get the value of password element
                    password = child.getValue();
                }

                if (userName != null && password != null) {

                    // ClientLoginModule.login("WEBSERVICE" + "^" + userName,
                    // password);
                    // return true;

                    authenticated = getUserAuth(userName, password);

                    break;
                }

            }
        }

        if (userName == null || password == null) {
            log.warn("Username or password is empty. userName : [" + userName
                    + "], password : [" + password + "]");
        }

        return authenticated;

    }

    public Map<String, String> getConstantMap() {
        return constantMap;
    }

    public void setConstantMap(Map<String, String> constantMap) {
        this.constantMap = constantMap;
    }

    private boolean getUserAuth(String username, String password) {

        //Constant Map populated with database information
        String dbUserId = (String) constantMap.get("useIdFormDatabase");
        String dbPassword = (String) constantMap
                .get("passwordFormDatabase");

        if (dbUserId.equalsIgnoreCase(username) && dbPassword.equals(password)) {
            return true;
        }

        return false;
    }
}

4. HelloWorldManager.java --


package com.worldofsoa.service;


import com.worldofsoa.xml.schema.PeopleInfoRequest;
import com.worldofsoa.xml.schema.PeopleInfoResponse;

public interface HelloWorldManager {

    public PeopleInfoResponse getPeopleInfo(PeopleInfoRequest peopleInfoRequest) throws Exception;

}

5. HelloWorldManagerImpl.java --

package com.worldofsoa.service.impl;


import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;

import com.worldofsoa.dao.UserDAO;
import com.worldofsoa.model.PeopleVO;
import com.worldofsoa.service.HelloWorldManager;

import com.worldofsoa.xml.schema.ObjectFactory;
import com.worldofsoa.xml.schema.Person;
import com.worldofsoa.xml.schema.PeopleInfoRequest;
import com.worldofsoa.xml.schema.PeopleInfoResponse;



public class HelloWorldManagerImpl implements HelloWorldManager {


    private UserDAO userDAO;




    public PeopleInfoResponse getPeopleInfo(PeopleInfoRequest peopleInfoRequest) throws Exception {

        ObjectFactory factory = new ObjectFactory();
        PeopleInfoResponse peopleInfoResponse = factory.createPeopleInfoResponse();

        PeopleVO peopleVO = new PeopleVO();
        peopleVO.setPeopleId(peopleInfoRequest.getPeopleId());

        peopleVO = userDAO.getPeopleInfo(peopleVO);

        Person person = factory.createPerson();

        person.setFirstName(peopleVO.getFirstName());
        person.setLastName(peopleVO.getLastName());
        person.setType(peopleVO.getPeopleType());
        person.setCreateDate(getXmlDate(peopleVO.getCreateDate()));

        peopleInfoResponse.setPerson(person);

        peopleInfoResponse.setMessage(SUCCESS_MESSAGE);
        peopleInfoResponse.setSuccess(true);

        return peopleInfoResponse;

    }

    private XMLGregorianCalendar getXmlDate(Date date) {
        try {
            GregorianCalendar cal = new GregorianCalendar();
            cal.setTime(date);
            XMLGregorianCalendar gc = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
            gc.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
            gc.setTime(DatatypeConstants.FIELD_UNDEFINED,
                    DatatypeConstants.FIELD_UNDEFINED,
                    DatatypeConstants.FIELD_UNDEFINED);
            return gc;
        } catch (DatatypeConfigurationException e) {
            log.warn("Cannot format expxiration date: " + date);
            return null;
        }catch(Exception ex){
            log.warn("Cannot format expxiration date: " + ex);
            return null;
        }
    }

    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

}