13 May 2015

Introduction

This document will help user to setup a RESTful webservice with Basic HTTP authentication powered by Jersey framework. You shall get lots of blogs discuss about how to write RESTful webservice? But there are a few that will cover Authentication of RESTful webservice.

Required software

  • J2EE eclipse (e.g. Eclipse Kepler)
  • JDK 1.7
  • Maven. If you want to know more about maven setup then follow my blog on maven

Objectives

Write a restful webservice that expectes authentication token in the header of the request. If the request does not contain authentication parameter then the web service call should fail. A test client java code is used to test the RESTful webservice. Backend developers always prefer testing RESTful webservice code using test client java code which is faster and convenient way of testing.

Steps to write a code

Create a RESTful project

  • Create a Dynamic web project in eclipse with “module version 3.0” and java source directory is src/main/java
  • Convert the project into maven project (right click on project-> Configure -> Convert to Maven project)
  • Create a package under java source (src/main/java): com.ashish.rest.controller
  • Add maven dependancy: Right click on project->properties->Deployment Assembly->Add->Java Build Path Entries->Maven Dependencies Note: Deploy path should be WEB-INF/lib by default The meaning of the below entry (as shown in the image) is that the dependent jars will get packaged into the WEB-INF/lib folder of the deployer
  • pom.xml: Go through the inline comments below for better understanding

<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 http://maven.apache.org/xsd/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ashish.rest.controller</groupId>
  <artifactId>RESTfulAuth</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
	
	<properties>
		<spring.version>4.0.1.RELEASE</spring.version>
	</properties>
  <repositories>
		<repository>
			<id>maven2-repository.java.net</id>
			<name>Java.net Repository for Maven</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
	</repositories>
 
 	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.4</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
 
	<dependencies>
				
		<dependency>
		  <groupId>commons-codec</groupId>
		  <artifactId>commons-codec</artifactId>
		  <version>1.9</version>
		</dependency>

 		<!-- Below dependency is for the jersey framework for RESTful web service-->
		<dependency>
			<groupId>com.sun.jersey</groupId>
			<artifactId>jersey-server</artifactId>
			<version>1.9</version>
		</dependency>
		
		<!--  Below two dependencies are added to support JSON response -->
 		<dependency>
		    <groupId>com.sun.jersey</groupId>
		    <artifactId>jersey-json</artifactId>
		    <version>1.8</version>
		 </dependency>
		<dependency> 
			<groupId>com.sun.jersey</groupId> 
			<artifactId>jersey-bundle</artifactId> 
			<version>1.18.1</version> 
		</dependency>
		
		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>javax.servlet-api</artifactId>
		    <version>3.0.1</version>
		    <scope>provided</scope>
		</dependency>
	</dependencies>
</project>
  • web.xml: All http requests must pass through com.ashish.rest.authentication.RestAuthenticationFilter authentication class.

<?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" id="WebApp_ID" version="3.0">
  <display-name>Restful WebApplication</display-name>
 <welcome-file-list>
 	<welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
	<servlet>
		<servlet-name>jersey-helloWorld-serlvet</servlet-name>
		<servlet-class>
            com.sun.jersey.spi.container.servlet.ServletContainer
        </servlet-class>
        <!-- Below init-param is added to for RESTful webservice with Jersey framework -->
		<init-param>
		     <param-name>com.sun.jersey.config.property.packages</param-name>
		     <param-value>com.ashish.rest.controller</param-value>
		</init-param>
		<!-- Below init-param is added to support JSON response -->
		<init-param>
            <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
            <param-value>true</param-value>
        </init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
 
	<servlet-mapping>
		<servlet-name>jersey-helloWorld-serlvet</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>
	
	<filter>
	    <filter-name>AuthenticationFilter</filter-name>
	    <filter-class>com.ashish.rest.authentication.RestAuthenticationFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>AuthenticationFilter</filter-name>
		<url-pattern>/rest/*</url-pattern>
	</filter-mapping>
</web-app>
  • A GET and POST request shown here which will return JSON output for a employee. This method takes employeeId as input URL is : http://localhost:8080/RESTfulAuth/rest/hello/getEmployee/123

package com.ashish.rest.controller;
import javax.ws.rs.GET;
.....
@Path("/hello")
public class HelloWorldREST {
  	@GET
	@Path("/getEmployee/{empId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Employee getEmployee( @PathParam("empId") int empId,
			@DefaultValue("No Employee Id passed") @QueryParam("value") String value) {
		System.out.println("getEmployee method is called");
		Employee emp = new Employee();
		emp.setEmpId(empId);
		emp.setName("Ashish Mondal");
	
		return emp;
	
	}
	
	@POST
	@Path("/getSalary")
	@Produces(MediaType.APPLICATION_JSON)
	public Employee getSalary( @PathParam("empId") int empId,
			@DefaultValue("No Employee Id passed") @QueryParam("value") String value) {
		System.out.println("getSalary method is called");
		Employee emp = new Employee();
		emp.setEmpId(empId);
		emp.setName("Ashish Mondal");
		emp.setSalary(1000);
		
		return emp;

	}
}
  • Once you hit http://localhost:8080/RESTfulAuth/rest/hello/getEmployee/123 URL you will see 401 error this means your HTTP basic authentication is working as expected.
  • Write the below RestAuthenticationFilter Java class to pass the REST request through Basic authentication class

public class RestAuthenticationFilter implements javax.servlet.Filter {
	public static final String AUTHENTICATION_HEADER = "Authorization";

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filter) throws IOException, ServletException {
		if (request instanceof HttpServletRequest) {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			String authCredentials = httpServletRequest
					.getHeader(AUTHENTICATION_HEADER);

			// You can implement dependancy injection here
			AuthenticationService authenticationService = new AuthenticationService();

			boolean authenticationStatus = authenticationService
					.authenticate(authCredentials);

			if (authenticationStatus) {
				filter.doFilter(request, response);
			} else {
				if (response instanceof HttpServletResponse) {
					HttpServletResponse httpServletResponse = (HttpServletResponse) response;
					httpServletResponse
							.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
				}
			}
		}
	}

	@Override
	public void destroy() {
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
	}
}
  • In the above code the authentication is done in AuthenticationService.authenticate() method as shown below

package com.ashish.rest.authentication.service;

import java.io.IOException;
import java.util.StringTokenizer;
import sun.misc.BASE64Decoder;

public class AuthenticationService {
	public boolean authenticate(String credential) {
		if (null == credential) {
			return false;
		}
		// header value format will be "Basic encodedstring" for Basic
		// authentication. Example "Basic YWRtaW46YWRtaW4="
		final String encodedUserPassword = credential.replaceFirst("Basic" + " ", "");
		String usernameAndPassword = null;
		try {
			byte[] decodedBytes = new BASE64Decoder().decodeBuffer(encodedUserPassword);
			usernameAndPassword = new String(decodedBytes, "UTF-8");
		} catch (IOException e) {
			e.printStackTrace();
		}
		final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
		final String username = tokenizer.nextToken();
		final String password = tokenizer.nextToken();

		// we have fixed the userid and password as admin
		// call some UserService/LDAP here
		boolean authenticationStatus = "admin".equals(username) && "admin".equals(password);
		return authenticationStatus;
	}
}
  • Write below client code to test your code. Below code can test the following
    • Test POST request without passing authentication request in the header
    • Test POST request with passing authentication request in the header
    • Test GET request with passing authentication request in the header

package com.ashish.rest.test;

import sun.misc.BASE64Encoder;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

public class TestClient {

  public static void main(String[] args) {

	  testPostWithoutBasicAuth();
	  testPOSTWithBasicAuth();
	  testGETWithBasicAuth();
 }

  /**
   * Below method is used to test GET request with HTTP Basic authentication in the header of the request
   */
	private static void testGETWithBasicAuth() {
		try {
	
	        Client client = Client.create();
	
	        String name = "admin";
	        String password = "admin";
	        String authString = name + ":" + password;
	        String authStringEnc = new BASE64Encoder().encode(authString.getBytes());
	        System.out.println("Base64 encoded auth string: " + authStringEnc);
	        WebResource webResource = client.resource("http://localhost:8080/RESTfulAuth/rest/hello/getEmployee/123");
	        
	        ClientResponse resp = webResource.accept("application/json")
	                                         .header("Authorization", "Basic " + authStringEnc)
	                                         .get(ClientResponse.class);
	        if(resp.getStatus() != 200){
	            System.err.println("Unable to connect to the server");
	        }
	        String output = resp.getEntity(String.class);
	        System.out.println("Response for the GET with HTTP Basic authentication request: "+output);
	
	      } catch (Exception e) {
	
	        e.printStackTrace();
	
	      } finally {
	    	  System.out.println("=========================================================================");
	      }
	}
	
	/**
	 * Below method is used to test POST request with HTTP Basic authentication in the header of the request
	 */
	private static void testPOSTWithBasicAuth() {
		try {
	
	        Client client = Client.create();
	
	        String name = "admin";
	        String password = "admin";
	        String authString = name + ":" + password;
	        String authStringEnc = new BASE64Encoder().encode(authString.getBytes());
	        System.out.println("Base64 encoded auth string: " + authStringEnc);
	        WebResource webResource = client.resource("http://localhost:8080/RESTfulAuth/rest/hello/getSalary");
	        
	        ClientResponse resp = webResource.accept("application/json")
	                                         .header("Authorization", "Basic " + authStringEnc)
	                                         .post(ClientResponse.class);
	        if(resp.getStatus() != 200){
	            System.err.println("Unable to connect to the server");
	        }
	        String output = resp.getEntity(String.class);
	        System.out.println("Response for the POST with HTTP Basic authentication request: "+output);
	      } catch (Exception e) {
	        e.printStackTrace();
	      } finally {
	    	  System.out.println("=========================================================================");
	      }
	}
	
	/**
	 * Below method is used to test POST request without HTTP Basic authentication in the header of the request
	 */
	private static void testPostWithoutBasicAuth() {
		try {
	
	        Client client = Client.create();
	        WebResource webResource = client.resource("http://localhost:8080/RESTfulAuth/rest/hello/getSalary");
	
	        String input = "{\"empId\":\"123\"}";
	
	        ClientResponse response = webResource.type("application/json")
	           .post(ClientResponse.class, input);
	
	        if (response.getStatus() != 201) {
	            throw new RuntimeException("Failed : HTTP error code : "
	                 + response.getStatus());
	        }
	
	        System.out.println("HTTP Basic authentication error .... \n");
	        String output = response.getEntity(String.class);
	        System.out.println(output);
	      } catch (Exception e) {
	        e.printStackTrace();
	      } finally {
	    	  System.out.println("=========================================================================");
	      }
	}
}

The output of the above code is as shown below. First method in the above example does not pass authentication token in the request header so the calling has failed. However, other two request with the authentication string in the header has got the successful output.

java.lang.RuntimeException: Failed : HTTP error code : 401
	at com.ashish.rest.test.TestClient.testPostWithoutBasicAuth(TestClient.java:96)
	at com.ashish.rest.test.TestClient.main(TestClient.java:13)
=========================================================================
Base64 encoded auth string: YWRtaW46YWRtaW4=
Response for the POST with HTTP Basic authentication request: {"empId":0,"address1":null,"address2":null,"address3":null,"pin":null,"salary":1000.0,"name":"Ashish Mondal"}
=========================================================================
Base64 encoded auth string: YWRtaW46YWRtaW4=
Response for the GET with HTTP Basic authentication request: {"empId":123,"address1":null,"address2":null,"address3":null,"pin":null,"salary":0.0,"name":"Ashish Mondal"}
=========================================================================

Common issues

SL NO Issues Solution
1 Unable to load class while running a java class from eclipse You may need to update maven project by the following option (right click on the project->Maven->update maven project)


blog comments powered by Disqus
J2EE,SOAP,RESTful,SVN,PMD,SONAR,JaCoCo,HTTP,API,MAVEN,AngularJS,GitHub,LDAP,AOP,ORM,JMS,MVC,AWS,SQL,PHP,H2DB,JDBC