Securing REST APIs with RESTEasy Filter

Lokesh Gupta

Learn to use RESTEasy ContainerRequestFilter to create security filter which is able to to perform authentication and authorization on RESTEasy based web applications.

1. RESTEasy ContainerRequestFilter and ContainerReponseFilter

The new RESTEasy version 3.0.2 final has been recently released and is made compatible with JAX-RS 2.0. If you remember that previous JAX-RS releases had no specification regarding implementing filters and interceptors. That’s why all JAX-RS implementations had their own flavors. RESTEasy had PreProcessorInterceptor and PostProcessorInterceptor which are deprecated now.

Now JAX-RS has it’s own specification around filters and interceptors. You can read a detailed discussion by this post by Bill Burke.

In resteasy, Filters are run before and after the resource method is invoked. These filters are essentially, ContainerRequestFilter and ContainerReponseFilter. ContainerRequestFilters run before your JAX-RS resource method is invoked. ContainerResponseFilters run after your JAX-RS resource method is invoked. As an added caveat, ContainerRequestFilters come in two flavors: pre-match and post-matching. Pre-matching ContainerRequestFilters are designated with the @PreMatching annotation and will execute before the JAX-RS resource method is matched with the incoming HTTP request. Post matching ContainerRequestFilters execute after the Java resource method has been matched.

While filters modify request or response headers, interceptors deal with message bodies. They can be used to implement a specific content-encoding. They can be used to generate digital signatures or to post or pre-process a Java object model before or after it is marshalled.

2. RESTEasy ContainerRequestFilter Example

In this post, I am modifying the resteasy authentication and authorization tutorial which was originally written in RESTEasy “2.3.1.GA” using PreProcessorInterceptor. I have updated it to RESTEasy version “3.0.2.Final” which is build on JAX-RS 2.0 specification.

2.1. Update maven dependencies

As I am using maven, I have updated the pom.xml file as below. If you are using ant or jar file, then update the required jars accordingly.

<dependencies>
 <!-- core library -->
 <dependency>
	<groupId>org.jboss.resteasy</groupId>
	 <artifactId>resteasy-jaxrs</artifactId>
	<version>3.0.2.Final</version>
 </dependency>
<!-- JAXB support -->
<dependency>
  <groupId>org.jboss.resteasy</groupId>
	<artifactId>resteasy-jaxb-provider</artifactId>
  <version>3.0.2.Final</version>
</dependency>
<dependency>
	<groupId>org.jboss.resteasy</groupId>
	<artifactId>jaxrs-api</artifactId>
	<version>3.0.2.Final</version>
</dependency>
<dependency>
	<groupId>net.sf.scannotation</groupId>
	<artifactId>scannotation</artifactId>
	<version>1.0.3</version>
</dependency>
</dependencies>

2.2. RESTEasy SecurityInterceptor

As JAX-RS 2.0 has filters for pre and post request handling, we will be using ContainerRequestFilter interface. Remember PreProcessorInterceptor is deprecated now.

@Provider
public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter
{
    @Override
	public void filter(ContainerRequestContext requestContext)
	{
		//More code...
	}
}

Now, first we have to access the resource method to check the security constraints and attributes it define.

	ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) 
				requestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
	Method method = methodInvoker.getMethod();

Now we have the access to resource method. Now everything will be same as we were doing it previously. i.e.

  • Check PermitAll annotation, if it is present then no need to check anything further
  • Check DenyAll annotation, if it is present then return with access-denied
  • Check RolesAllowed annotation, and fetch the roles required from annotation. Get authorization information from request and match it as per application logic. If authorization is successful, give the access otherwise return access-denied.

2.3. RESTEasy SecurityInterceptor sourcecode

Complete code for SecurityInterceptor is as follow.

package com.howtodoinjava.demo.rest.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.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.util.Base64;

/**
 * This interceptor verify the access permissions for a user 
 * based on username and passowrd provided in request
 * */
@Provider
public class SecurityInterceptor implements javax.ws.rs.container.ContainerRequestFilter
{
	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 void filter(ContainerRequestContext requestContext)
	{
		ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
		Method method = methodInvoker.getMethod();
		//Access allowed for all 
		if( ! method.isAnnotationPresent(PermitAll.class))
		{
			//Access denied for all 
			if(method.isAnnotationPresent(DenyAll.class))
			{
				requestContext.abortWith(ACCESS_FORBIDDEN);
				return;
			}
			
			//Get request headers
			final MultivaluedMap<String, String> headers = requestContext.getHeaders();
			
			//Fetch authorization header
		    final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
		    
		    //If no authorization information present; block access
		    if(authorization == null || authorization.isEmpty())
		    {
		    	requestContext.abortWith(ACCESS_DENIED);
		    	return;
		    }
		    
		    //Get encoded username and password
		    final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
		    
		    //Decode username and password
		    String usernameAndPassword = null;
			try {
				usernameAndPassword = new String(Base64.decode(encodedUserPassword));
			} catch (IOException e) {
				requestContext.abortWith(SERVER_ERROR);
				return;
			}

			//Split username and password tokens
		    final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
		    final String username = tokenizer.nextToken();
		    final String password = tokenizer.nextToken();
		    
		    //Verifying Username and password
		    System.out.println(username);
		    System.out.println(password);
			
		    //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))
				{
					requestContext.abortWith(ACCESS_DENIED);
					return;
				}
			}
		}
	}
	private boolean isUserAllowed(final String username, final String password,	final Set<String> rolesSet) 
	{
		boolean isAllowed = false;
		
		//Step 1. Fetch password from database and match with password in argument
		//If both match then get the defined role for user from database and continue; else return isAllowed [false]
		//Access the database and do this part yourself
		//String userRole = userMgr.getUserRole(username);
		String userRole = "ADMIN";
		
		//Step 2. Verify user role
		if(rolesSet.contains(userRole))
		{
			isAllowed = true;
		}
		return isAllowed;
	}
	
}

2.4. RESTEasy security filter demo

To test the security code, deploy the web application in any application server like Tomcat. Now, send following requests:

  • HTTP GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1 without username and password

    User is able to access the API successfully.

    resteasy authorization test get api

  • HTTP PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1 without username and password

    User is not able to access the API.

    resteasy authorization test get api

  • Add basic authorization credentials

    Add basic authorization credentials

  • HTTP PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1 with username and password added

    User is able to access protected API

    resteasy authorization test put api 2

That’s all in resteasy security interceptor example. If you have any query or suggestion, drop me a comment.


Update: Below are the steps to run this project in tomcat 7.

Today, I again worked on this project to run on tomcat 7. To run successfully, I did following steps:

– Import the project in eclipse
– Run Prompt > mvn eclipse:eclipse -Dwtpversion=2.0 in project root folder [Reference]
Update the @Produces and @Consumes annotations on methods
– Start the tomcat server and test the application. You will get desired results.

Happy Learning !!

Comments

Subscribe
Notify of
guest
69 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode