Wednesday, June 2, 2010

JAX-WS Webservice secured With XWS-Security (plain text password)

Hello,
We will learn webservice with security by a example, which fetch data and save data using usernametoken security. For this we have some requirements, but you can use other server or other build tool, it may require little more efforts.

Requirements:

• jdk1.6, glassfish, maven 2

• This is tested on jdk1.6.0_18, glassfish v2-ur1, apache-maven-2.0.9.

Here, some steps to follow...

• Create "Webservice" folder.

• Create "work" under "Webservice".

• Create below files under "work".

• person.xsd

First we have to determine what we want to send and what we want back from webservice so for that purpose we have to define a xsd file.
The fetch request contains only id and we will get detail information.
The save request carries detail of person and we will get response message of success or failure.

<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.samplews.com/person/beans"
    elementFormDefault="qualified" xmlns:tns="http://www.samplews.com/person/beans">
    <xsd:complexType name="PersonDetail">
        <xsd:sequence>
            <xsd:element name="Id" type="xsd:int"/>
            <xsd:element name="Name" type="xsd:string"/>
            <xsd:element name="Address" type="tns:Address"/>
            <xsd:element name="PhoneNo" type="xsd:decimal"/>
            <xsd:element name="Email" type="xsd:string"/>
            <xsd:element name="BirthDate" type="xsd:date"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="PersonSaveResponse">
        <xsd:sequence>
            <xsd:element name="Status" type="xsd:boolean"/>
            <xsd:element name="Message" type="xsd:string"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="PersonFetchRequest">
        <xsd:sequence>
            <xsd:element name="Id" type="xsd:int"/>
        </xsd:sequence>
    </xsd:complexType>
    <xsd:complexType name="Address">
        <xsd:sequence>
            <xsd:element name="Line1" type="xsd:string"/>
            <xsd:element name="Line2" type="xsd:string"/>
            <xsd:element name="Line3" type="xsd:string"/>
            <xsd:element name="City" type="xsd:string"/>
            <xsd:element name="Pincode" type="xsd:decimal"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:schema>

• person.xjb

The xjb file specifies in which package the generated java classes resides for given targetnamespace of xsd specified in schema location.

<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0">
    <jaxb:bindings schemaLocation="person.xsd" node="/xsd:schema">
        <jaxb:schemaBindings>
            <jaxb:package name="com.samplews.person.beans"/>
        </jaxb:schemaBindings>
    </jaxb:bindings>
</jaxb:bindings>

• web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>

• handlers.xml

This file contains information of handler like soap handler and logical handler to intercept request and response of webservice.
We will use only one soap handler to secure webservice.

<?xml version="1.0" encoding="UTF-8"?>
<jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">
    <jws:handler-chain>
        <jws:handler>
            <jws:handler-class>com.samplews.person.handler.SecuritySOAPHandler</jws:handler-class>
        </jws:handler>
    </jws:handler-chain>
</jws:handler-chains>

• security-config.xml

This file specifies which type of security we requires like username token, signed message, encrypted message, etc.
We will use username token with plain text password security.

<?xml version="1.0" encoding="UTF-8"?>
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config" dumpMessages="true">
    <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
</xwss:SecurityConfiguration>

• Navigate to "Webservice" on command prompt.

• Execute the command. This will create a maven project under "Webservice".

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.samplews.person -DartifactId=personwsapp

• Replace generated pom.xml with this.

• 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>com.samplews.person</groupId>
    <artifactId>personwsapp</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>
    <name>personwsapp</name>
    <url>http://maven.apache.org</url>
    <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.wss</groupId>
            <artifactId>xws-security</artifactId>
            <version>3.0</version>
        </dependency>
    </dependencies>  
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                       <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.0</version>
                <configuration>
                    <webXml>../work/web.xml</webXml>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <targetPath>com/samplews/person/service</targetPath>
                <directory>../work</directory>
                <includes>
                    <include>handlers.xml</include>
                </includes>
            </resource>
            <resource>
                <targetPath>com/samplews/person/handler</targetPath>
                <directory>../work</directory>            
                <includes>
                    <include>security-config.xml</include>
                </includes>
            </resource>
        </resources>
    </build>    
</project>

• Navigate to "work" on command prompt.

• Execute the command.

This command generates java classes according to xsd and configuration of xjb which we will use to pass into and to return from webservice. This will generates beans classes packaged in "com.samplews.person".

xjc person.xsd -b person.xjb -d ..\personwsapp\src\main\java

• Put this source files under appropriate packages under "personwsapp\src\main\java".

This is the webservice class.

• PersonWS.java

package com.samplews.person.service;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.HandlerChain;
import java.math.BigDecimal;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.DatatypeConfigurationException;
import com.samplews.person.beans.*;


@WebService(name = "PersonWS", serviceName = "PersonWSService", portName = "PersonWSPort", targetNamespace = "http://www.samplews.com/person/service")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.BARE)
@HandlerChain(file = "handlers.xml")

public class PersonWS {

    public PersonWS() {

    }

    @WebMethod(operationName = "Fetch")
    @WebResult(name = "PersonFetchResponse", targetNamespace = "http://www.samplews.com/person/beans", partName = "PersonFetchResponse")
    public PersonDetail fetch(@WebParam(name = "PersonFetchRequest", targetNamespace = "http://www.samplews.com/person/beans", partName = "PersonFetchRequest")PersonFetchRequest request) {
        PersonDetail response=new PersonDetail();
        response.setId(request.getId());
        response.setName("testperson");
        Address address=new Address();
        address.setLine1("Line1");
        address.setLine2("Line2");
        address.setLine3("Line3");
        address.setCity("Mahemdabad");
        address.setPincode(new BigDecimal("387130"));
        response.setAddress(address);
        response.setEmail("testperson@testemail.com");
        response.setPhoneNo(new BigDecimal("9999999999"));
        try {
            response.setBirthDate(DatatypeFactory.newInstance().newXMLGregorianCalendar("1985-01-11"));
        } catch(DatatypeConfigurationException ex) {
            ex.printStackTrace();
        }
        return response;
    }

    @WebMethod(operationName = "Save")
    @WebResult(name = "PersonSaveResponse", targetNamespace = "http://www.samplews.com/person/beans", partName = "PersonSaveResponse")
    public PersonSaveResponse save(@WebParam(name = "PersonSaveRequest", targetNamespace = "http://www.samplews.com/person/beans", partName = "PersonSaveRequest")PersonDetail request) {
        PersonSaveResponse response=new PersonSaveResponse();
        response.setStatus(true);
        response.setMessage("Person with Id:"+request.getId()+" saved.");
        return response;
    }

}

This is soap handler which we have defined in handlers.xml file. The handler intercept request and response as SOAPMessage.

• SecuritySOAPHandler.java

package com.samplews.person.handler;

import com.sun.xml.wss.ProcessingContext;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSecurityException;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.WebServiceException;
import java.util.HashSet;
import java.util.Set;


public class SecuritySOAPHandler implements SOAPHandler<SOAPMessageContext> {
    public Set<QName> getHeaders() {
        QName securityHeader = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", "wsse");
        HashSet<QName> headers = new HashSet<QName>();
        headers.add(securityHeader);
        return headers;
    }

    public boolean handleMessage(SOAPMessageContext soapMessageContext) {
        try {
            boolean outMessageIndicator = (Boolean) soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
            if (!outMessageIndicator) {
                XWSSProcessor xwssProcessor = SecurityConfigProcessorFactory.getXWSSProcessor();
                SOAPMessage message = soapMessageContext.getMessage();
                ProcessingContext context = xwssProcessor.createProcessingContext(message);
                context.setSOAPMessage(message);
                SOAPMessage verifiedMsg = xwssProcessor.verifyInboundMessage(context);
                soapMessageContext.setMessage(verifiedMsg);
            }
        } catch (XWSSecurityException ex) {
            throw new WebServiceException(ex);
        }
        return true;
    }

    public boolean handleFault(SOAPMessageContext soapMessageContext) {
        return true;
    }

    public void close(MessageContext messageContext) {

    }
}

• SecurityConfigProcessorFactory.java

package com.samplews.person.handler;

import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSProcessorFactory;
import com.sun.xml.wss.XWSSecurityException;

import java.io.InputStream;

public class SecurityConfigProcessorFactory {
    protected static XWSSProcessor xwssProcessor;

    public static XWSSProcessor getXWSSProcessor() throws XWSSecurityException {
        if (xwssProcessor == null) {
            InputStream serverConfig = SecurityConfigProcessorFactory.class.getResourceAsStream("/com/samplews/person/handler/security-config.xml");
            XWSSProcessorFactory factory = XWSSProcessorFactory.newInstance();
            xwssProcessor = factory.createProcessorForSecurityConfiguration(serverConfig, new SecurityCallbackHandler());
        }
        return xwssProcessor;
    }
}

The framework passes callbacks according to "security-config.xml".

• SecurityCallbackHandler.java

package com.samplews.person.handler;

import com.sun.xml.wss.impl.callback.PasswordValidationCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

public class SecurityCallbackHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            if (callback instanceof PasswordValidationCallback) {
                PasswordValidationCallback passwordValidationCallback = (PasswordValidationCallback) callback;
                passwordValidationCallback.setValidator(new PlainTextPasswordValidator());
            } else {
                throw new UnsupportedCallbackException(callback);
            }
        }
    }
}

• PlainTextPasswordValidator.java

package com.samplews.person.handler;

import com.sun.xml.wss.impl.callback.PasswordValidationCallback;

import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.util.HashMap;

public class PlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator {
    public boolean validate(PasswordValidationCallback.Request request) throws PasswordValidationCallback.PasswordValidationException {
        PasswordValidationCallback.PlainTextPasswordRequest passwordRequest = (PasswordValidationCallback.PlainTextPasswordRequest) request;
        String username = passwordRequest.getUsername();
        String password = passwordRequest.getPassword();
        if (username != null && password != null) {
            if (username.equals("manan") && password.equals("panchal")) {
                return true;
            }
        } else {
            return false;
            //throw new PasswordValidationCallback.PasswordValidationException("Username or/and Password in request is null.");
        }
        return false;
    }
}

• Navigate to "personwsapp" on command prompt.

• Execute the command. This will compile, and package the project into war file under "target".

mvn package

• Deploy the generated war file into glassfish.

When you deploy the war the glassfish internally uses wsgen to create server artifact like wsdl file, java file of request, java file of response, etc.

• Check webservice is deployed well by writing this url in address bar of browser.
  Change the url if your configuration is different.

http://localhost:8080/personwsapp-1.0/PersonWSService

• Create "client" directory under "Webservice".

• Put this files under "client" directory.

• CallPersonFetchWS.java

import java.io.*;
import java.net.*;

public class CallPersonFetchWS {

    public static void main(String args[]) throws Exception {
        //Change the url if your configuration is different
        URL url = new URL("http://localhost:8080/personwsapp-1.0/PersonWSService");
        URLConnection con = url.openConnection();
        con.setDoInput(true);
        con.setDoOutput(true);
        con.setUseCaches(false);
        con.setDefaultUseCaches(false);
        con.setRequestProperty("content-type", "text/xml");
        /*
        Run alternatively save and fetch request.
        */
        FileInputStream requestIn = new FileInputStream("FetchRequest.xml");
        //FileInputStream requestIn = new FileInputStream("SaveRequest.xml");
        byte[] requestBytes=new byte[requestIn.available()];
        requestIn.read(requestBytes);
        requestIn.close();
        String request=new String(requestBytes);
        byte[] filebytes = request.getBytes();
        con.connect();
        OutputStream out = con.getOutputStream();
        try {
            out.write(filebytes);
            out.flush();
        } finally {
            out.close();
        }
        InputStream in = con.getInputStream();
        byte[] inbytes = new byte[in.available()];
        try {
            in.read(inbytes);
        } finally {
            in.close();
        }
        String resStr = new String(inbytes);
        System.out.println(resStr);
    }
}

• FetchRequest.xml

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://www.samplews.com/person/beans" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <soap:Header>
        <wsse:Security>
            <wsse:UsernameToken>
                <wsse:Username>manan</wsse:Username>
                <wsse:Password>panchal</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soap:Header>
    <soap:Body>
        <ns1:PersonFetchRequest>
            <ns1:Id>1</ns1:Id>
        </ns1:PersonFetchRequest>
    </soap:Body>
</soap:Envelope>

• SaveRequest.xml

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://www.samplews.com/person/beans" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <soap:Header>
        <wsse:Security>
            <wsse:UsernameToken>
                <wsse:Username>manan</wsse:Username>
                <wsse:Password>panchal</wsse:Password>
            </wsse:UsernameToken>
        </wsse:Security>
    </soap:Header>
    <soap:Body>
        <ns1:PersonSaveRequest>
            <ns1:Id>1</ns1:Id>
            <ns1:Name>testperson</ns1:Name>
            <ns1:Address>
                <ns1:Line1>Line1</ns1:Line1>
                <ns1:Line2>Line2</ns1:Line2>
                <ns1:Line3>Line3</ns1:Line3>
                <ns1:City>Mahemdabad</ns1:City>
                <ns1:Pincode>387130</ns1:Pincode>
            </ns1:Address>
            <ns1:PhoneNo>9999999999</ns1:PhoneNo>
            <ns1:Email>testperson@testmail.com</ns1:Email>
            <ns1:BirthDate>1985-01-11</ns1:BirthDate>
        </ns1:PersonSaveRequest>
    </soap:Body>
</soap:Envelope>

• By running "CallPersonFetchWS", you can call the webservice.

I think this would help. Please put suggestions if this not helping you, so I can improve this.
Thanks...

JAX-WS client to call this service

5 comments:

  1. Hello
    very good.
    1.- If i make this into the wsdl do not appears the security configuration. Is normal?

    2.- into th eclient how must i to put the suername and passwd whithout use files? (FetchRequest.xml)
    How can i from one variables to set to the soap message the passwd and username?

    thanks

    ReplyDelete
  2. hello
    into my client i have put this.


    BindingProvider bp = (BindingProvider)port;
    Map map = bp.getRequestContext();
    map.put(BindingProvider.USERNAME_PROPERTY, "manan");
    map.put(BindingProvider.PASSWORD_PROPERTY,"panchal");

    ((BindingProvider)port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "manan");
    ((BindingProvider)port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "panchal");


    but always appears the same error.


    com.sun.xml.ws.client.ClientTransportException: request requires HTTP authentication: Unauthorized
    at com.sun.xml.ws.transport.http.client.HttpClientTransport.checkResponseCode(HttpClientTransport.java:212)
    at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:149)
    at com.sun.xml.xwss.XWSSClientPipe.process(XWSSClientPipe.java:118)
    at com.sun.xml.ws.api.pipe.helper.PipeAdapter.processRequest(PipeAdapter.java:115)
    at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:595)
    at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:554)
    at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:539)
    at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:436)
    at com.sun.xml.ws.client.Stub.process(Stub.java:248)
    at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:135)
    at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:109)
    at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:89)
    at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:118)


    can you help me?
    thanks

    ReplyDelete
  3. Hello MIREN,
    I am creating a post about jaxws client, you will see it soon.
    Thanks

    ReplyDelete
  4. but i have one trouble.
    is normal not appear into the wdl any security node?
    if i make my ws using this manual doesm't appears security nodes
    thanks

    ReplyDelete
  5. yes, no security node will be in wsdl.

    ReplyDelete