Security in Java Restful Web Services


Use JAR files which are listed below:

asm-3.1.jar
commons-codec-1.10.jar
jackson-core-asl-1.9.2.jar
jackson-jaxrs-1.9.2.jar
jackson-mapper-asl-1.9.2.jar
jackson-xc-1.9.2.jar
javassist-3.19.0-GA.jar
jersey-client-1.18.jar
jersey-core-1.18.jar
jersey-json-1.18.jar
jersey-server-1.18.jar
jersey-servlet-1.18.jar
jettison-1.1.jar
jsr311-api-1.1.1.jar
resteasy-jaxb-provider-2.3.3.Final.jar
resteasy-jaxrs-2.3.1.GA.jar
scannotation-1.0.2.jar

Create Dynamic Web Project in Eclipse. Copy all jar files above to the lib directory in the project




This file is used to register a URL pattern in Jersey to intercept HTTP calls to the service.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <context-param>
        <param-name>resteasy.scan</param-name>
        <param-value>true</param-value>
    </context-param>

    <servlet>
        <servlet-name>resteasy-servlet</servlet-name>
        <servlet-class>
            org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
        </servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>resteasy-servlet</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

</web-app>

Create entities package in server project. Create a entity class: Account.java as below

Account.java

package entities;

public class Account {

	private String username;
	private String password;
	private String[] roles;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String[] getRoles() {
		return roles;
	}

	public void setRoles(String[] roles) {
		this.roles = roles;
	}

	public Account(String username, String password, String[] roles) {
		this.username = username;
		this.password = password;
		this.roles = roles;
	}

	public Account() {
	}

}

Create model package in server project. Create a model class: AccountModel.java as below

AccountModel.java

package model;

import java.util.ArrayList;
import java.util.List;
import entities.Account;

public class AccountModel {

	private List<Account> accounts;

	public AccountModel() {
		this.accounts = new ArrayList<Account>();
		this.accounts.add(new Account("acc1", "123", new String[] { "superadmin", "admin", "employee" }));
		this.accounts.add(new Account("acc2", "123", new String[] { "admin", "employee" }));
		this.accounts.add(new Account("acc3", "123", new String[] { "employee" }));
	}

	public Account login(String username, String password) {
		for (Account account : this.accounts) {
			if (account.getUsername().equalsIgnoreCase(username) && account.getPassword().equalsIgnoreCase(password)) {
				return account;
			}
		}
		return null;
	}

}




Create security package in server project. Create a interceptor class: SecurityInterceptor.java as below

SecurityInterceptor.java

package security;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
import org.jboss.resteasy.util.Base64;
import entities.Account;
import model.AccountModel;

@Provider
@ServerInterceptor
public class SecurityInterceptor implements PreProcessInterceptor {
	private static final String AUTHORIZATION_PROPERTY = "Authorization";
	private static final String AUTHENTICATION_SCHEME = "Basic";
	private static final ServerResponse ACCESS_DENIED = new ServerResponse("Access denied for this resource", 401,
			new Headers<Object>());
	private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse("Nobody can access this resource", 403,
			new Headers<Object>());
	private static final ServerResponse SERVER_ERROR = new ServerResponse("INTERNAL SERVER ERROR", 500,
			new Headers<Object>());

	@Override
	public ServerResponse preProcess(HttpRequest request, ResourceMethod methodInvoked)
			throws Failure, WebApplicationException {
		Method method = methodInvoked.getMethod();

		// Access allowed for all
		if (method.isAnnotationPresent(PermitAll.class)) {
			return null;
		}
		// Access denied for all
		if (method.isAnnotationPresent(DenyAll.class)) {
			return ACCESS_FORBIDDEN;
		}

		// Get request headers
		final HttpHeaders headers = request.getHttpHeaders();

		// Fetch authorization header
		final List<String> authorization = headers.getRequestHeader(AUTHORIZATION_PROPERTY);

		// If no authorization information present; block access
		if (authorization == null || authorization.isEmpty()) {
			return ACCESS_DENIED;
		}

		// Get encoded username and password
		final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");

		// Decode username and password
		String usernameAndPassword;
		try {
			usernameAndPassword = new String(Base64.decode(encodedUserPassword));
		} catch (IOException e) {
			return SERVER_ERROR;
		}

		// Split username and password tokens
		final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
		final String username = tokenizer.nextToken();
		final String password = tokenizer.nextToken();

		// Verify user access
		if (method.isAnnotationPresent(RolesAllowed.class)) {
			RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
			Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value()));

			// Is user valid?
			if (!isUserAllowed(username, password, rolesSet)) {
				return ACCESS_DENIED;
			}
		}

		// Return null to continue request processing
		return null;
	}

	private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet) {
		AccountModel accountModel = new AccountModel();
		Account account = accountModel.login(username, password);
		if (account == null) {
			return false;
		} else {
			for (String role : rolesSet) {
				for (String r : account.getRoles()) {
					if (role.equalsIgnoreCase(r)) {
						return true;
					}
				}
			}
			return false;
		}
	}

}

Create Restful Web Services provides text/plain data for the client

package ws;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("api/demo")
public class DemoRestController {

	@GET
	@Path("work1")
	@Produces({ MediaType.TEXT_PLAIN })
	@PermitAll
	public Response work1() {
		try {
			return Response.ok("Work 1").build();
		} catch (Exception e) {
			return Response.status(Response.Status.BAD_REQUEST).build();
		}
	}

	@GET
	@Path("work2")
	@Produces({ MediaType.TEXT_PLAIN })
	@RolesAllowed({ "superadmin" })
	public Response work2() {
		try {
			return Response.ok("Work 2").build();
		} catch (Exception e) {
			return Response.status(Response.Status.BAD_REQUEST).build();
		}
	}

	@GET
	@Path("work3")
	@Produces({ MediaType.TEXT_PLAIN })
	@RolesAllowed({ "superadmin", "admin" })
	public Response work3() {
		try {
			return Response.ok("Work 3").build();
		} catch (Exception e) {
			return Response.status(Response.Status.BAD_REQUEST).build();
		}
	}

	@GET
	@Path("work4")
	@Produces({ MediaType.TEXT_PLAIN })
	@RolesAllowed({ "superadmin", "admin", "employee" })
	public Response work4() {
		try {
			return Response.ok("Work 4").build();
		} catch (Exception e) {
			return Response.status(Response.Status.BAD_REQUEST).build();
		}
	}

}




Access restful web services use the following url: http://localhost:8080/SecurityJavaRestful_Server/api/demo/work1

Output

Work 1

Access restful web services use the following url: http://localhost:8080/SecurityJavaRestful_Server/api/demo/work2

Output

Access denied for this resource

Access restful web services use the following url: http://localhost:8080/SecurityJavaRestful_Server/api/demo/work3

Output

Access denied for this resource

Access restful web services use the following url: http://localhost:8080/SecurityJavaRestful_Server/api/demo/work4

Output

Access denied for this resource

Create Java Project in Eclipse. Add all jar files above to the project

DemoRestClientModel class contain methods call restful web services

package models;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import javax.ws.rs.core.MediaType;
import org.apache.commons.codec.binary.Base64;

public class DemoRestClientModel {

	private String username = "acc2";
	private String password = "123";
	private Client client;
	private WebResource webResource;
	private String BASE_URL = "http://localhost:8080/SecurityJavaRestful_Server/api/";

	public DemoRestClientModel() {
		this.client = Client.create(new DefaultClientConfig());
		this.webResource = this.client.resource(BASE_URL).path("demo");
	}

	public ClientResponse work1() {
		ClientResponse response = null;
		try {
			WebResource resource = this.webResource;
			response = resource.path("work1")
								.type(MediaType.TEXT_PLAIN)
								.get(ClientResponse.class);
		} catch (Exception e) {
			response = null;
		}
		return response;
	}

	public ClientResponse work2() {
		ClientResponse response = null;
		try {
			String authStringAccount = new Base64().encodeToString((this.username + ":" + this.password).getBytes());
			WebResource resource = this.webResource;
			response = resource.path("work2")
								.header("Authorization", "Basic " + authStringAccount)
								.type(MediaType.TEXT_PLAIN)
								.get(ClientResponse.class);
		} catch (Exception e) {
			response = null;
		}
		return response;
	}

	public ClientResponse work3() {
		ClientResponse response = null;
		try {
			String authStringAccount = new Base64().encodeToString((this.username + ":" + this.password).getBytes());
			WebResource resource = this.webResource;
			response = resource.path("work3")
								.header("Authorization", "Basic " + authStringAccount)
								.type(MediaType.TEXT_PLAIN)
								.get(ClientResponse.class);
		} catch (Exception e) {
			response = null;
		}
		return response;
	}

	public ClientResponse work4() {
		ClientResponse response = null;
		try {
			String authStringAccount = new Base64().encodeToString((this.username + ":" + this.password).getBytes());
			WebResource resource = this.webResource;
			response = resource.path("work4")
								.header("Authorization", "Basic " + authStringAccount)
								.type(MediaType.TEXT_PLAIN)
								.get(ClientResponse.class);
		} catch (Exception e) {
			response = null;
		}
		return response;
	}

}




package main;

import com.sun.jersey.api.client.ClientResponse;
import models.DemoRestClientModel;

public class Main {

	public static void main(String[] args) {

		DemoRestClientModel demoRestClientModel = new DemoRestClientModel();

		System.out.println("Test with work1 web method");
		ClientResponse response1 = demoRestClientModel.work1();
		int statusCode1 = response1.getStatus();
		System.out.println("\tStatus Code: " + statusCode1);
		String result1 = response1.getEntity(String.class);
		System.out.println("\tResult: " + result1);

		System.out.println("Test with work2 web method without account");
		ClientResponse response2 = demoRestClientModel.work2();
		int statusCode2 = response2.getStatus();
		System.out.println("\tStatus Code: " + statusCode2);
		String result2 = response2.getEntity(String.class);
		System.out.println("\tResult: " + result2);

		System.out.println("Test with acc2 have roles: admin and employee access work2 web method");
		ClientResponse response3 = demoRestClientModel.work2();
		int statusCode3 = response3.getStatus();
		System.out.println("\tStatus Code: " + statusCode3);
		String result3 = response3.getEntity(String.class);
		System.out.println("\tResult: " + result3);

		System.out.println("Test with acc2 have roles: admin and employee access work3 web method");
		ClientResponse response4 = demoRestClientModel.work3();
		int statusCode4 = response4.getStatus();
		System.out.println("\tStatus Code: " + statusCode4);
		String result4 = response4.getEntity(String.class);
		System.out.println("\tResult: " + result4);

		System.out.println("Test with acc2 have roles: admin and employee access work4 web method");
		ClientResponse response5 = demoRestClientModel.work4();
		int statusCode5 = response5.getStatus();
		System.out.println("\tStatus Code: " + statusCode5);
		String result5 = response5.getEntity(String.class);
		System.out.println("\tResult: " + result5);

	}

}
Test with work1 web method
	Status Code: 200
	Result: Work 1
Test with work2 web method without account
	Status Code: 401
	Result: Access denied for this resource
Test with acc2 have roles: admin and employee access work2 web method
	Status Code: 401
	Result: Access denied for this resource
Test with acc2 have roles: admin and employee access work3 web method
	Status Code: 200
	Result: Work 3
Test with acc2 have roles: admin and employee access work4 web method
	Status Code: 200
	Result: Work 4