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
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
- 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)
Jar Libraries Used in the Project (Maven Dependencies)
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>
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 !!