Friday, 20 April 2012

Spring security - @PreAuthorize("hasPermission()")

the role checking is cool in spring security, by annotating we can restrict access to method calls in the controller or service tiers (or both). I'll leave it up to you to decide where (but if you need to restrict access to resources, then restrict it at the resource level!)

there is a way to hook into the "hasPermission" expression language and do really cool stuff see spring security by example for a good example. but this is only available if we compile with debug. Surely we can enhance things so we don't need this debug map around...

It turns out there is!

But how?.... let me explain....

I'm aiming for something like (to use the example form the linked blog)
@PreAuthorize("hasPermission(#myParam,'isDirector')")
        public void exampleSecuredMethod( @SpelParameter("myParam") long parameter )
        {
            ...some code in here to do something secure
        }

First we need a new annotation that is retained at runtime

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Documented
public @interface SpelParameter {

    String value() default "";
}

then we need a new ParameterNameDiscoverer
public class SpelParameterMethodNameDiscoverer implements ParameterNameDiscoverer
{
     public String[] getParameterNames( Method method )
     {
        List< String > foundParameters = new ArrayList< String >();
        Annotation[][] parameterAnnotationArrays = method.getParameterAnnotations();
        for( Annotation[] parameterAnnotations : parameterAnnotationArrays )
        {
            for( Annotation parameterAnnotation : parameterAnnotations )
            {
                if( parameterAnnotation instanceof SpelParameter )
                {
                    foundParameters.add( ( (SpelParameter)parameterAnnotation ).value() );
                }
            }
        }
        return foundParameters.isEmpty() ? null : foundParameters.toArray( new String[ foundParameters.size() ] );
     }

    /**
     * currently we don't support permissioning on contructors
     */
    public String[] getParameterNames( Constructor arg0 )
    {
        return null;
    }
}

and finally an evaluation context which will look very much like the org.springframework.security.access.expression.method.MethodSecurityEvaluationContext
public class SpelParameterMethodSecurityEvaluationContext extends StandardEvaluationContext
{
    private ParameterNameDiscoverer parameterNameDiscoverer;
    private boolean                 argumentsAdded;
    private MethodInvocation        mi;

    public SpelParameterMethodSecurityEvaluationContext( Authentication user, MethodInvocation mi )
    {
        this( user, mi, new LocalVariableTableParameterNameDiscoverer() );
    }

    public SpelParameterMethodSecurityEvaluationContext( Authentication user, MethodInvocation mi, ParameterNameDiscoverer parameterNameDiscoverer )
    {
        this.mi = mi;
        this.parameterNameDiscoverer = parameterNameDiscoverer;
    }

    @Override
    public Object lookupVariable( String name )
    {
        Object variable = super.lookupVariable( name );
        if( variable != null )
        {
            return variable;
        }

        if( !argumentsAdded )
        {
            addArgumentsAsVariables();
            argumentsAdded = true;
        }

        return super.lookupVariable( name );
    }

    public void setParameterNameDiscoverer( ParameterNameDiscoverer parameterNameDiscoverer )
    {
        this.parameterNameDiscoverer = parameterNameDiscoverer;
    }

    private void addArgumentsAsVariables()
    {
        Object[] args = mi.getArguments();
        Object targetObject = mi.getThis();
        Method method = ClassUtils.getMostSpecificMethod( mi.getMethod(), targetObject.getClass() );

        List< String > paramNames = Arrays.asList( parameterNameDiscoverer.getParameterNames( method ) );

        // this might be slow as we are looping over each parameter... if so
        // cache locations and parameter names
        Annotation[][] parameterArrayAnnotations = mi.getMethod().getParameterAnnotations();
        for( int parameterIndex = 0; parameterIndex < parameterArrayAnnotations.length; parameterIndex++ )
        {
            Annotation[] annotationsOnParameter = parameterArrayAnnotations[ parameterIndex ];
            for( final Annotation annotation : annotationsOnParameter )
            {
                if( annotation instanceof SpelParameter && paramNames.contains( ( (SpelParameter)annotation ).value() ) )
                {
                    this.setVariable( ( (SpelParameter)annotation ).value(), args[ parameterIndex ] );
                }
            }
        }

    }

}
now finally the spring config for the security expression language evaluator



        
  
 


  
 

 
  
   
                                          
   
  
 

 
  
  
   
  

 



you'll detect that this xml is aimed at my test class, and not production stuff, but its the same thing.

And so we can do the things that JAKUB NABRDALIK suggested in his blog, but now with the parameters availble at runtime WITHOUT the debug information around.

How cool is that?