Trang chủ > Uncategorized > Spring Security 3.1: securing methods with custom annotations

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 implementation, so that they can be expressed declaratively like @IsAuthenticated, @CanReadPost etc. 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 custom 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 <global-method-security>. According to the documentation, this attribute allow 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 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() { }
  1. Chưa có phản hồi.
  1. No trackbacks yet.

Gửi phản hồ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

%d bloggers like this: