Java Libraries
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 Database
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 Server Project
Create Dynamic Web Project in Eclipse. Copy all jar files above to the lib directory in the project
Create web.xml
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>
Entities Class
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>
Create HibernateUtil class
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;
}
}
Create AccountModel class
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;
}
}
Interceptor Class
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
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();
}
}
}
Testing Restful Web Services
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
Consume Restful Web Services from Java Application
Create Java Project in Eclipse. Add all jar files above to the project
Create DemoRestClientModel
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;
}
}
Run It
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);
}
}
Output
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