A JEE6 security interceptor for Tomcat 7 02 May 2012
Security in Java EE is a well-known subject and JAAS, the framework that manages security, is stable and reliable. Nevertheless, its API is quite low level and not always very convenient. For that reason, there exist a number of frameworks that have been built on top of JAAS that provide nice abstractions and convenient tools. This is especially true for the JEE application servers such as Glassfish, JBoss and the like.
In servlet containers such as Tomcat or Jetty however, only the web components are secured and it is not trivial to secure services and the data access layer. This can be solved by integrating framework such as Spring Security, Apache Shiro.
As always it is not always possible to put a new framework or library in place. I will not discuss whether it is a good or a bad idea to develop a new security mechanism. Nevertheless, a good rule of thumb is to reuse existing components and not reinvent the wheel … when possible. If for let’s say licensing reasons, it is not possible: here is a way to provide a simple declarative authorisation procedure based on JAAS and the JEE6 interceptors. The objective is to propagate the authorisation to non-web layers such as services or data access layer through a thread-local variable.
The goal is to be able to write something similar to the following snippet. The idea is to annotate a method (or a class) with a list of roles that are authorised. If the method executes in a context in which a user has not enough rights, it raises an exception.
@Secure(roles = { "user" })
public List<Student> getAll() {
...
}
@Secure(roles = { "admin" })
public void add(final Student student) {
...
}
Java Authentication and Authorisation System
Java Authentication and Authorisation System (JAAS) provides a security infrastructure for JAVA.
Both authentication (are you who you pretend to be?) and authorisation (are you allowed to do something you would like to do?) are covered. JAAS relies on the concept of principal.
A principal is a particular identity of a user (e.g. social security id, driver licence id, …).
By default, in JAVA SE, there is no easy way to have the list of roles granted to a given user.
To solve that problem, we need a bean that knows to which role a given Principal is authorised.
This is the role of MyPrincipal
.
public class MyPrincipal implements Principal, Serializable {
public MyPrincipal(final Principal pPrincipal, final List<String> pRoles) {
this.principal = pPrincipal;
this.roles = pRoles;
}
...
public boolean isUserInRole(final String pRole) {
return roles.contains(pRole);
}
}
JEE6 Interceptors
Since JEE6, a feature called interceptors enables simple Aspect Oriented Programming without using any specific framework. It works, of course, only on managed classes. Namely, classes that are instantiated by CDI through dependency injection.
Just to fix the vocabulary, here some definition about Aspect Oriented Programming:
- Advice: code at it executed at a certain point in the code. This point is called a join point;
- Join point: place in the code at which a given advice is executed.;
- Pointcut: set of join point, usually described by a meta data that can be internal or external to the program (xml, annotations);
- Aspect: advice + its pointcut;
In Java EE6, an interceptor is made of an annotation that characterizes an aspect and an implementation for that annotation. The annotation is placed at a join point, that is a place in the code where a specific advice must be executed. Usually around a method call. However, JEE6 interceptors are not as powerful as advanced AOP frameworks such as AspectJ, it provides enough facility to decouple non-functional behaviors from the business code. That is exactly what we want to achieve here. Let us now illustrate JEE6 interceptors through the implementation of a declarative security annotation that protects calls to the service layer.
A meta data that asks for security
The first thing is to declare an annotation that is parametrized by an array of String
that
represents roles. This is presented in the following snippet. The annotation InterceptorBinding
is required to mark the annotation as a pointcut. The @Retention(RetentionPolicy.RUNTIME)
declares that Then @Target({ ElementType.METHOD, ElementType.TYPE })
tells that the annotation
can be applied at both method level and type level. Finally, the member String[] roles();
declares that the annotation has a parameter called roles
.
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Secure {
@Nonbinding
String[] roles();
}
The interceptor (advice) that check the authorisation
The next step is to program an advice. To that end, a
simple class must be annotated by the annotation @Interceptor
and by the annotation that it
implements @Secure(roles = { })
(i.e. the pointcut). The method that is annotated by
@AroundInvoke
is executed by the container when it encounters a method annotated by @Secure
(i.e. join point). The name of the method (e.g. invoke
) does not matter as long as it returns an
Object
and it accepts an InvocationContext
as a parameter. This context contains information
about the intercepted method.
About the method itself, getRoles
is a private method (given hereafter) that extract the list
of roles that are authorised to this method. The SecurityContext
is described below and is
container for the ThreadLocal
variable that contains the principal of the current user. The
method principal.isUserInRoles(roles)
returns true if one the roles matches the expected
authorisation. If it is not the case an exception is raised and the interceptor stops there without
having executed the intercepted method. Otherwise, the interceptor goes on and executes the
intercepted method and finally returns its return value by executing return context.proceed()
.
@Secure(roles = { })
@Interceptor
public class SecurityInterceptor implements Serializable {