Security in Java Restful Web Services and Hibernate


Use JAR files which are listed below:

antlr-2.7.7.jar
asm-3.1.jar
commons-codec-1.10.jar
commons-logging-1.1.3.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-4.0.2.Final.jar
hibernate-core-4.2.5.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.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
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.1.Final.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
mysql-connector-java-5.1.36.jar
resteasy-jaxb-provider-2.3.3.Final.jar
resteasy-jaxrs-2.3.1.GA.jar
scannotation-1.0.2.jar

Create a database with the name is learnjavarestfulwebservices. This database have 3 tables: account, role and account_role.

--
-- Table structure for table `account`
--

CREATE TABLE `account` (
  `username` varchar(250) NOT NULL PRIMARY KEY,
  `password` varchar(250) COLLATE utf8_unicode_ci NOT NULL,
  `fullname` varchar(250) COLLATE utf8_unicode_ci NOT NULL,
  `email` varchar(250) COLLATE utf8_unicode_ci NOT NULL,
  `enable` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

--
-- Dumping data for table `account`
--

INSERT INTO `account` (`username`, `password`, `fullname`, `email`, `enable`) VALUES
('acc1', '123', 'Account 1', 'acc1@gmail.com', 1),
('acc2', '123', 'Account 2', 'acc2@gmail.com', 1),
('acc3', '123', 'Account 3', 'acc3@gmail.com', 1);

--
-- Table structure for table `role`
--

CREATE TABLE `role` (
  `id` varchar(250) NOT NULL PRIMARY KEY,
  `name` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `role`
--

INSERT INTO `role` (`id`, `name`) VALUES
('admin', 'Admin'),
('employee', 'Employee'),
('superadmin', 'SuperAdmin');

--
-- Table structure for table `account_role`
--

CREATE TABLE `account_role` (
  `roleId` varchar(250) NOT NULL,
  `username` varchar(250) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `enable` tinyint(1) NOT NULL,
   PRIMARY KEY (`roleId`,`username`),
   FOREIGN KEY (`username`) REFERENCES `account` (`username`),
   FOREIGN KEY (`roleId`) REFERENCES `role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `account_role`
--

INSERT INTO `account_role` (`roleId`, `username`, `enable`) VALUES
('admin', 'acc1', 1),
('admin', 'acc2', 1),
('employee', 'acc1', 1),
('employee', 'acc2', 1),
('employee', 'acc3', 1),
('superadmin', 'acc1', 1);

Structure of Account Table

Data of Account Table

Structure of Role Table

Data of Role Table

Structure of Account_Role Table

Data of Account_Role Table




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 entities class: Account.java, Role.java, AccountRole.java and AccountRoleId.java as below

Account.java

package entities;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "account")
public class Account implements java.io.Serializable {

	private String username;
	private String password;
	private String fullname;
	private String email;
	private boolean enable;
	private Set<AccountRole> accountRoles = new HashSet<AccountRole>(0);

	@Id
	@Column(name = "username", unique = true, nullable = false, length = 250)
	public String getUsername() {
		return this.username;
	}

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

	@Column(name = "password", nullable = false, length = 250)
	public String getPassword() {
		return this.password;
	}

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

	@Column(name = "fullname", nullable = false, length = 250)
	public String getFullname() {
		return this.fullname;
	}

	public void setFullname(String fullname) {
		this.fullname = fullname;
	}

	@Column(name = "email", nullable = false, length = 250)
	public String getEmail() {
		return this.email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	@Column(name = "enable", nullable = false)
	public boolean isEnable() {
		return this.enable;
	}

	public void setEnable(boolean enable) {
		this.enable = enable;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "account")
	public Set<AccountRole> getAccountRoles() {
		return this.accountRoles;
	}

	public void setAccountRoles(Set<AccountRole> accountRoles) {
		this.accountRoles = accountRoles;
	}

}

Role.java

package entities;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "role")
public class Role implements java.io.Serializable {

	private String id;
	private String name;
	private Set<AccountRole> accountRoles = new HashSet<AccountRole>(0);

	@Id
	@Column(name = "id", unique = true, nullable = false, length = 250)
	public String getId() {
		return this.id;
	}

	public void setId(String id) {
		this.id = id;
	}

	@Column(name = "name", nullable = false, length = 250)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "role")
	public Set<AccountRole> getAccountRoles() {
		return this.accountRoles;
	}

	public void setAccountRoles(Set<AccountRole> accountRoles) {
		this.accountRoles = accountRoles;
	}

}

AccountRole.java

package entities;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "account_role")
public class AccountRole implements java.io.Serializable {

	private AccountRoleId id;
	private Account account;
	private Role role;
	private boolean enable;

	@EmbeddedId
	@AttributeOverrides({
			@AttributeOverride(name = "roleId", column = @Column(name = "roleId", nullable = false, length = 250)),
			@AttributeOverride(name = "username", column = @Column(name = "username", nullable = false, length = 250)) })
	public AccountRoleId getId() {
		return this.id;
	}

	public void setId(AccountRoleId id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "username", nullable = false, insertable = false, updatable = false)
	public Account getAccount() {
		return this.account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "roleId", nullable = false, insertable = false, updatable = false)
	public Role getRole() {
		return this.role;
	}

	public void setRole(Role role) {
		this.role = role;
	}

	@Column(name = "enable", nullable = false)
	public boolean isEnable() {
		return this.enable;
	}

	public void setEnable(boolean enable) {
		this.enable = enable;
	}

}

AccountRoleId.java

package entities;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class AccountRoleId implements java.io.Serializable {

	private String roleId;
	private String username;

	@Column(name = "roleId", nullable = false, length = 250)
	public String getRoleId() {
		return this.roleId;
	}

	public void setRoleId(String roleId) {
		this.roleId = roleId;
	}

	@Column(name = "username", nullable = false, length = 250)
	public String getUsername() {
		return this.username;
	}

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

}




Hibernate Configuration File

Puts Account.java, Role.java, AccountRole.java and AccountRoleId.java in your Hibernate configuration file, and also MySQL connection details.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                         "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.enable_lazy_load_no_trans">true</property>
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.password">123456</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/learnjavarestfulwebservices</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hibernate.current_session_context_class">thread</property>
		<mapping class="entities.Account" />
		<mapping class="entities.AccountRole" />
		<mapping class="entities.AccountRoleId" />
		<mapping class="entities.Role" />
	</session-factory>
</hibernate-configuration>

The HibernateUtil class helps in creating the SessionFactory from the Hibernate configuration file. The SessionFactory is threadsafe, so it is not necessary to obtain one for each thread.

package model;

import org.hibernate.*;
import org.hibernate.boot.*;
import org.hibernate.boot.registry.*;

public class HibernateUtil {

	private static final SessionFactory sessionFactory;

	static {
		try {
			StandardServiceRegistry standardRegistry = new
					StandardServiceRegistryBuilder()
					.configure("hibernate.cfg.xml")
					.build();
			Metadata metaData = new MetadataSources(
					standardRegistry)
					.getMetadataBuilder()
					.build();
			sessionFactory = metaData.getSessionFactoryBuilder().build();
		} catch (Throwable th) {
			throw new ExceptionInInitializerError(th);
		}
	}

	public static SessionFactory getSessionFactory() {
		return sessionFactory;

	}
}

The AccountModel class contains methods to interact with the database.

package model;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import entities.Account;

public class AccountModel {

	private SessionFactory sessionFactory = HibernateUtil.getSessionFactory();

	public Account login(String username, String password) {
		Account account = null;
		Session session = sessionFactory.openSession();
		Transaction transaction = null;
		try {
			transaction = session.beginTransaction();
			Query query = session.createQuery("from Account "
								+ "where username = :username "
								+ "and password = :password "
								+ "and enable = :enable");
			query.setString("username", username);
			query.setString("password", password);
			query.setBoolean("enable", true);
			account = (Account) query.uniqueResult();
			transaction.commit();
		} catch (Exception e) {
			account = null;
			if (transaction != null) {
				transaction.rollback();
			}
		} finally {
			session.close();
		}
		return account;
	}

}

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 entities.AccountRole;
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 (AccountRole accountRole : account.getAccountRoles()) {
					if (accountRole.isEnable() && accountRole.getRole().getName().equalsIgnoreCase(role)) {
						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
	@Produces({ MediaType.TEXT_PLAIN })
	@Path("work1")
	@PermitAll
	public Response work1() {
		try {
			return Response.ok("Work 1").build();
		} catch (Exception e) {
			return Response.status(Response.Status.BAD_REQUEST).build();
		}
	}

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

	@GET
	@Produces({ MediaType.TEXT_PLAIN })
	@Path("work3")
	@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
	@Produces({ MediaType.TEXT_PLAIN })
	@Path("work4")
	@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/SecurityRest_Server/api/demo/work1

Output

Work 1

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

Output

Access denied for this resource

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

Output

Access denied for this resource

Access restful web services use the following url: http://localhost:8080/SecurityRest_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/SecurityRest_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