views:

739

answers:

3

Hello,

this is my first post here, so I apologize in advance for any stupidity on my side.

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.

Thanks in advance, Sean

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>
+8  A: 

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!

Espen
There is a small typo. it is actually: execution(public * com.mycompany.myservice.MyService+.*(..))But it works like a charm. Thanks a lot!!!
seanizer
That's nice! I will correct my answer to include a wildcard on all methods as well.
Espen
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: http://stackoverflow.com/questions/3100228/spring-aop-pointcut-expression-to-access-method-return-type , I have to keep the pointcut minimal and check the proceedingjoinpoint object myself.
seanizer
+1  A: 

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?

pkmk
this works fine:execution(public * com.mycompany.myservice.*+.*(..))
seanizer
A: 

I have run into the same issue, however the suggestions above don't help. If I only have this as a pointcut:

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

Then the advice is invoked. However, if I add this:

   && @annotation(x.y.z.Logged)

Then the advice is not invoked. The MyService interface contains a @Logged annotation on one of its methods and even on its type declaration.

seanizer
yup, that's right. The annotation has to be on the implementing method, not the service interface method.
seanizer