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+

4 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