Spring AOP pointcut that matches annotation on interface
Asked Answered
H

2

32

I have a service class implemented in Java 6 / Spring 3 that needs an annotation to restrict access by role.

I have defined an annotation called RequiredPermission that has as its value attribute one or more values from an enum called OperationType:

public @interface RequiredPermission {

/**
 * One or more {@link OperationType}s that map to the permissions required
 * to execute this method.
 * 
 * @return
 */
OperationType[] value();}

public enum OperationType {
      TYPE1,
      TYPE2;
}

package com.mycompany.myservice;
public interface MyService{
   @RequiredPermission(OperationType.TYPE1)
   void myMethod( MyParameterObject obj );
}

package com.mycompany.myserviceimpl;
public class MyServiceImpl implements MyService{
   public myMethod( MyParameterObject obj ){
       // do stuff here
   }
}

I also have the following aspect definition:

/**
 * Security advice around methods that are annotated with
 * {@link RequiredPermission}.
 * 
 * @param pjp
 * @param param
 * @param requiredPermission
 * @return
 * @throws Throwable
 */
@Around(value = "execution(public *"
        + " com.mycompany.myserviceimpl.*(..))"
        + " && args(param)" + // parameter object
        " && @annotation( requiredPermission )" // permission annotation

, argNames = "param,requiredPermission")
public Object processRequest(final ProceedingJoinPoint pjp,
        final MyParameterObject param,
        final RequiredPermission requiredPermission) throws Throwable {
    if(userService.userHasRoles(param.getUsername(),requiredPermission.values()){
        return pjp.proceed();
    }else{
        throw new SorryButYouAreNotAllowedToDoThatException(
            param.getUsername(),requiredPermission.value());
    }
}

The parameter object contains a user name and I want to look up the required role for the user before allowing access to the method.

When I put the annotation on the method in MyServiceImpl, everything works just fine, the pointcut is matched and the aspect kicks in. However, I believe the annotation is part of the service contract and should be published with the interface in a separate API package. And obviously, I would not like to put the annotation on both service definition and implementation (DRY).

I know there are cases in Spring AOP where aspects are triggered by annotations one interface methods (e.g. Transactional). Is there a special syntax here or is it just plain impossible out of the box.

PS: I have not posted my spring config, as it seems to be working just fine. And no, those are neither my original class nor method names.

PPS: Actually, here is the relevant part of my spring config:

<aop:aspectj-autoproxy proxy-target-class="false" />

<bean class="com.mycompany.aspect.MyAspect">
    <property name="userService" ref="userService" />
</bean>
Hedvig answered 17/5, 2010 at 8:40 Comment(2)
So, if I understand correctly, the best solution you found for your original question (ie, having the annotation in the interface, not in the implementation) is to match the whole package of service provider interfaces, and then introspect on the ProceedingJoinPoint in order to find out the annotations of the method in the interface? Is that even possible? This is a great thread, and don't want to start a new one with the same question! Thanks!!Amazonite
@Amazonite actually I was so frustrated that I copied all annotations from the interface to the implementing class and enforced that with a rigurous unit test that checked all methods on both interface and implementing class for identical annotations. Not exactly AOP, I know. I'd say it doesn't work reliably, after allHedvig
E
37

If I understand you correct, you want a pointcut that finds all methods in classes that extends MyService and is annotated and with the preferred arguments.

I propose that you replace:

execution(public * com.mycompany.myserviceimpl.*(..))

with:

execution(public * com.mycompany.myservice.MyService+.*(..))

The plus sign is used if you want a joinpoint to match the MyService class or a class that extends it.

I hope it helps!

Epidermis answered 17/5, 2010 at 10:10 Comment(3)
There is a small typo. it is actually: execution(public * com.mycompany.myservice.MyService+.*(..)) But it works like a charm. Thanks a lot!!!Hedvig
That's nice! I will correct my answer to include a wildcard on all methods as well.Epidermis
I realized much later that this is not entirely correct (although this is probably the best bet there is). I wanted the annotation to be on the service interface method, but this only works if the annotation is on the implementing method. And since I also need this behavior: #3100728 , I have to keep the pointcut minimal and check the proceedingjoinpoint object myself.Hedvig
S
6

Espen, your code works only for one class:

execution(public * com.mycompany.myservice.MyService+.*(..))

but what if I want this behaviour for all services in *com.mycompany.services.** package?

Sauncho answered 18/5, 2010 at 8:44 Comment(1)
this works fine: execution(public * com.mycompany.myservice.*+.*(..))Hedvig

© 2022 - 2024 — McMap. All rights reserved.