lundi 23 avril 2012

Annotation-based authorization with Guice Servlet & AOP

Most web applications use an authorization system to make sure the user is logged in and authorized to execute specific procedures. For example, a simple user will only have read access, while an admin can edit and delete. For every server endpoint, we ask ourselves : "Who has the privilege to access that service ?"

For such cross-cutting concerns, we are told that AOP is the key. Intercepting the methods we want to secure and checking authorization levels in a separate security aspect...
One thing that bothers me with such an approach is that when I want to check the access level of a method, I have to go look in the security aspect to see how it was intercepted. I'd rather have a declarative way to explicit the access level of a method, like an annotation :

@AuthorizationRequired(Role.ADMIN)
public void securedMethod() {}

Implementing such a system is deceptively simple using Guice AOP once you have installed Guice Servlet to enable injection of servlet related objects, like HttpSession.

First, we need to create the annotation :

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorizationRequired{
   Role value();
}

Then we implement the method interceptor :

public class AuthorizationInterceptor implements MethodInterceptor {

   @Inject private Provider<HttpSession> sessionProvider;

   @Override
   public Object invoke(MethodInvocation invoc) throws Throwable {
      Role required = invoc.getMethod().getAnnotation(AuthorizationRequired.class).value();
      User user = getUserFromSession(sessionProvider.get());
      if (isUserAuthorized(user, required))
         return invoc.proceed();
      else
         throw new UnauthorizedException("User is not authorized");
   }
}

Obviously, to get the logged in user you need the session. Thanks to Guice Servlet magic, you can inject a session provider in any object.

Finally, we bind the interceptor in the Guice ServletModule :

AuthorizationInterceptor interceptor = new AuthorizationInterceptor ();
requestInjection(interceptor);
bindInterceptor(classMatcher, annotatedWith(AuthorizationRequired.class), interceptor );

Don't forget that only objects instantiated by Guice will have their methods intercepted.

Aucun commentaire:

Enregistrer un commentaire