Spring Security 3.1: securing methods with custom annotations

Spring Security offers a powerful way to secure your application on method level, based on the invocation context like arguments and returned values, using annotations (such as @PreAuthorize and @PreFilter) and the Spring Expression Language (EL). Because it is possible to add your own functions to EL, you are pretty much able to realize any access control rules required by the application.

The problem is that the values of all elements in Java annotations are evaluated at compile time, therefore there is no way to externalize your access control rules outside your Java code using Spring Security’s built-in annotations. That means whenever the requirement change you have to compile and deploy the whole application again. This post suggests using custom annotations to abstract all the rules away from their implementations, so that they can be expressed declaratively using such as @IsAuthenticated or @CanReadPost . It also makes the code clean and easy to refactor.

So how can we add custom annotations to Spring Security? In Spring Security 3.0, it is really a challenge since your have to sacrifice the concise namespace-based configuration and manually configure a customized SecurityMetadataSource that also scans ConfigAttributes denoted by custom annotations. Fortunately, Spring Security 3.1 and later provide us an extension point, and it is the attribute metadata-source-ref of the element. According to the documentation, this attribute allows specifying an implementation of MethodSecurityMetadataSource which will take priority over other sources (such as the default annotations).

Now let’s take a look at a trivial way to realize this approach. First, we need to tell Spring Security that we have a custom implementation of MethodSecurityMetadataSource.

<global-method-security metadata-source-ref="customMetadataSource" pre-post-annotations="enabled" />

The idea is that we will provide a MethodSecurityMetadataSource instance that can translate our custom annotations to ConfigAttributes of @Pre… and @Post… annotation. Below is a simple implementation of MethodSecurityMetadataSource, making use of @PreAuthorize’s ConfigAttribute.

public class CustomMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { 

    private PrePostInvocationAttributeFactory attributeFactory; 

    private Map<Class<? extends Annotation>, String> authorizeAttributes; 

    public CustomMetadataSource(Map<Class<? extends Annotation>, String> authorizedAttributes, PrePostInvocationAttributeFactory attributeFactory) {
        this.authorizeAttributes = authorizedAttributes;
        this.attributeFactory = attributeFactory;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) {
        return doFindAttributes(method, targetClass);
    }

    @Override
    protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
        return doFindAttributes(null, clazz);
    } 

    private Collection<ConfigAttribute> doFindAttributes(Method method, Class<?> clazz) {
        for (Class<? extends Annotation> annotationType : authorizeAttributes.keySet()) {
            Annotation annotation = null;
            if (method != null) {
                annotation = AnnotationUtils.findAnnotation(method, annotationType);
            } else {
                annotation = AnnotationUtils.findAnnotation(clazz, annotationType);
            }
            if (annotation != null) {
                String authorizeAttribute = authorizeAttributes.get(annotationType);
                ConfigAttribute configAttribute = attributeFactory.createPreInvocationAttribute(null, null, authorizeAttribute);
                Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
                configAttributes.add(configAttribute);
                return configAttributes;
            }
        }
        return null;
    }
}

Take a note on how Map<Class<? extends Annotation>, String> authorizeAttributes is used to translate custom annotations to respective expressions. This helps externalize the literal strings represent those expressions, for example by defining this Map in the configuration file.

<beans:bean id="customMetadataSource" class="com.safeguard.cms.ratesystem.service.auth.CustomMetadataSource">
    <beans:constructor-arg>
        <beans:map>
            <beans:entry key="#{T(com.safeguard.cms.ratesystem.service.auth.annotation.ForAdmin)}" value="hasRole('ROLE_ADMIN')" />
        </beans:map>
    </beans:constructor-arg>
    <beans:constructor-arg>
        <beans:bean class="org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory">
            <beans:constructor-arg>
                <beans:bean class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" />
            </beans:constructor-arg>
        </beans:bean>
    </beans:constructor-arg>
</beans:bean>

Here, ForAdmin is a custom annotation, and it is actually empty.

@Retention(RUNTIME)
public @interface ForAdmin { }

And now if you want to allow a method to be invoked only when the current user is an admin, just put @ForAdmin before it.

@ForAdmin
public void doSomething() { }
Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s