Introduction

Here we will create an example on JAX-WS SOAP Webservice authentication using Spring Boot framework. User needs to pass username and password in the header to authenticate a user before he or she can access the JAX-WS SOAP Webservice. We will apply two approaches to publish our endpoint using Apache CXF Spring Boot starter or JAX-WS Spring API.

We have seen the similar authentication example without using Spring framework.

We will use gradle tool to build our application.

Prerequisites

Eclipse 4.12, Java 8 or 12, Spring Boot 2.1.8, JAX-WS 2.3.1, Gradle 5.6

Example

I am going to give an example on how to implement a simple application level authentication in JAX-WS based SOAP Webservice.

In this tutorial I am going to authenticate a client to the endpoint server.

The idea is straight forward. The client who wants to consume the Service, will have to authenticate using sending the credentials like username and password in the HTTP Request Header.

Then server retrieves these username and password from header information and validates the client’s identity.

In reality password should be encrypted to achieve an acceptable level of security. But in this tutorial I am going to keep things as simple as possible, but keep in mind that authentication along with session management are hard and crucial issues when developing a secure web application.

Creating Project

Create a gradle based project in Eclipse. The name of the project is spring-soap-header-authentication.

We will update the default generated build.gradle script as it doesn’t have the required dependencies for our application.

As we mentioned earlier in the Introduction section that we will use two approaches to publish our endpoint. Therefore we will use different libraries to publish our endpoint using two approaches.

Using Apache CXF Spring Boot Starter

We have included Apache CXF Spring Boot starter to publish our endpoint into the server.

buildscript {
	ext {
		springBootVersion = '2.1.8.RELEASE'
	}
	
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenLocal()
	mavenCentral()
	maven {
		url 'http://maven.java.net/content/repositories/staging/'
	}
}

dependencies {
	implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
	implementation 'org.apache.cxf:cxf-spring-boot-starter-jaxws:3.3.3'
	implementation 'javax.xml.ws:jaxws-api:2.3.1'
    implementation 'com.sun.xml.ws:jaxws-rt:2.3.1'
    implementation 'org.glassfish.jaxb:txw2:2.4.0-b180608.0325'
}

Using JAX-WS Spring API

We have included jaxws-spring API to publish endpoint into the server.

Notice we need to exclude the Servlet API from jaxws-spring otherwise it will make a conflict with Spring Boot starter web API.

buildscript {
	ext {
		springBootVersion = '2.1.8.RELEASE'
	}
	
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenLocal()
	mavenCentral()
	maven {
		url 'http://maven.java.net/content/repositories/staging/'
	}
}

dependencies {
	implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
	implementation('org.jvnet.jax-ws-commons.spring:jaxws-spring:1.9') {
		exclude group: 'javax.servlet', module: 'servlet-api'
	}
	implementation 'javax.xml.ws:jaxws-api:2.3.1'
    implementation 'com.sun.xml.ws:jaxws-rt:2.3.1'
    implementation 'org.glassfish.jaxb:txw2:2.4.0-b180608.0325'
}

Creating Endpoint Interface

We will create a simple endpoint interface and it will have a simple method that will return a greeting message.

We use @Webservice annotation to mark it as a Webservice. We also mark RPC style with body type as Literal.

We have used annotation @Webmethod on a method to indicate it as a Webservice method.

package com.roytuts.spring.soap.header.authentication.endpoint;

import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

@WebService
@SOAPBinding(style = Style.RPC, use = Use.LITERAL)
public interface Hello {

	@WebMethod
	public String sayHello(String name);

}

Creating Service Implementation

Now we need to provide corresponding service implementation class.

The endpoint implementation class provides implementation for sayHello() that simply returns greeting to a user.

As said previously, the server would have to read the HTTP request header information which the client put, and validate its identity.

Service Endpoint Implementation obtains a MessageContext object through a WebServiceContext for accessing the objects of the message.

The WebServiceContext interface enables a web service endpoint implementation class to access message contexts and security information of the requester.

The service runtime will inject the WebServiceContext on any field marked with @Resource annotation. We will call the WebServiceContext‘s getMessageContext() method to get MessageContext instance.

The HTTP Request Header will be retrieved using MessageContext‘s get method with MessageContext.HTTP_REQUEST_HEADERS as a parameter which nominates the kind of context we need from the message.

The Header will be retrieved as a Map which is very convenient because you can specify (key,value) pairs directly. Thus, we retrieve the corresponding credentials.

package com.roytuts.spring.soap.header.authentication.endpoint.impl;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.jws.WebService;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.handler.MessageContext;

import org.springframework.stereotype.Component;

import com.roytuts.spring.soap.header.authentication.endpoint.Hello;

@Component
@WebService(endpointInterface = "com.roytuts.spring.soap.header.authentication.endpoint.Hello")
public class HelloImpl implements Hello {

	@Resource
	WebServiceContext context;

	@Override
	public String sayHello(String name) {
		MessageContext messageContext = context.getMessageContext();

		/**
		 * get http header information
		 */
		Map<?, ?> httpHeaders = (Map<?, ?>) messageContext.get(MessageContext.HTTP_REQUEST_HEADERS);
		List<?> users = (List<?>) httpHeaders.get("username");
		List<?> passwords = (List<?>) httpHeaders.get("password");

		/**
		 * retrieve username and password information from http headers
		 */
		String username = "";
		String password = "";

		if (users != null && !users.isEmpty()) {
			username = users.get(0).toString();
		}

		if (passwords != null && !passwords.isEmpty()) {
			password = passwords.get(0).toString();
		}

		/**
		 * verify the user and return response
		 */
		if (username.equalsIgnoreCase("user") && password.equals("pass")) {
			return "Hello, " + name;
		} else {
			return "Authentication faild! Please provide correct credentials";
		}
	}
}

Creating Publisher Config

As we said earlier that we will use two approaches to publish our service endpoint into the server.

Using JAX-WS Spring

Using purely JAX-WS RI (Reference Implementation) along with Spring we will publish our endpoint and we do not need any other third party library.

First we register the WSSpringServlet with a URL pattern /services/* and then we bind our Hello service with the Spring which will be accessible at /services/hello.

package com.roytuts.spring.soap.header.authentication.config;

import javax.servlet.Servlet;

import org.jvnet.jax_ws_commons.spring.SpringService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.roytuts.spring.soap.header.authentication.endpoint.Hello;
import com.sun.xml.ws.transport.http.servlet.SpringBinding;
import com.sun.xml.ws.transport.http.servlet.WSSpringServlet;

@Configuration
public class SpringSoapConfig {

	@Autowired
	private Hello hello;
	
	@Bean
	public Servlet servlet() {
		return new WSSpringServlet();
	}
	
	@Bean
	public ServletRegistrationBean<Servlet> servletRegistrationBean(){
		return new ServletRegistrationBean<Servlet>(servlet(), "/services/*");
	}
	
	@Bean()
	public SpringBinding springBinding() throws Exception {
		SpringService service=new SpringService();
		service.setBean(hello);
		SpringBinding binding=new SpringBinding();
		binding.setService(service.getObject());
		binding.setUrl("/services/hello");
		return binding;
	}

}

Using Apache CXF Spring Boot Starter

Using Apache CXF Spring Boot starter API it’s easy to publish the service.

The API’s CXFServlet already sets the URL pattern at /services/ for us but you can customize using property cxf.path in the property configuration file.

package com.roytuts.spring.soap.header.authentication.config;

import javax.xml.ws.Endpoint;

import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.roytuts.spring.soap.header.authentication.endpoint.Hello;

@Configuration
public class SpringSoapConfig {

	@Autowired
	private Bus bus;
	
	@Autowired
	private Hello hello;

	@Bean
	public Endpoint endpoint() {
		EndpointImpl endpoint = new EndpointImpl(bus, hello);
		endpoint.publish("/hello");
		return endpoint;
	}

}

Creating Main Class

A class with main method and @SpringBootApplication annotation are enough to start and deploy the Spring Boot application into embedded Tomcat server.

package com.roytuts.spring.soap.header.authentication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.roytuts.spring.soap.header.authentication")
public class SpringSoapHeaderAuthApp {

	public static void main(String[] args) {
		SpringApplication.run(SpringSoapHeaderAuthApp.class, args);
	}

}

Accessing WSDL

For the both approaches mentioned in the above class the WSDL file can be accessible at http://localhost:8080/services/hello?wsdl.

Make sure you already deployed the application by executing the above main class.

The content of the WSDL is given below:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.3.1 svn-revision#6ef5f7eb9a938dbc4562f25f8fa0b67cc4ff2dbb. -->
<!-- Generated by JAX-WS RI (http://javaee.github.io/metro-jax-ws). RI's version is JAX-WS RI 2.3.1 svn-revision#6ef5f7eb9a938dbc4562f25f8fa0b67cc4ff2dbb. -->
<definitions xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://impl.endpoint.authentication.header.soap.spring.roytuts.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://impl.endpoint.authentication.header.soap.spring.roytuts.com/" name="HelloImplService">
<import namespace="http://endpoint.authentication.header.soap.spring.roytuts.com/" location="http://localhost:8080/services/hello?wsdl=1"/>
<binding xmlns:ns1="http://endpoint.authentication.header.soap.spring.roytuts.com/" name="HelloImplPortBinding" type="ns1:Hello">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
<operation name="sayHello">
    <soap:operation soapAction=""/>
    <input>
        <soap:body use="literal" namespace="http://endpoint.authentication.header.soap.spring.roytuts.com/"/>
    </input>
    <output>
        <soap:body use="literal" namespace="http://endpoint.authentication.header.soap.spring.roytuts.com/"/>
    </output>
</operation>
</binding>
<service name="HelloImplService">
    <port name="HelloImplPort" binding="tns:HelloImplPortBinding">
        <soap:address location="http://localhost:8080/services/hello"/>
    </port>
</service>
</definitions>

Testing the Application

Once your application has been deployed into server by executing the main class, you can test your webservice using SOAP UI tool or any other SOAP based testing clients or by writing Java program.

Writing Java Client

We will write a Java program to test our service. You can also generate stubs from the WSDL file and call the service instead of accessing the WSDL using Java program in the way I have shown here.

package com.roytuts.spring.soap.header.authentication.client;

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

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

import com.roytuts.spring.soap.header.authentication.endpoint.Hello;

public class HelloConsumer {

	public static void main(String[] args) throws MalformedURLException {
		URL url = new URL("http://localhost:8080/services/hello?wsdl");
		QName qname = new QName("http://impl.endpoint.authentication.header.soap.spring.roytuts.com/",
				"HelloImplService");
		Service service = Service.create(url, qname);
		Hello hello = service.getPort(Hello.class);
		/******************* Username & Password ******************************/
		Map<String, Object> reqContext = ((BindingProvider) hello).getRequestContext();
		reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/services/hello?wsdl");
		Map<String, List<String>> headers = new HashMap<String, List<String>>();
		headers.put("username", Collections.singletonList("user"));
		headers.put("password", Collections.singletonList("pass"));
		reqContext.put(MessageContext.HTTP_REQUEST_HEADERS, headers);
		/**********************************************************************/
		System.out.println(hello.sayHello("Soumitra"));
	}

}

By executing the above program you will see below output in the console:

Hello, Soumitra

That’s all.

Related Posts:

Source Code

Thanks for reading.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *