Felipe Cypriano

You're about to read

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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:

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
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.