Tuesday, October 1, 2013

Grails: Calling bean methods in Spring Security expressions

Some days ago while working on a Grails application I was in a situation where I wanted to call a bean method from a Spring security SPEL expression. I was using the @PreAuthorize annotation from the Spring Security ACL plugin and wanted to do something like this:
@PreAuthorize("myService.canAccessUserProfile(#profileId)")
public Profile getUserProfile(long profileId) {
  ...
}
@PreAuthorize takes a SPEL expression as parameter that is evaluated to see if the currently logged in user is allowed to access the getUserProfile() method. Within this SPEL expression I wanted to call the canAccessUserProfile() method of myService for performing the security check. In the following I will explain which steps are necessary to make this working.

Luckily it is possible to reference beans from SPEL expressions by prefixing their name with an @ symbol:
@PreAuthorize("@myService.canAccessUserProfile(#profileId)")
However, this change alone won't work out of the box and we have to do some small adjustments on the Spring Security configuration.

The SPEL expression parser (see: SpelExpressionParser) that is used to parse the security expression delegates the lookup of beans to a BeanResolver. In order to make the security expression from above work we have to create a BeanResolver implementation and add it to the Spring Security configuration.

Creating a BeanResolver for Grails is very simple:
class GrailsBeanResolver implements BeanResolver {

  GrailsApplication grailsApplication

  @Override
  public Object resolve(EvaluationContext evaluationContext, String beanName) throws AccessException {
    return grailsApplication.mainContext.getBean(beanName)
  }
}
We only have to implement the resolve() method to satisfy the BeanResolver interface. In our example we delegate this job to the bean factory of our Grails application. So, we can access all available beans within security expressions.

Now we have to add our bean resolver to the SPEL evaluation context (see: EvaluationContext). This can be done by overriding the createEvaluationContext() of Spring Securities DefaultMethodSecurityExpressionHandler:
class GrailsExpressionHandler extends DefaultMethodSecurityExpressionHandler {

  BeanResolver beanResolver

  @Override
  public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation method) {
    StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(auth, method)
    context.setBeanResolver(beanResolver)
    return context;
  }
}
As the name suggests createEvaluationContext() is responsible for creating the evaluation context for security expressions. The only thing we do is adding a beanResovler after the EvaluationContext has been created.

After that, we have to configure our two new beans in grails-app/conf/spring/resources.groovy using the Spring Bean DSL:
beans = {
  expressionHandler(GrailsExpressionHandler) {
    beanResolver              = ref('beanResolver')
    parameterNameDiscoverer   = ref('parameterNameDiscoverer')
    permissionEvaluator       = ref('permissionEvaluator')
    roleHierarchy             = ref('roleHierarchy')
    trustResolver             = ref('authenticationTrustResolver')  
  }

  beanResolver(GrailsBeanResolver) {
    grailsApplication = ref('grailsApplication')
  }
}
Normally the expressionHandler bean would be an instance of DefaultMethodSecurityExpressionHandler as mentioned above. Since we want Spring
Security to use our GrailsExpressionHandler we have to override the expressionHandler bean. The only new dependency we added is the beanResolver property. The other four dependencies of expressionHandler are required by DefaultMethodSecurityExpressionHandler (the base class of GrailsExpressionHandler). These dependencies are already provided by the Spring Security plugins.

Now it should be possible to reference beans using the @ prefix and call their methods in security expressions.

Share this post using Facebook, Twitter or Google+

8 comments:

  1. Hi Michael Scharhag,

    Thanks for writing a detailed blog. I am in the same situation. I followed the steps you have mentioned, but @PreAuthorize("@securityService.canDeleteJob()") is not calling.

    securityService is a grails service and contains canDeleteJob method, which only prints a line and returns true.

    public boolean canDeleteJob() {
    println "SecurityService.canDeleteJob()"
    return true
    }

    In the controller I have add the PreAuthorize annotation, like

    @PreAuthorize("@securityService.canDeleteJob()")
    def _deletejob() {
    def uuid = params.uuid
    ...
    But canDeleteJob is not called when I access _deleteJob. I have followed all the steps you have mentioned.
    Is there is something which I need to configure.

    I am using following spring security plugins.

    compile ":spring-security-core:1.2.7.3"
    compile ":spring-security-acl:1.1.1"

    And my grails version is 2.1.1

    Need your help, thanks.


    ReplyDelete
  2. So the problem is that I was using @PreAuthorize annotation in controller's action, but it is only supported for service. Is there any way to override this behavior.

    ReplyDelete
  3. Unfortunately @Pre/PostAuthorize only works at Service methods in Grails (in Spring Web-MVC it works fine on controllers. However it is not the recommended way).
    I'am not sure but I don't think you can enable these annotations in Grails controllers without hooking into Grails controller creation.

    ReplyDelete
  4. Hi Michael,

    First of all, thank you very much for this post and for your blog overall, it's been quite helpful!

    I've been trying to follow your suggestions here but I believe AbstractSecurityExpressionHandler has changed or maybe I am missing something.
    I cannot create the GrailsExpressionHandler as the createEvaluationContext is final.

    Do you happen to know if there's an equivalent way?

    In addition, where should the GrailsBeanResolver and the GrailsExpressionHandler live in the directory tree?

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Hello,

      I took another go at this. Do you think this would be a valid alternative?

      @Override
      public EvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation method) {
      StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContextInternal(auth, method)
      context.setBeanResolver(beanResolver);
      }

      Delete
    3. Hi Tiago,

      looks like you are right. createEvaluationContext() is final in the current version. Looks like it changed between Spring Security 3.1 and 4.0.

      From looking at the source on GitHub it seems that a custom BeanResolver is no longer needed. The current implementation of createEvaluationContext in AbstractSecurityExpressionHandler looks like this:

      public final EvaluationContext createEvaluationContext(Authentication authentication, T invocation) {
      SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation);
      StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation);
      ctx.setBeanResolver(br);
      ctx.setRootObject(root);
      return ctx;
      }

      (see: https://github.com/spring-projects/spring-security/blob/master/core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java)

      So a BeanResolver is set by default. The BeanResolver (br) itself is set in setApplicationContext():

      public void setApplicationContext(ApplicationContext applicationContext) {
      br = new BeanFactoryResolver(applicationContext);
      }

      According to the name BeanFactoryResolver should automatically resolve beans from the Spring bean factory.

      Delete
    4. Hi again,

      I think we no longer need to overload all those classes anymore. I think Spring is making it really hard for us to do so and steering us towards using annotations.

      Apparently if you annotate your service with @Component("myService"), it becomes available in security annotations with the "@mySevice.myMethod()" form.

      Tiago

      Delete