Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MethodSecurityInterceptor for multiple methods

I would like to secure my services layer using Spring Security. As explained in the documentation, I need to use a MethodSecurityInterceptor that will check if the method invocation is allowed.

To decide if a service method invocation is allowed for a given user, affecting a required role to the invoked method (using MethodSecurityMetadataSource) is not enough for me since it also depends on the parameters passed to the method. As suggested in the documentation, I can write a custom AccessDecisionVoter and access the arguments though the secured object (MethodInvocation in this case).

But, my authorization logic is different across the methods. For example, the arguments may be different between multiple methods and the authorization logic will also be different.

I see two options:

  • I can use conditional logic in the AccessDecisionVoter to determine the invoked method and the authorization logic to use, but it seems to be an ugly solution.
  • I can define one MethodSecurityInterceptor per method to secure. According to the Spring documentation, a MethodSecurityInterceptor is used to secure many methods, so it makes me thinking there is another way.

The same question exists for access decision after method invocation (using AfterInvocationProvider).

What are the alternatives?

like image 727
manash Avatar asked Nov 14 '12 09:11

manash


People also ask

What's the difference between @secured and @PreAuthorize in Spring Security?

The real difference is that @PreAuthorize can work with Spring Expression Language (SpEL). You can: Access methods and properties of SecurityExpressionRoot . (Advanced feature) Add your own methods (override MethodSecurityExpressionHandler and set it as <global-method-security><expression-handler ... /></...> ).

What is the use of @PreAuthorize annotation?

The @PreAuthorize annotation checks the given expression before entering the method, whereas the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.

What is @secured annotation?

The Secured annotation is used to define a list of security configuration attributes for business methods. This annotation can be used as a Java 5 alternative to XML configuration.

What is global method security?

Figure 16.1 Global method security enables you to apply authorization rules at any layer of your application. This approach allows you to be more granular and to apply authorization rules at a specifically chosen level.


2 Answers

I achieved that by implementing my own AccessDecisionManager that delegates access decisions to my special interface AccessDecisionStrategy:

public interface AccessDecisionStrategy {

    void decide(Authentication authentication, MethodInvocation methodInvocation, ConfigAttribute configAttribute);

}

Each access decision strategy represents different way of making access decision.

You can easily implement your own strategy (even in other language - for instance Scala):

public class SomeStrategy implements AccessDecisionStrategy { ...

As you can see, my AccessDecisionManager has a map of strategies. Strategy used by manager is based on annotation argument.

public class MethodSecurityAccessDecisionManager implements AccessDecisionManager {

    private Map<String, AccessDecisionStrategy> strategyMap;

    public MethodSecurityAccessDecisionManager(Map<String, AccessDecisionStrategy> strategyMap) {
        this.strategyMap = strategyMap;
    }

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        ConfigAttribute configAttribute = getSingleConfigAttribute(configAttributes);
        AccessDecisionStrategy accessDecisionStrategy = strategyMap.get(configAttribute.getAttribute());
        if (accessDecisionStrategy == null) {
            throw new IllegalStateException("AccessDecisionStrategy with name "
                    + configAttribute.getAttribute() + " was not found!");
        }
        try {
            accessDecisionStrategy.decide(authentication, (MethodInvocation) object, configAttribute);
        } catch (ClassCastException e) {
            throw new IllegalStateException();
        }
    }

    private ConfigAttribute getSingleConfigAttribute(Collection<ConfigAttribute> configAttributes) {
        if (configAttributes == null || configAttributes.size() != 1) {
            throw new IllegalStateException("Invalid config attribute configuration");
        }
        return configAttributes.iterator().next();
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(MethodInvocation.class);
    }
}

Now when I want to protect my method I put @Secured annotation with argument that is name of the strategy:

@Secured("GetByOwner")
FlightSpotting getFlightSpotting(Long id);

You can implement and configure as many strategies as you want:

<bean id="methodSecurityAccessDecisionManager"
      class="some.package.MethodSecurityAccessDecisionManager">

    <constructor-arg>
        <map>
            <entry key="GetByOwner">
                <bean class="some.package.GetByOwnerStrategy"/>
            </entry>

            <entry key="SomeOther">
                <bean class="some.package.SomeOtherStrategy"/>
            </entry>
        </map>
    </constructor-arg>

</bean>

To inject that access decision manager you type:

<sec:global-method-security secured-annotations="enabled"
                            access-decision-manager-ref="methodSecurityAccessDecisionManager">
</sec:global-method-security>

I also implemented helper class to handle MethodInvocation arguments:

import org.aopalliance.intercept.MethodInvocation;

public class MethodInvocationExtractor<ArgumentType> {

    private MethodInvocation methodInvocation;

    public MethodInvocationExtractor(MethodInvocation methodInvocation) {
        this.methodInvocation = methodInvocation;
    }

    public ArgumentType getArg(int num) {
        try {
            Object[] arguments = methodInvocation.getArguments();
            return (ArgumentType) arguments[num];
        } catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
            throw new IllegalStateException();
        }
    }

}

Now you can easily extract interesting arguments in the code of your strategy to make decision:

Let's say I want to get argument number 0 that is of type Long:

MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);
like image 167
Maciej Ziarko Avatar answered Sep 27 '22 18:09

Maciej Ziarko


You can implement your own method security annotations based on Spring @PreAuthorize("") construction.

To fetch extra information about the method(beyond method argument values) to SpEL evaluation context you can implement your own MethodSecurityExpressionHandler

@Service
public class MySecurityExpressionHandler extends
    DefaultMethodSecurityExpressionHandler {

    @Override
    public StandardEvaluationContext createEvaluationContextInternal(
        Authentication auth, MethodInvocation mi) {

    StandardEvaluationContext evaluationContext = super
            .createEvaluationContextInternal(auth, mi);

    SomeMethodInfoData methodInfoData = mi.getMethod(). ...;

    evaluationContext.setVariable("someData", <value computed based on method info data>);
    }

    return evaluationContext;
} 

and register it in your global-method-security declaration

<security:global-method-security
        pre-post-annotations="enabled">
        <security:expression-handler
            ref="mySecurityExpressionHandler" />
    </security:global-method-security>

Now you can create custom security annotations(and extra process annotation data in MySecurityExpressionHandler if required)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#<someData>")
public @interface CustomSecurityAnnotation { ... }

for example you can create a custom annotation to check user roles without messing with strings:

@MyUserRoleCheck(MyAppRole.Admin)
public void someMethod()
like image 43
Boris Treukhov Avatar answered Sep 27 '22 18:09

Boris Treukhov