In this post we will show a example of simple and clean way to check your users permissions across your application using aspect-oriented programming (AOP).
In computing, AOP is a programming paradigm which aims to increase modularity by allowing the separation of cross-cutting concerns. AOP forms a basis for aspect-oriented software development. Read more about it here.
This example is extracted from a web aplication project build on dependency injection framework Google Guice. To compliment dependency injection, Guice supports method interception that divides a problem into aspects rather than objects. This feature enables you to write code that is executed each time a matching method is invoked. It’s suited for cross cutting concerns (“aspects”), such as transactions, security and logging.
Now lets define our simple user base with this three types:
public enum UserType {
ADMIN,
CUSTOMER,
GUEST;
}
To mark select methods that need user validation, we define an annotation:
import com.google.inject.BindingAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@BindingAnnotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresUserType {
UserType value();
}
…and apply it to the methods that need to be intercepted:
public class ProductService implements IProductService {
@RequiresUserType(CUSTOMER)
public Product view(Key key) {
...
}
@RequiresUserType(ADMIN)
public void delete(Key key) {
...
}
}
Next, we define the interceptor by implementing the org.aopalliance.intercept.MethodInterceptor interface. When we need to call through to the underlying method, we do so by calling invocation.proceed():
public class UserValidationInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
final UserType requiredType = invocation.getMethod().getAnnotation(RequiresUserType.class).value();
//cross check current user type and throw an exception if validation fails
...
//if everything checks out proceed to method execution
return invocation.proceed();
}
}
Finally, we configure everything. This is where we create matchers for the classes and methods to be intercepted. In this case we match any class, but only the methods with our @RequiresUserType annotation:
import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;
public class SecurityModule extends AbstractModule {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.annotatedWith(RequiresUserType.class), new UserValidationInterceptor());
}
}
The method interceptor API implemented by Guice is a part of a public specification called AOP Alliance. This makes it possible to use the same interceptors across a variety of frameworks.