REST Webservice Basic Authentication

Table of Contents

Introduction

In this tutorial I am going to show you how you can authenticate the REST webservice using sending credential information in Cookie.

The most important concept in REST is resources, which are identified by global IDs — typically using URIs. Client applications use HTTP methods (GET/ POST/ PUT/ DELETE) to manipulate the resource or collection of resources. A RESTful Web service is implemented using HTTP and the principles of REST. Typically, a RESTful Web service should define the following aspects:

The base/root URI for the Web service such as http://<host>/<appcontext/contextpath>/<url pattern>/<resources>.

The MIME type of the response data supported, which are JSON/XML/TEXT/HTML etc.
The set of operations supported by the service. (for example, POST, GET, PUT or DELETE).

HTTP Methods

HTTP methods are mapped to CRUD (create, read, update and delete) actions for a resource. Although you can make slight modifications such as making the PUT method to be create or update, the basic patterns are listed as follows.

HTTP GET: Get/List/Retrieve an individual resource or a collection of resources.
HTTP POST: Create a new resource or resources.
HTTP PUT: Update an existing resource or collection of resources.
HTTP DELETE: Delete a resource or collection of resources.

Prerequisites

Java 8 – 11, JAX-RS jars 2.6/3.1.0-M2, Maven 3.6.3 – 3.8.5

For this tutorial, I will create a web based maven project.

Project Setup

You can create a maven based project in your favorite IDE or tool. The following pom.xml file can be used for the project.

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

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.roytuts</groupId>
	<artifactId>rest-service-authentication</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>4.0.1</version>
			<scope>provided</scope>
		</dependency>
		<!--<dependency>
			<groupId>javax.ws.rs</groupId>
			<artifactId>javax.ws.rs-api</artifactId>
			<version>2.0</version>
		</dependency>-->
		<dependency>
			<groupId>jakarta.ws.rs</groupId>
			<artifactId>jakarta.ws.rs-api</artifactId>
			<version>3.1.0</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.grizzly</groupId>
			<artifactId>grizzly-http-server</artifactId>
			<version>3.0.1</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-servlet</artifactId>
			<version>3.1.0-M2</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.bundles.repackaged</groupId>
			<artifactId>jersey-guava</artifactId>
			<version>2.26-b03</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.containers</groupId>
			<artifactId>jersey-container-grizzly2-http</artifactId>
			<version>3.1.0-M2</version>
		</dependency>
		<!-- jersey-hk2 is required for jersey 3.1.0-M2 -->
		<dependency>
			<groupId>org.glassfish.jersey.inject</groupId>
			<artifactId>jersey-hk2</artifactId>
			<version>3.1.0-M2</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.jersey.test-framework.providers</groupId>
			<artifactId>jersey-test-framework-provider-inmemory</artifactId>
			<version>3.1.0-M2</version>
			<scope>test</scope>
		</dependency>		
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.13.2</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>rest-service-authentication</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>3.2.2</version>
			</plugin>
		</plugins>
	</build>
</project>

Deployment Descriptor

The following web.xml file is used for REST web service:

<?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">-->
    
<web-app version="4.0"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
   http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">

	<servlet>
		<servlet-name>REST</servlet-name>
		<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
		<init-param>
			<param-name>jersey.config.server.provider.packages</param-name>
			<param-value>com.roytuts.rest.authentication.resource</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map /rest/* to Jersey -->
	<servlet-mapping>
		<servlet-name>REST</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

</web-app>

REST Resource

Create a REST resource class as shown below:

package com.roytuts.rest.authentication.resource;

import java.util.Map;

import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/todo")
public class ToDoResource {
	@Context
	HttpHeaders httpHeaders;

	@GET
	@Path("getToDo/{id}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getToDo(@PathParam("id") @NotNull int id) {
		// read headers information to retrieve cookie
		Map<String, Cookie> cookieMap = httpHeaders.getCookies();
		String userName = cookieMap.get("username").getValue();
		String userPass = cookieMap.get("password").getValue();
		
		// authenticate
		if ("roytuts".equals(userName) && "roytuts".equals(userPass)) {
			return Response.status(Response.Status.OK).entity("Get ToDo for " + id).type(MediaType.APPLICATION_JSON)
					.build();
		} else {
			return Response.status(Response.Status.OK).entity("Authentication failed : invalid credentials")
					.type(MediaType.APPLICATION_JSON).build();
		}
	}

	@POST
	@Path("addToDo")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	public Response addToDo(String toDo) {
		// read headers information to retrieve cookie
		Map<String, Cookie> cookieMap = httpHeaders.getCookies();
		String userName = cookieMap.get("username").getValue();
		String userPass = cookieMap.get("password").getValue();

		// authenticate
		if ("roytuts".equals(userName) && "roytuts".equals(userPass)) {
			return Response.status(Response.Status.OK).entity("New ToDo " + toDo + " added successfully")
					.type(MediaType.APPLICATION_JSON).build();
		} else {
			return Response.status(Response.Status.OK).entity("Authentication failed : invalid credentials")
					.type(MediaType.APPLICATION_JSON).build();
		}
	}
}

I have published two endpoints for REST APIs. I am also checking the username and password in the cookie and validating the username and password for accessing the REST APIs. This example provides basic authentication example for REST APIs.

Test Class

Create JUnit test class for testing the REST service:

package com.roytuts.rest.authentication.resource;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation.Builder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.MediaType;

public class ToDoResourceTest {

	private HttpServer httpServer;
	private WebTarget webTarget;
	private List<Cookie> cookies;
	private static final URI baseUri = URI.create("http://localhost:9090/rest/");

	@Before
	public void setup() throws Exception {
		// create ResourceConfig from Resource class
		ResourceConfig rc = new ResourceConfig(ToDoResource.class);
		// create the Grizzly server instance
		httpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, rc);
		// start the server
		httpServer.start();
		// configure client with the base URI path
		Client client = ClientBuilder.newClient();
		webTarget = client.target(baseUri);
		if (cookies == null) {
			buildCookies();
		}
	}

	@After
	public void tearDown() throws Exception {
		// if you want to stop the server from the input through keyboard then
		// uncomment below two lines
		// System.out.println(String
		// .format("Application started.%nHit enter to stop it..."));
		// System.in.read();
		// stop the server
		httpServer.shutdown();
	}

	@Test
	public void testGetToDo() {
		webTarget = webTarget.path("todo/getToDo/1");
		Builder builder = webTarget.request();
		// add cookie to the headers
		for (Cookie cookie : cookies) {
			builder.cookie(cookie);
		}
		String response = builder.accept(MediaType.APPLICATION_JSON).get().readEntity(String.class);
		System.out.println("Response: " + response);
	}

	@Test
	public void testAddToDo() {
		webTarget = webTarget.path("todo/addToDo");
		Builder builder = webTarget.request();
		// add cookie to the headers
		for (Cookie cookie : cookies) {
			builder.cookie(cookie);
		}
		String response = builder.accept(MediaType.APPLICATION_JSON)
				.post(Entity.entity("NEW TODO", MediaType.APPLICATION_JSON), String.class);
		System.out.println("Response: " + response);
	}

	private void buildCookies() {
		cookies = new ArrayList<Cookie>();
		Cookie cookieUser = new Cookie("username", "roytuts");
		Cookie cookiePass = new Cookie("password", "roytuts");
		cookies.add(cookieUser);
		cookies.add(cookiePass);
	}

}

In the above Junit test class, I have started/stopped server programmatically and accessing the REST APIs programmatically.

Testing Basic Authentication in REST Service

Console output when valid credentials are given:

Response: New ToDo NEW TODO added successfully
Response: Get ToDo for 1

Console output when invalid credentials are given (Just change the username or password in buildCookies() method):

Response: Authentication failed : invalid credentials
Response: Authentication failed : invalid credentials

Hope you got an idea how to apply basic authentication in jersey based REST API.

Source Code

Download

Leave a Reply

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