Felipe Cypriano You are about to read it

19Oct/097

Enable @Secured annotation with Grails Spring Security plugin

How could I protect each method of my classes? Using Spring Security it should be easy, right? After a quick search I realize that the best way is to use @Secured annotation, good! Another issue fixed. But as life isn't fun without problems to solve it didn't work as expected.

The problem started because grails acegi plugin, in version 0.5.2, doesn't support this annotation.Talking about this in grail user mailing list Benjamin Doerr gave me a nice idea: use groovy's invokeMethod to add the support that I needed.

The idea is to use groovy meta magic to add behavior to the classes that have at least one method annotated, we well override the metaClass.invokeMethod of the class we want to enable the annotation. To keep things organized I create a new boot strap file in grail-app/conf/SecurityBootStrap.groovy and all the related code is place in this file.

First off all let's create a closure that can be used to override invokeMethod of any class:

    def verifyMethodAccess = {String name, args ->
        def method = delegate.metaClass.getMetaMethod(name, args)
        def logMsg = new StringBuilder("invoking ${method}, ")
        if (method) {
            def annotation = method.cachedMethod.getAnnotation(Secured)
            logMsg += "is @Secured ${annotation != null}"
            if (annotation) {
                def annotationValue = annotation.value()?.toString()?.replaceAll(~/[\[-\]]/, '') // remove [ and ]
                logMsg += " by ${annotationValue}"
                if (!authenticateService?.ifAnyGranted(annotationValue)) {
                    log.debug logMsg.toString()
                    throw new AccessDeniedException("Access denied to ${delegate.class.simpleName}.${name} from user ${authenticateService.principal()?.username}")
                }
            }
            log.debug logMsg.toString()
            return method.invoke(delegate, args)
        }

        log.error "Method ${delegate.class.simpleName}.${name} not found"
        return null
    }

In a glance this closure gets the method that is been invoked and verifies if this method is annotated with @Secured annotation, if it does then the granted authorities passed to the annotation's value is obtained - line 8 - and is passed to the authenticationService if the current user has access the method is invoked otherwise an AccessDeniedException is thrown.

To active this behavior in a class we only need to override it's metaClass.invokeMethod:

import org.springframework.security.annotation.Secured
import org.springframework.security.AccessDeniedException

public class SecurityBootStrap {
    def init = {servletContext ->
        ClassifiedClass.metaClass.invokeMethod = verifyMethodAccess
    }
    // verifyMethodAccess declaration and body goes here
}

To complete the example this is the ClassifiedClass:

public class ClassifiedClass {
    @Secured("ROLE_PRESIDENT")
    String protectedMethod() {
        "I'm a top classified information!"
    }

    def ordinaryMethod() {
        "Anyone can call me."
    }
}

Every time a groovy code calls any method of ClassifiedClass it's metaClass.invokeMethod will be executed before the actual method. Just pay attention to use the same Secured annotation in your class and in SecurityBootStrap.

Unfortunately this solution only works if the call came from groovy code, if any java code calls classifiedObj.protectedMethod() it will succeed even without permission. Java code completely ignores the invokeMethod. Now stop thinking that you read all of this for nothing, because on next post I'll talk about using InvokerHelper class to make java code obey the law.

# Update 1

Read this post to see how to make java code be aware of groovy's dynamic method, including invokeMethod.

Comments (7) Trackbacks (0)
  1. Great article. I previously had problems with the Secured annotation, but thought that my code was broken.
    I’ll try to implement your solution. You should really ask the Spring Security guys to include your code into the plugin!

    • Burt Beckwith told me that on version 0.5.2 the built-in support for this annotation needed to be removed because of Spring Security update, some class that he needs was removed. I don’t understand exactly why he didn’t explained.

      But he told me that on the next version, which will use Spring Security 3, the plugin will support the annotation.

      Thanks for the compliments about the article and if you have any problems feel free to ask.

  2. I just used @Secured with acegi 0.5.2 and grails 1.1.1 last night. After following the documentation changes, it works perfectly for me.

    • Only use @Secured in controllers works out of the box and using the plugin’s own class: org.codehaus.groovy.grails.plugins.springsecurity.Secured

      What this article describe is how to use the default spring security Secured class with non controllers classes.

  3. Hi Felipe,
    I am having issue with @Secured annotation (org.codehaus.groovy.grails.plugins.springsecurity.Secured) in controllers. Acegi failing throw login page even though there are no Roles . I am using Acegi 0.5.2 and grails 1.1.2 version. I configured acegi to use annotations in class. I am using LDAP to authentication and for authorization/ roles were queried using Hibernate session and created GrailsUser. Logging in and logout works fine but when the session exprieds @Secured annotated method doesn’t redirect to login page. Any clues..would help debugging issue..


Leave a comment


 

No trackbacks yet.