animesh kumar

Running water never grows stale. Keep flowing!

Securing, Versioning and Auditing REST (JAX-RS, Jersey) APIs

with 27 comments

Now that your functionalities are working, you want a layer of security to authenticate/authorize your APIs. Though this is a bad approach towards security, but – I know – real life is a tough game and nothing happens they way they should be… and so be it. Additionally, you might want to control API versions (i.e. expose newer APIs only to newer clients) and audit API usage.

Well, I am going to propose a tangential way to implement these concerns. You won’t need to touch any of your business logic as such. Only few annotations (custom and otherwise) would need to be applied. That way, you won’t feel bad about missing these things when you started the project and your concerns will be taken care of in the most un-obtrusive way possible. Wonderful… eh?

First, you will need to create some sort of sign-in API, which will accept username/password (or oAuth or whatever you fancy) and generate some sort of session information which you will store in some database (Redis maybe!) and share its ID, say sessionId, with client. Then, with every subsequent request, Client will attach this sessionId in the request header which server will pick and look up for associated session information (permission, roles etc.) and based upon that server will authenticate and authorize the request.

Here is your Session bean.

package com.strumsoft.api;

import java.io.Serializable;
import java.util.Date;

/**
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 *
 */
public class Session implements Serializable {
	// 
	private static final long serialVersionUID = -7483170872690892182L;
	
	private String sessionId;   // id
	private String userId;      // user
	private boolean active;     // session active?
	private boolean secure;     // session secure?

	private Date createTime;    // session create time
	private Date lastAccessedTime;  // session last use time

	// getters/setters here
}

And this is your User bean. This must implement java.security.Principal.


package com.strumsoft.api;

import java.util.Set;

/**
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 *
 */
public class User implements java.security.Principal {
	// Role
	public enum Role {
		Editor, Visitor, Contributor
	};

	private String userId;          // id
	private String name;            // name
	private String emailAddress;    // email
	private Set<Role> roles;        // roles

	@Override
	public String getName() {
		return null;
	}

	// getters/setters here
}

Now, you need to implement javax.ws.rs.core.SecurityContext. This will be bound to incoming request and will decide whether to allow or deny it.

package com.strumsoft.api;

import java.security.Principal;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

/**
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 *
 */
public class MySecurityContext implements javax.ws.rs.core.SecurityContext {

	private final User user;
	private final Session session;

	public MySecurityContext(Session session, User user) {
		this.session = session;
		this.user = user;
	}

	@Override
	public String getAuthenticationScheme() {
		return SecurityContext.BASIC_AUTH;
	}

	@Override
	public Principal getUserPrincipal() {
		return user;
	}

	@Override
	public boolean isSecure() {
		return (null != session) ? session.isSecure() : false;
	}

	@Override
	public boolean isUserInRole(String role) {

		if (null == session || !session.isActive()) {
			// Forbidden
			Response denied = Response.status(Response.Status.FORBIDDEN).entity("Permission Denied").build();
			throw new WebApplicationException(denied);
		}

		try {
			// this user has this role?
			return user.getRoles().contains(User.Role.valueOf(role));
		} catch (Exception e) {
		}
		
		return false;
	}
}

Then, you need a ResourceFilter which will intercept the request, look for sessionId in the header and generate and attach our SecurityContext implementation to it. Notice, how our implementation only gets applied on Request but not on Response.

package com.strumsoft.api;

import javax.ws.rs.ext.Provider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.flockthere.api.repository.SessionRepository;
import com.flockthere.api.repository.UserRepository;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;

/**
 * Filter all incoming requests, look for possible session information and use that
 * to create and load a SecurityContext to request. 
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 * 
 */
@Component   // let spring manage the lifecycle
@Provider    // register as jersey's provider
public class SecurityContextFilter implements ResourceFilter, ContainerRequestFilter {

	@Autowired
	private SessionRepository sessionRepository;  // DAO to access Session

	@Autowired
	private UserRepository userRepository;  // DAO to access User

	
	@Override
	public ContainerRequest filter(ContainerRequest request) {
		// Get session id from request header
		final String sessionId = request.getHeaderValue("session-id");

		User user = null;
		Session session = null;

		if (sessionId != null && sessionId.length() > 0) {
			// Load session object from repository
			session = sessionRepository.findOne(sessionId);
			
			// Load associated user from session
			if (null != session) {
				user = userRepository.findOne(session.getUserId());
			}
		}

		// Set security context
		request.setSecurityContext(new MySecurityContext(session, user));
		return request;
	}

	@Override
	public ContainerRequestFilter getRequestFilter() {
		return this;
	}

	@Override
	public ContainerResponseFilter getResponseFilter() {
		return null;
	}
}

Okay, the hard part is over. All we need now is a way to fire our SecurityContextFilter. For this, we will create a ResourceFilterFactory. During application startup, this factory will create a List of filters for all AbstractMethods of each of our Resources. We are going to extend RolesAllowedResourceFilterFactory which will generate all Role based ResourceFilters for us. And then, we will add our SecurityContextFilter on the top of the list with VersionFilter and AuditFilter in the bottom. That way, SecurityContextFilter will executed first because you need to make auth decisions early. VersionFilter will be next. And Audit in the bottom. You want to audit when everything else has been done. No?

package com.strumsoft.api;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.ext.Provider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.flockthere.api.AllowAllVersions;
import com.flockthere.api.audit.Audit;
import com.flockthere.api.resource.interceptor.AuditingFilter;
import com.flockthere.api.resource.interceptor.SecurityContextFilter;
import com.flockthere.api.resource.interceptor.VersionFilter;
import com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory;
import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ResourceFilter;

/**
 * FilterFactory to create List of request/response filters to be applied on a particular
 * AbstractMethod of a resource.
 * 
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 * 
 */
@Component // let spring manage the lifecycle
@Provider  // register as jersey's provider
public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory {

	@Autowired
	private SecurityContextFilter securityContextFilter;

	// Similar to SecurityContextFilter to check incoming requests for API Version information and
	// act accordingly
	@Autowired
	private VersionFilter versionFilter;

	// Similar to SecurityContextFilter to audit incoming requests
	@Autowired
	private AuditingFilter auditingFilter;

	@Override
	public List<ResourceFilter> create(AbstractMethod am) {
		// get filters from RolesAllowedResourceFilterFactory Factory!
		List<ResourceFilter> rolesFilters = super.create(am);
		if (null == rolesFilters) {
			rolesFilters = new ArrayList<ResourceFilter>();
		}

		// Convert into mutable List, so as to add more filters that we need
		// (RolesAllowedResourceFilterFactory generates immutable list of filters)
		List<ResourceFilter> filters = new ArrayList<ResourceFilter>(rolesFilters);

		// Load SecurityContext first (this will load security context onto request)
		filters.add(0, securityContextFilter);

		// Version Control?
		filters.add(versionFilter);

		// If this abstract method is annotated with @Audit, we will apply AuditFilter to audit
		// this request.
		if (am.isAnnotationPresent(Audit.class)) {
			filters.add(auditingFilter);
		}

		return filters;
	}
}

VersionFilter will help control Client’s access to API methods based upon client’s version. Implementation would be similar to SecurityContextFilter. You will need to annotate API methods with @Version(“>1.3″). VersionFilter will read this value (“>1.3″), check request headers for API-Version keys and then decide whether to allow or reject the request. Similarly, AuditFilter will log all such annotated (@Audit(“audit-note”)) API methods. I will not discuss their actual implementations. You can very easily write them based upon your requirement or can remove them altogether if not needed.

Here is how these annotations will look like.

// @Version
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface Version {
	String value();
}

// @Audit
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface Audit {
	String value();
}

And now, you will need to update you web.xml to include ResourceFilterFactory

...
	<servlet>
		<servlet-name>REST</servlet-name>
		<servlet-class>
			com.sun.jersey.spi.spring.container.servlet.SpringServlet
		</servlet-class>
		...		
		<!-- Our resource filter for tangential concerns (security, logging, version etc.) -->
		<init-param>
			<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
			<param-value>com.strumsoft.api.ResourceFilterFactory</param-value>
		</init-param>
		...		
	</servlet>
...

That’s it. All configuration is done. Now, you just need to annotate you Resources. Let’s see how.

Say, you have a BookResource which exposes APIs to create/edit/list books. And you want
1. “Editor/Contributor” to be able to create a book, (@RolesAllowed({ “Editor”, “Contributor” }))
2. Only “Editor” to be able to edit a book, (@RolesAllowed({ “Editor” }))
3. While, anyone can see all the books. (@PermitAll)

Also, let’s say editing a book was released in API 1.3, so any client still using older APIs should not be able to update (@Version(“>1.3″)) an existing book. (Assuming you implemented VersionFilter properly)

Additionally, create or edit book should be audited with respective notes “create-book” and “edit-book” given your AuditFilter is in place.

package com.strumsoft.api;

import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;

import com.flockthere.api.audit.Audit;

/**
 * Book Resource
 * 
 * @Path("/book/")
 * @author "Animesh Kumar <animesh@strumsoft.com>"
 * 
 */
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public interface BookResource {

	// Only Editor and Contributor can create a book entry
	@javax.annotation.security.RolesAllowed({ "Editor", "Contributor" })
	@Audit("create-book")  // Audit
	@POST
	public Book createBook(@Context SecurityContext sc, @FormParam("title") String title,
			@FormParam("author") String author, @FormParam("publisher") String publisher,
			@FormParam("isbn") String isbn, @FormParam("edition") String edition);

	// Only Editor can edit an existing book entry
	@javax.annotation.security.RolesAllowed({ "Editor" })
	@Audit("edit-book")   // Audit
	@Version(">1.3")  // Available only on API version 1.3 onwards
	@PUT
	public Book editBook(@Context SecurityContext sc, @FormParam("title") String title,
			@FormParam("author") String author, @FormParam("publisher") String publisher,
			@FormParam("isbn") String isbn, @FormParam("edition") String edition);
	
	// Anyone can see these books
	@javax.annotation.security.PermitAll
	@GET
	public Book listBooks();

}

And at last, you will need to add jsr250-api to your project dependencies which defines javax.annotation.security.* annotations.

	<!-- securty tags: javax.annotation.security.* (@RolesAllowed, @PermitAll, @DenyAll etc.) -->
	<dependency>
		<groupId>javax.annotation</groupId>
		<artifactId>jsr250-api</artifactId>
		<version>1.0</version>
	</dependency>

Chill. :)

About these ads

Written by Animesh

March 2, 2012 at 7:19 pm

Posted in Technology

Tagged with , , , , ,

27 Responses

Subscribe to comments with RSS.

  1. pretty cool article, thank you for sharing.

    ronischuetz

    March 26, 2012 at 5:26 pm

  2. Hello animesh,

    Congratulations by the article, it’s fantastic. I’m really interested in this example. Could you put here the example project?.

    Thank you very much.

    David S

    March 27, 2012 at 1:10 am

    • Thanks David. Well, actually this is a pretty large project. I would need to extract relevant classes and build a dummy project. I will do that in 2-3 days and share with you.

      -Animesh

      Animesh

      March 27, 2012 at 11:23 am

      • Thanks!!!

        -David

        David S

        April 3, 2012 at 1:16 pm

      • Hello animesh,

        It is really great article! well done :), can you please be kind enough and share this example project you sent to David?

        Amit B.

        April 18, 2012 at 3:15 pm

      • Thanks Amit. I haven’t yet found time to do this dummy sample project yet. I have been dead stuck into some urgent deliveries for few weeks. Hopefully, I will do this this weekend and share on github.

        - Animesh

        Animesh

        April 18, 2012 at 4:32 pm

      • Have you posted the sample project in demo ?

        Silver Mist

        January 30, 2013 at 12:07 pm

      • Great article Animesh
        I’m searching how to make any authentications fo rest project.
        Could You send me demo of this project, I really want to know how to implements this. Thanks

        Mirek

        April 9, 2013 at 4:01 pm

      • This authentication mechanism was part of a big project, and I haven’t got a chance to separate the relevant sections out. Looks like, I should do it.

        Animesh

        April 29, 2013 at 8:15 am

    • Great article and +1 for a sample project. Did you get a chance to post it to Github?

      tom

      October 17, 2012 at 8:36 am

      • I’m searching how to make any authentications fo rest project.
        Have You get demo of this project, I really want to know how to implements this. Could You send me a demo of this project. Thanks

        Mirek

        April 9, 2013 at 3:59 pm

  3. Many thanks,
    Amit.

    Amit B.

    April 18, 2012 at 5:11 pm

  4. Hello Animesh,
    Really nice article.. I have learned lots of stuff from this article. Thanks a lot.
    Could you please put some sample code for version filter and Audit Filter?

    Saurav

    June 27, 2012 at 9:45 pm

  5. Thanks Animesh for a fantastic article. I just to know is there any way we can pass @Context to Filter class?

    Sheeraz Junejo

    July 19, 2012 at 9:57 pm

  6. It’s a nice article , but still many of the things still black box for me can u pls share an example….

    Samir

    July 22, 2012 at 1:52 pm

  7. Reblogged this on BrainStorm and commented:
    Worthy to read but unnecessary engagement of Spring Framework smells bad.

    Puspendu Banerjee

    August 14, 2012 at 2:27 am

  8. Nice article. Is it possible to share some examples of how clients will pass roles and version in their invocation?

    Mahesh Venkat

    September 16, 2012 at 7:50 am

  9. Great post animesh. I found that Versioning concept with the Version annotation and the filter very interesting. Wonder if you have any thoughts to the below.

    I am looking into how best should we do versioning for JAX-RS resources. I am not talking about if the version number should be in te URI or request header debate but more on how to architecture the backend java objects.

    For example, lets say we have a CustomerDTO and it has a single email property. After awhile, the requirement changes to allow it to have a list of email address. So what I would do is create a new email list property in the DTO and update it with the list of email address. To keep backward compatibility, I would take the first email in this list and update the old single email property. Now I was thinking of annotating these two properties with the Version annotation and annotate the single email with V1 and the new email list property with V2.

    Then the filter will function as the mediation layer and based on the request, accordingly respond by stripping off the properies that is not part of the requested version.

    Maybe for larger systems, we could replace the filters with a mediation engine (maybe a standalone or an ESB)

    Is this a good design? How do people handle versioning objects in the JAX-RS world

    Cheers
    Travis

    Travis De Slva

    October 16, 2012 at 5:06 am

  10. Hi,

    Very nice article. What I’m wondering though is when the session-id and user object actually get set and how you are getting the actual logged in user.

    It would be nice if you could elaborate a bit on this.

    tx

    Hans

    Hans

    October 17, 2012 at 12:55 pm

  11. Hi,

    i was looking for this kind of implementation.
    Can you tell me how i can implement that without spring?

    i’ve created a securityContextFilter and modified my web.xml
    like this:

    REST API
    com.sun.jersey.spi.container.servlet.ServletContainer

    import com.sun.jersey.spi.container.ResourceFilter
    de.moviemania.Model.Filter.SecurityContextFilter

    com.sun.jersey.config.property.packages
    de.myrestapi

    1

    but it does’nt work.

    acid2k42

    November 20, 2012 at 2:36 am

  12. Hi there,

    Your article helped me a lot, although I was using MSSL and client certificate (SN) for authentication and authorization purposes. The adjustments I did was the Principal (getName to return the SN of the certificate), SecurityContext (to store the new Principal) and one additional ContainerRequestFilter (to set the security context by reading certificate’s name from HttpServletRequest).
    Thank you for sharing the above!

    Regards,
    P.

    phalgus

    February 6, 2013 at 7:24 pm

  13. Hi there,

    The article is very good but i have question I’m getting issue when in trying to inject DAO in SecurityFilter in order to validate apiKey(session-id) in SecurityContextFilter class. Can you privide or point me to the GitHub location to download code.

    Regards,
    Ravi

    Ravi Reddy

    March 2, 2013 at 3:40 am

  14. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each
    time a comment is added I get four emails with the same
    comment. Is there any way you can remove me
    from that service? Cheers!

    Pyjama Bébé

    March 14, 2013 at 3:42 am

  15. Hi there, i need implement a simil solution for soap web services, with cxf in jboss server but not have success with the configuration. Can you recommend any forum or tutorial?. Thanks

    Nicolas

    August 2, 2013 at 6:19 pm

  16. […] I’m using the @PermitAll and @RolesAllowed annotation to secure my methods. Based on this example http://anismiles.wordpress.com/2012/03/02/securing-versioning-and-auditing-rest-jax-rs-jersey-apis/ […]

  17. Hi Animesh,
    Great artical, I was searching for the same for a long time.
    Can you please let me know where can I find “com.flockthere.api.*”, is it publicly available if not can you please provide its jar?

    Thanks in advance

    Ashfaq

    February 21, 2014 at 12:37 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 205 other followers

%d bloggers like this: