MethodSecurityInterceptor for multiple methods

后端 未结 2 1307
独厮守ぢ
独厮守ぢ 2020-12-20 17:54

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

相关标签:
2条回答
  • 2020-12-20 18:18

    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);
    
    0 讨论(0)
  • 2020-12-20 18:25

    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()
    
    0 讨论(0)
提交回复
热议问题