Metro JAX-WS: SOAP based Web Service using Top-Down approach + Adding WS-Security using UsernameToken

In this article, we will add UsernameToken security headers to the demo example seen in the last article “Web Service using top-down approach

 

To protect the exposed services, we will add WS-Security policy either directly in WSDL file or else we can have separate policy file (for bottom-up approach)

Creating policy file rather a complex task so Metro team recommends using NetBeans IDE to create policy using wizard. But in this example, we will create WSDL with policy using NetBeans IDE and later copy into Eclipse project (this will ensure we will stick to our favorite Eclipse IDE)

This policy requires transport-level encryption i.e.; Secured Socket Layer (SSL) in the server, see below article on how to enable SSL. We will use Tomcat server for this example

Apache Tomcat –> activating transport layer security see here

Technology Used

  • Java 1.7
  • Eclipse Luna IDE
  • jaxws-rt-2.2.8
  • webservices-rt-2.3
  • Apache Maven 3.2.1
  • Apache Tomcat-7.0.55

Mavenize or download required jars

Add jaxws-rt & webservices-rt dependencies to pom.xml

<!-- properties -->
<properties>
	<sun.jaxws.version>2.2.8</sun.jaxws.version>
	<metro.jaxws.version>2.3</metro.jaxws.version>
	<jaxws.scope>compile</jaxws.scope> <!-- provided(weblogic) / compile(tomcat) -->
	<compileSource>1.7</compileSource>
	<maven.compiler.source>1.7</maven.compiler.source>
	<maven.compiler.target>1.7</maven.compiler.target>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>

	<!-- jax-ws runtime from sun -->
	<dependency>
		<groupId>com.sun.xml.ws</groupId>
		<artifactId>jaxws-rt</artifactId>
		<version>${sun.jaxws.version}</version>
		<scope>${jaxws.scope}</scope>
	</dependency>

	<!-- jax-ws runtime from glassfish Metro - project tango -->
	<dependency>
		<groupId>org.glassfish.metro</groupId>
		<artifactId>webservices-rt</artifactId>
		<version>${metro.jaxws.version}</version>
		<scope>${jaxws.scope}</scope>
	</dependency>

</dependencies>

Folks who aren’t familiar with Maven concepts or don’t require maven for their project, can download the below jars individually from the central repository or maven repository or maven2 and include them in the classpath

Steps to generate Java artifacts from WSDL/XSD

  • write/design XML Schema (XSD)
  • similarly, write/design WSDL document including above XSD for Type attributes
  • configure maven plugins (wsimport/wsdl2java goal) in pom.xml with correct and complete path of the wsdlFile
  • Run maven command “mvn generate-sources” from project’s context-root
  • java artifacts will be generated under “generated” folder within specified targetNamespace

1_Metro-JAX-WS-Top-Down-UsernameToken_web_service_resources_new

Let us understand above steps in more detail

Write/design well-formed XML Schema

Book.xsd (src/main/webapp/WEB-INF/wsdl)

Below XSD contains two elements with name “BookRequestType” and “BookResponseType

  • BookRequestType contains single string called isbnNumber
  • BookResponseType contains four sub-elements namely bookISBN, bookName, author and category
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	targetNamespace="http://benchresources.in/entities/Book" xmlns:tns="http://benchresources.in/entities/Book"
	elementFormDefault="qualified">

	<!-- Book Request Type -->
	<xsd:element name="BookRequestType">
		<xsd:complexType>
			<xsd:sequence>
				<xsd:element name="isbnNumber" type="xsd:string" />
			</xsd:sequence>
		</xsd:complexType>
	</xsd:element>

	<!-- Book Response Type -->
	<xsd:element name="BookResponseType">
		<xsd:complexType>
			<xsd:sequence>
				<xsd:element name="bookISBN" type="xsd:string" />
				<xsd:element name="bookName" type="xsd:string" />
				<xsd:element name="author" type="xsd:string" />
				<xsd:element name="category" type="xsd:string" />
			</xsd:sequence>
		</xsd:complexType>
	</xsd:element>

</xsd:schema>

Write/design well-formed WSDL

BookService.wsdl (src/main/webapp/WEB-INF/wsdl)

This WSDL document describes,

  • This is the contract document for Book Service exposing one operation called “getBookByISDNRequestNumber” whose input argument is “BookRequestType” and return type is “BookResponseType
  • Policy element is added at the end of this WSDL file and this policy is referenced from <wsdl:binding> section (from <wsp:PolicyReference> element)
  • UsernameToken element (<sp:UsernameToken>) configured to asserts username/password and this ensures to verify every SOAP requests
  • A validator element is configured in the Policy element with classname specifying the fully qualified class name of the validation handler at server side
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	targetNamespace="http://benchresources.in/services/BookService/"
	xmlns:tns="http://benchresources.in/services/BookService/" xmlns:book="http://benchresources.in/entities/Book"
	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"
	xmlns:fi="http://java.sun.com/xml/ns/wsit/2006/09/policy/fastinfoset/service"
	xmlns:tcp="http://java.sun.com/xml/ns/wsit/2006/09/policy/soaptcp/service"
	xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
	xmlns:sc="http://schemas.sun.com/2006/03/wss/server" xmlns:wspp="http://java.sun.com/xml/ns/wsit/policy"
	name="BookService">

	<wsdl:types>
		<xsd:schema targetNamespace="http://benchresources.in/services/BookService/">
			<xsd:import namespace="http://benchresources.in/entities/Book"
				schemaLocation="book.xsd" />
		</xsd:schema>
	</wsdl:types>

	<wsdl:message name="BookRequest">
		<wsdl:part element="book:BookRequestType" name="parameters" />
	</wsdl:message>
	<wsdl:message name="BookResponse">
		<wsdl:part element="book:BookResponseType" name="parameters" />
	</wsdl:message>

	<wsdl:portType name="IBookService">
		<wsdl:operation name="getBookByISDNRequestNumber">
			<wsdl:input message="tns:BookRequest" />
			<wsdl:output message="tns:BookResponse" />
		</wsdl:operation>
	</wsdl:portType>

	<wsdl:binding name="BookServiceSOAPBinding" type="tns:IBookService">
		<wsp:PolicyReference URI="#BookServiceSOAPBindingPolicy" />
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />
		<wsdl:operation name="getBookByISDNRequestNumber">
			<soap:operation soapAction="" />
			<wsdl:input>
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output>
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="BookService">
		<wsdl:port name="BookServicePort" binding="tns:BookServiceSOAPBinding">
			<soap:address
				location="http://localhost:8080/Metro-JAX-WS-Top-Down/services/BookService" />
		</wsdl:port>
	</wsdl:service>

	<wsp:Policy wsu:Id="BookServiceSOAPBindingPolicy">
		<wsp:ExactlyOne>
			<wsp:All>
				<wsam:Addressing wsp:Optional="false" />
				<sp:TransportBinding>
					<wsp:Policy>
						<sp:TransportToken>
							<wsp:Policy>
								<sp:HttpsToken RequireClientCertificate="false" />
							</wsp:Policy>
						</sp:TransportToken>
						<sp:Layout>
							<wsp:Policy>
								<sp:Lax />
							</wsp:Policy>
						</sp:Layout>
						<sp:IncludeTimestamp />
						<sp:AlgorithmSuite>
							<wsp:Policy>
								<sp:Basic128 />
							</wsp:Policy>
						</sp:AlgorithmSuite>
					</wsp:Policy>
				</sp:TransportBinding>
				<sp:SignedEncryptedSupportingTokens>
					<wsp:Policy>
						<sp:UsernameToken
							sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
							<wsp:Policy>
								<sp:WssUsernameToken10 />
							</wsp:Policy>
						</sp:UsernameToken>
					</wsp:Policy>
				</sp:SignedEncryptedSupportingTokens>
				<sc:ValidatorConfiguration
					wspp:visibility="private">
					<sc:Validator name="usernameValidator"
						classname="com.jaxws.series.top.down.approach.service.utils.PlainTextPasswordValidator" />
				</sc:ValidatorConfiguration>
				<sp:Wss11 />
			</wsp:All>
		</wsp:ExactlyOne>
	</wsp:Policy>

</wsdl:definitions>

Configure maven plugin in pom.xml (wsimport goal)

This plugin which defines wsimport goal from jaxws-maven-plugin generates java artifacts from the supplied WSDL file under resources folder

<!-- plugin 4-maven wsimport goal -->
<plugin>
	<groupId>org.jvnet.jax-ws-commons</groupId>
	<artifactId>jaxws-maven-plugin</artifactId>
	<version>2.3</version>
	<executions>
		<execution>
			<id>basic</id>
			<phase>generate-sources</phase>
			<goals>
				<goal>wsimport</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<keep>true</keep>
		<wsdlDirectory>${basedir}\src\main\webapp</wsdlDirectory>
		<wsdlFiles>
			<wsdlFile>WEB-INF\wsdl\BookService.wsdl</wsdlFile>
		</wsdlFiles>
		<sourceDestDir>${basedir}\generated\java\source</sourceDestDir>
		<verbose>true</verbose>
		<target>2.1</target>
	</configuration>
</plugin>

Run “mvn generate-sources”

Look at the generated java source files in the generated folder

After running above maven command, you will get to see below generated java files

2_Metro-JAX-WS-Top-Down-UsernameToken_generated_java_artifacts

  • IBookService.java
  • BookRequestType.java
  • BookResponseType.java
  • BookService.java
  • ObjectFactory.java
  • package-info.java

We will look at one file IBookService.java, for other files you can download this eclipse project provided in the last section “Download Project”

This is the interface which is implemented by our endpoint business implementation class

IBookService.java

package in.benchresources.services.bookservice;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;
import in.benchresources.entities.book.BookRequestType;
import in.benchresources.entities.book.BookResponseType;
import in.benchresources.entities.book.ObjectFactory;

/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.2.8
 * Generated source version: 2.1
 *
 */
@WebService(name = "IBookService", targetNamespace = "http://benchresources.in/services/BookService/")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@XmlSeeAlso({
    ObjectFactory.class
})
public interface IBookService {

    /**
     *
     * @param parameters
     * @return
     *     returns in.benchresources.entities.book.BookResponseType
     */
    @WebMethod
    @WebResult(name = "BookResponseType", targetNamespace = "http://benchresources.in/entities/Book", partName = "parameters")
    public BookResponseType getBookByISDNRequestNumber(
        @WebParam(name = "BookRequestType", targetNamespace = "http://benchresources.in/entities/Book", partName = "parameters")
        BookRequestType parameters);

}

 

Directory Structure

Before moving on, let us understand the directory/package structure once you create project and/plus after generating java artifacts in Eclipse IDE

Maven has to follow certain directory structure

  • src/test/java –> test related files, mostly JUnit test cases
  • src/main/java –> create java source files under this folder
  • generated/java/source –> generated java source files are placed here
  • src/main/resources –> all configuration files placed here
  • src/test/resources –> all test related configuration files placed here
  • Maven Dependencies or Referenced Libraries –> includes jars in the classpath
  • WEB-INF under webapp –> stores web.xml & other configuration files related to web application

Project Structure (Package Explorer view in Eclipse)

3_Metro-JAX-WS-Top-Down-UsernameToken_Project_Structure_In_Eclipse

Jar Libraries Used in the Project (Maven Dependencies)

4_Metro-JAX-WS-Top-Down-UsernameToken_jars_in_classpath

Web application

For any web application, entry point is web.xml which describes how the incoming http requests are served / processed. Further, it describes about the global-context and local-context param (i.e.; <context-param> & <init-param>) for loading files particular to project requirements & contains respective listener

With this introduction, we will understand how we configured web.xml & sun-jaxws.xml for Metro JAX-WS SOAP based Web Service

web.xml (the entry point –> under WEB-INF)

This web.xml file describes,

  • Like any JEE web framework register com.sun.xml.ws.transport.http.servlet.WSServlet with servlet container
  • http requests with URL pattern “/services/*” will be sent to the registered servlet called “metro-jaxws-servlet” i.e.; (com.sun.xml.ws.transport.http.servlet.WSServlet)
  • configure web service listener “com.sun.xml.ws.transport.http.servlet.WSServletContextListener
  • configure session timeout in secs using <session-config> tag
  • <welcome-file-list> files under this tag is the start-up page
  • <security-constraint> element added at the end to intercept the incoming SOAP request for validation, which verifies the username/password with server side validation handler

web.xml

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

	<display-name>Metro-JAX-WS-Top-Down-UsernameToken</display-name>

	<!-- listener for metro jax-ws -->
	<listener>
		<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
	</listener>

	<!-- metro jax-ws servlet -->
	<servlet>
		<servlet-name>metro-jaxws-servlet</servlet-name>
		<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>metro-jaxws-servlet</servlet-name>
		<url-pattern>/services/*</url-pattern>
	</servlet-mapping>

	<!-- session timeout -->
	<session-config>
		<session-timeout>60</session-timeout>
	</session-config>

	<!-- welcome file -->
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>

	<!-- UsernameToken security headers in Metro JAX-WS -->
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>restricted web services</web-resource-name>
			<url-pattern>/*</url-pattern>
			<http-method>GET</http-method>
			<http-method>POST</http-method>
		</web-resource-collection>
		<user-data-constraint>
			<transport-guarantee>CONFIDENTIAL</transport-guarantee>
		</user-data-constraint>
	</security-constraint>

</web-app>

 

sun-jaxws.xml (under WEB-INF)

This file basically defines endpoint url-pattern stating/mentioning qualified package name of the implementation (java class)

sun-jaxws.xml

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
	version="2.0">
	<endpoint name="IBookService"
		implementation="com.jaxws.series.top.down.approach.service.BookServiceImpl"
		wsdl="WEB-INF/wsdl/BookService.wsdl" url-pattern="/services/BookService" />
</endpoints>

 

Let’s see coding in action

 

URL Pattern

Http url for any common web application is http://<server>:<port>/<root-context>/<from_here_application_specific_path>

In our example, we are going to deploy the war into Tomcat 8.0 server with SSL enabled, so our server and port are localhost and 8443 respectively. Context root is the project name i.e.; Metro-JAX-WS-Top-Down-UsernameToken. Initial path for this application is https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken

We have configured “/services/*” as url-pattern for the “metro-jaxws-servlet” servlet in web.xml and our business implementation class implements the portType interface generated from WSDL file which is annotated with @WebService annotation at class-level

 

Service side validation handler

This server side classes asserts/validates the incoming SOAP headers which contains username/password and subsequently service the request on successful authenticated case, otherwise throwing error

NOTE: This example depicts a very simple case where it compares string values. For more complex application, there could be a separate LDAP server or database which stores username/password

PlainTextPasswordValidator.java

package com.jaxws.series.top.down.approach.service.utils;

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

public class PlainTextPasswordValidator implements PasswordValidationCallback.PasswordValidator {

	PasswordValidationCallback.PlainTextPasswordRequest plainTextPasswordRequest = null;

	@Override
	public boolean validate(PasswordValidationCallback.Request request) throws PasswordValidationCallback.PasswordValidationException {

		plainTextPasswordRequest = (PasswordValidationCallback.PlainTextPasswordRequest) request;

		// here actually we can integrate with database or LDAP server for authentication
		if(plainTextPasswordRequest.getUsername().equals("sj") && plainTextPasswordRequest.getPassword().equals("benchresources")) {
			return true;
		}
		return false;
	}
}

 

Book Service Implementation (business logic)

This service provider class implements portType interface generated from WSDL file. Also, class annotated with @WebService annotation at class-level and this is very important

Always, keep a practice of defining most of the attributes of @WebService annotation if not all while developing top-down approach. Defining means providing values of the following attributes

  • serviceName (this is very important, as it defines/controls endpoint URL)
  • endpointInterface (this is mandatory)
  • targetNamespace (same as in the WSDL file)
  • portName (port value under <service> element)
  • name (optional)
  • wsdlLocation (optional)

BookServiceImpl.java

package com.jaxws.series.top.down.approach.service;

import in.benchresources.entities.book.BookRequestType;
import in.benchresources.entities.book.BookResponseType;
import in.benchresources.services.bookservice.IBookService;

import javax.jws.WebService;

@WebService(serviceName="BookService", endpointInterface="in.benchresources.services.bookservice.IBookService",
targetNamespace="http://benchresources.in/services/BookService/", portName="BookServicePort", name="BookServiceImpl")
public class BookServiceImpl implements IBookService {

	// https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken/services/BookService?wsdl - Apache Tomcat 7.x

	@Override
	public BookResponseType getBookByISDNRequestNumber(BookRequestType parameters) {

		// create object of responseType and set values & return
		BookResponseType bookResponseType = new BookResponseType();
		bookResponseType.setBookISBN(parameters.getIsbnNumber());
		bookResponseType.setBookName("Microbiology Study");
		bookResponseType.setAuthor("T. Devasena");
		bookResponseType.setCategory("Microbiology");
		return bookResponseType;
	}
}

 

That’s all with coding part, now let us move on to deployment and testing

 

Deployment: We can either deploy on it Apache Tomcat server or Oracle weblogic application server. Steps for deployment on both these servers are detailed below

 

Server 1: Apache Tomcat-8.0.15 Deployment

  • Run maven command to build the war: mvn clean install (use command prompt or integrated maven in eclipse IDE)
  • Copy(ctrl+c) the war file from the target folder
  • Paste(ctrl+v) it into apache tomcat (webapps folder)
  • Start the tomcat server (Tomcat_Home\bin\startup.bat)

Server 2: Oracle weblogic application server

Test the service !!

Testing

There are many ways to do testing

  • SOAP UI Client
  • Java Client using JDK’s in-built classes like HttpURLConnection
  • Java Client using SOAP API
  • Eclipse’s Web Services Explorer
  • Write your own client for example, Java client using httpcomponents from Apache

We will cover first 3 ways of testing above JAX-WS deployed service

1. SOAP UI Client

Load the endpoint URL in SOAP UI Client, which will pre-populate the request XML based on the operation deployed/exposed using WSDL. In this request XML, add header part comprising security tag with username/password

For this example, https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken/services/BookService?wsdl

Request XML

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:book="http://benchresources.in/entities/Book">
	<soapenv:Header>
		<MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:93e0c133-f54b-47ab-bcce-4d7dfdd71f53
		</MessageID>
		<wsse:Security
			xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
			xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
			soapenv:mustUnderstand="1">
			<wsu:Timestamp wsu:Id="_1"
				xmlns:ns15="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512"
				xmlns:ns14="http://www.w3.org/2003/05/soap-envelope">
				<wsu:Created>2014-11-16T15:05:26Z</wsu:Created>
				<wsu:Expires>2014-11-16T15:10:26Z</wsu:Expires>
			</wsu:Timestamp>
			<wsse:UsernameToken wsu:Id="UsernameToken-1">
				<wsse:Username>sj</wsse:Username>
				<wsse:Password
					Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">benchresources</wsse:Password>
			</wsse:UsernameToken>
		</wsse:Security>
	</soapenv:Header>
	<soapenv:Body>
		<book:BookRequestType>
			<book:isbnNumber>ISBN-2134</book:isbnNumber>
		</book:BookRequestType>
	</soapenv:Body>
</soapenv:Envelope>

Response XML

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
	xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
	xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
	xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<S:Header>
		<Action S:mustUnderstand="1" xmlns="http://www.w3.org/2005/08/addressing">http://benchresources.in/services/BookService/IBookService/getBookByISDNRequestNumberResponse
		</Action>
		<MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:a78142a5-7497-4c09-b48f-f1bc78c3499f
		</MessageID>
		<RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:93e0c133-f54b-47ab-bcce-4d7dfdd71f53
		</RelatesTo>
		<To xmlns="http://www.w3.org/2005/08/addressing">http://www.w3.org/2005/08/addressing/anonymous</To>
		<wsse:Security S:mustUnderstand="1">
			<wsu:Timestamp wsu:Id="_1"
				xmlns:ns15="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512"
				xmlns:ns14="http://www.w3.org/2003/05/soap-envelope">
				<wsu:Created>2014-11-16T15:07:29Z</wsu:Created>
				<wsu:Expires>2014-11-16T15:12:29Z</wsu:Expires>
			</wsu:Timestamp>
		</wsse:Security>
	</S:Header>
	<S:Body>
		<BookResponseType xmlns="http://benchresources.in/entities/Book">
			<bookISBN>ISBN-2134</bookISBN>
			<bookName>Microbiology Study</bookName>
			<author>T. Devasena</author>
			<category>Microbiology</category>
		</BookResponseType>
	</S:Body>
</S:Envelope>

5_Metro-JAX-WS-Top-Down-UsernameToken_SOAP_UI_Testing

 

2. Java client

For this java client to work/execute, we don’t need to add any extra jars or any new dependency in pom.xml as these classes comes shipped along with JDK. Observe, import statements closely for this client

Note: Request XML pattern formed taking help from pre-populated request XML from SOAP UI client as explained above

TestBookService.java

package test.jaxws.series.top.down.approach.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;

public class TestBookService {

	/**
	 * main method
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {

		String soapRequestURL = "https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken/services/BookService?wsdl";
		String soapRequestXML = createSOAPRequest();
		String responseFromService = testBookService(soapRequestURL, soapRequestXML);
		System.out.println("Response String: " + responseFromService);
	}

	/**
	 * static block - to suppress the below exception
	 * java.security.cert.CertificateException: No name matching localhost found
	 */
	static {
		//for localhost testing only
		javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
				new javax.net.ssl.HostnameVerifier(){

					public boolean verify(String hostname,
							javax.net.ssl.SSLSession sslSession) {
						if (hostname.equals("localhost")) {
							return true;
						}
						return false;
					}
				});
	}

	/**
	 * create SOAP Request XML
	 * @return
	 */
	private static String createSOAPRequest() {

		String soapRequestParam = 	"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:book=\"http://benchresources.in/entities/Book\">"
				+						"<soapenv:Header>"
				+							"<Action soapenv:mustUnderstand=\"1\" xmlns=\"http://www.w3.org/2005/08/addressing\">http://benchresources.in/services/BookService/IBookService/getBookByISDNRequestNumberRequest</Action>"
				+							"<MessageID xmlns=\"http://www.w3.org/2005/08/addressing\">uuid:93e0c133-f54b-47ab-bcce-4d7dfdd71f53</MessageID>"
				+							"<To xmlns=\"http://www.w3.org/2005/08/addressing\">https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken/services/BookService</To>"
				+							"<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\" soapenv:mustUnderstand=\"1\">"
				//				+								"<wsu:Timestamp wsu:Id=\"_1\" xmlns:ns15=\"http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512\" xmlns:ns14=\"http://www.w3.org/2003/05/soap-envelope\">"
				//				+									"<wsu:Created>2014-11-08T16:29:42Z</wsu:Created>"
				//				+ 									"<wsu:Expires>2014-11-08T16:34:42Z</wsu:Expires>"
				//				+								"</wsu:Timestamp>"
				+								"<wsse:UsernameToken wsu:Id=\"UsernameToken-1\">"
				+									"<wsse:Username>sj</wsse:Username>"
				+									"<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">benchresources</wsse:Password>"
				+								"</wsse:UsernameToken>"
				+							"</wsse:Security>"
				+   					"</soapenv:Header>"
				+						"<soapenv:Body>"
				+							"<book:BookRequestType>"
				+								"<book:isbnNumber>ISBN-2134</book:isbnNumber>"
				+							"</book:BookRequestType>"
				+						"</soapenv:Body>"
				+					"</soapenv:Envelope>";
		return soapRequestParam;
	}

	/**
	 * This method uses HttpURLConnection to invoke exposed Restful web service and returns the response string to the calling client
	 * @param requestParams
	 * @return
	 * @throws IOException
	 */
	public static String testBookService(String soapRequestURL, String soapRequestParam) throws IOException {

		// local variables
		URL url = null;
		HttpURLConnection httpURLConnection = null;
		OutputStreamWriter outputStreamWriter = null;
		String responseMessageFromServer = null;
		String responseXML = null; 

		try {
			url = new URL(soapRequestURL);
			httpURLConnection = (HttpURLConnection) url.openConnection();
			httpURLConnection.setRequestMethod("POST");
			httpURLConnection.setRequestProperty("Content-Type", "text/xml");
			httpURLConnection.setRequestProperty("accept", "text/xml");
			httpURLConnection.setDoOutput(true);

			outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream());
			outputStreamWriter.write(soapRequestParam);
			outputStreamWriter.flush();
			outputStreamWriter.close();

			System.out.println("Response code: " + httpURLConnection.getResponseCode());

			if (httpURLConnection.getResponseCode() == 200) {

				responseMessageFromServer = httpURLConnection.getResponseMessage();
				System.out.println("ResponseMessageFromServer: " + responseMessageFromServer);
				responseXML = getResponseXML(httpURLConnection);
			}
		}
		catch(Exception  ex){
			ex.printStackTrace();
		}
		finally{

			httpURLConnection.disconnect();
		}
		return responseXML;
	}

	/**
	 * This method is used to get response XML from the HTTP GET Request created for Authorization WireKey
	 * @param httpURLConnection
	 * @return stringBuffer.toString()
	 * @throws IOException
	 */
	private static String getResponseXML(HttpURLConnection httpURLConnection) throws IOException{

		StringBuffer stringBuffer = new StringBuffer();
		BufferedReader bufferedReader = null;
		InputStreamReader inputStreamReader = null;
		String readSingleLine = null;

		try{
			// read the response stream AND buffer the result into a StringBuffer
			inputStreamReader = new InputStreamReader(httpURLConnection.getInputStream());
			bufferedReader = new BufferedReader(inputStreamReader);

			// reading the XML response content line BY line
			while ((readSingleLine = bufferedReader.readLine()) != null) {
				stringBuffer.append(readSingleLine);
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		finally{
			// finally close all operations
			bufferedReader.close();
			httpURLConnection.disconnect();
		}
		return stringBuffer.toString();
	}
}

Output in console

Response code: 200
ResponseMessageFromServer: OK
Response String : 
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
	xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
	xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
	xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<S:Header>
		<Action xmlns="http://www.w3.org/2005/08/addressing" xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
			S:mustUnderstand="1">http://benchresources.in/services/BookService/IBookService/getBookByISDNRequestNumberResponse
		</Action>
		<MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:c397c076-bc99-46e0-b7a0-334c6d94b9e4</MessageID>
		<RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:93e0c133-f54b-47ab-bcce-4d7dfdd71f53</RelatesTo>
		<To xmlns="http://www.w3.org/2005/08/addressing">http://www.w3.org/2005/08/addressing/anonymous</To>
		<wsse:Security S:mustUnderstand="1">
			<wsu:Timestamp
				xmlns:ns15="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512"
				xmlns:ns14="http://www.w3.org/2003/05/soap-envelope" wsu:Id="_1">
				<wsu:Created>2014-11-16T15:24:22Z</wsu:Created>
				<wsu:Expires>2014-11-16T15:29:22Z</wsu:Expires>
			</wsu:Timestamp>
		</wsse:Security>
	</S:Header>
	<S:Body>
		<BookResponseType xmlns="http://benchresources.in/entities/Book">
			<bookISBN>ISBN-2134</bookISBN>
			<bookName>Microbiology Study</bookName>
			<author>T. Devasena</author>
			<category>Microbiology</category>
		</BookResponseType>
	</S:Body>
</S:Envelope>

 

3. Java client using SOAP API

This client uses SOAP API to hit the target https URL

TestBookServiceUsingSoapAPI.java

package test.jaxws.series.top.down.approach.service;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import javax.xml.ws.soap.AddressingFeature;
import javax.xml.ws.soap.SOAPBinding;

public class TestBookServiceUsingSoapAPI {

	/**
	 * main method
	 * @param args
	 * @throws SOAPException
	 * @throws IOException
	 */
	public static void main(String[] args) throws SOAPException, IOException {

		// create SOAP Request and hit the service to get SOAP Response
		SOAPMessage soapResponseXML = createSoapRequestAndHitService();

		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		soapResponseXML.writeTo(byteArrayOutputStream); 

		System.out.println("SOAP Response XML : \n" + byteArrayOutputStream);
	}

	/**
	 * static block - to suppress the below exception
	 * java.security.cert.CertificateException: No name matching localhost found
	 */
	static {
		//for localhost testing only
		javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
				new javax.net.ssl.HostnameVerifier(){

					public boolean verify(String hostname,
							javax.net.ssl.SSLSession sslSession) {
						if (hostname.equals("localhost")) {
							return true;
						}
						return false;
					}
				});
	}

	/**
	 * create SOAP Request using SOAP API and hit  target https URL
	 * @return
	 * @throws SOAPException
	 * @throws IOException
	 */
	private static SOAPMessage createSoapRequestAndHitService() throws SOAPException, IOException {

		// set required namespace to form SOAP Request
		final String SECURITY_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
		final String MUSIC_NAMESPACE = "http://benchresources.in/entities/Book";

		// set QName for service and port
		QName serviceQName = new QName("http://benchresources.in/services/BookService/", "BookService");
		QName portQName = new QName("http://benchresources.in/services/BookService/", "BookServiceSOAPBinding");

		// add target https URL
		Service service = Service.create(serviceQName);
		service.addPort(portQName, SOAPBinding.SOAP11HTTP_BINDING, "https://localhost:8443/Metro-JAX-WS-Top-Down-UsernameToken/services/BookService?wsdl");

		// set required properties for request
		Dispatch<SOAPMessage> dispatch = service.createDispatch(portQName, SOAPMessage.class,Service.Mode.MESSAGE, new AddressingFeature());
		dispatch.getRequestContext().put(BindingProvider.SOAPACTION_USE_PROPERTY, true);
		dispatch.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, "http://benchresources.in/services/BookService/IBookService/getBookByISDNRequestNumberRequest");

		// create SOAP factory to set header and body
		SOAPFactory soapFactory = SOAPFactory.newInstance();

		// set headers
		QName securityQName = new QName(SECURITY_NAMESPACE, "Security");
		SOAPElement securityElement = soapFactory.createElement(securityQName);

		// UsernameToken
		QName usernameTokenQName = new QName(SECURITY_NAMESPACE, "UsernameToken");
		SOAPElement usernameTokenElement = soapFactory.createElement(usernameTokenQName);

		// Username
		QName userQName = new QName(SECURITY_NAMESPACE, "Username");
		SOAPElement usernameElement = soapFactory.createElement(userQName);
		usernameElement.addTextNode("sj");

		// Password
		QName passwordQName = new QName(SECURITY_NAMESPACE, "Password");
		SOAPElement passwordElement = soapFactory.createElement(passwordQName);
		passwordElement.addTextNode("benchresources");

		// final SOAP header
		usernameTokenElement.addChildElement(usernameElement);
		usernameTokenElement.addChildElement(passwordElement);
		securityElement.addChildElement(usernameTokenElement);

		// set body
		QName musicQName = new QName(MUSIC_NAMESPACE, "BookRequestType");
		SOAPElement musicElement = soapFactory.createElement(musicQName);

		// input tag composerName with its value
		QName requsetTypeQName = new QName(MUSIC_NAMESPACE, "isbnNumber");
		SOAPElement requsetTypeElement = soapFactory.createElement(requsetTypeQName);
		requsetTypeElement.addTextNode("ISBN-2134");

		// final SOAP body
		musicElement.addChildElement(requsetTypeElement);

		MessageFactory messageFactory = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
		SOAPMessage soapMessage = messageFactory.createMessage();

		// add final header element to SOAP message (SOAP envelope)
		SOAPHeader soapHeader = soapMessage.getSOAPHeader();
		soapHeader.addChildElement(securityElement);

		// add final body element to SOAP message (SOAP envelope)
		SOAPBody soapBody = soapMessage.getSOAPBody();
		soapBody.addChildElement(musicElement);

		// save changes
		soapMessage.saveChanges();

		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		soapMessage.writeTo(byteArrayOutputStream); 

		System.out.println("SOAP Response *** : \n" + byteArrayOutputStream);

		// invoke the endpoint and get response
		SOAPMessage soapResponseXML = (SOAPMessage) dispatch.invoke(soapMessage);

		// return SOAP Response
		return soapResponseXML;
	}
}

Output in console

SOAP Response XML :
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
	xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"
	xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
	xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<S:Header>
		<Action xmlns="http://www.w3.org/2005/08/addressing"
			S:mustUnderstand="1">http://benchresources.in/services/BookService/IBookService/getBookByISDNRequestNumberResponse
		</Action>
		<MessageID xmlns="http://www.w3.org/2005/08/addressing">uuid:07104db0-9a32-4b80-8a4b-f7a2743ba78c</MessageID>
		<RelatesTo xmlns="http://www.w3.org/2005/08/addressing">uuid:b44cb1fc-9840-4e99-ab07-80e061868075</RelatesTo>
		<To xmlns="http://www.w3.org/2005/08/addressing">http://www.w3.org/2005/08/addressing/anonymous</To>
		<wsse:Security S:mustUnderstand="1">
			<wsu:Timestamp xmlns:ns14="http://www.w3.org/2003/05/soap-envelope"
				xmlns:ns15="http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512"
				wsu:Id="_1">
				<wsu:Created>2014-11-16T15:29:47Z</wsu:Created>
				<wsu:Expires>2014-11-16T15:34:47Z</wsu:Expires>
			</wsu:Timestamp>
		</wsse:Security>
	</S:Header>
	<S:Body>
		<BookResponseType xmlns="http://benchresources.in/entities/Book">
			<bookISBN>ISBN-2134</bookISBN>
			<bookName>Microbiology Study</bookName>
			<author>T. Devasena</author>
			<category>Microbiology</category>
		</BookResponseType>
	</S:Body>
</S:Envelope>

 

Conclusion: With WS-Security using UsernameToken profile, we can protect the exposed SOAP based Web Service

Download project

Metro-JAX-WS-Top-Down-UsernameToken (18kB)

 

Happy Coding !!
Happy Learning !!

Metro JAX-WS: SOAP based Web Service using Top-Down approach + Integrating with Spring & Hibernate ORM framework