Java 多方法的MethodSecurityInterceptor

Java 多方法的MethodSecurityInterceptor,java,spring-security,Java,Spring Security,我想使用Spring Security保护我的服务层。正如文档中所解释的,我需要使用一个MethodSecurityInterceptor,它将检查是否允许方法调用 要决定是否允许对给定用户进行服务方法调用,对调用的方法影响所需的角色(使用MethodSecurityMetadataSource)对我来说是不够的,因为它还取决于传递给方法的参数。正如文档中所建议的,我可以编写一个自定义的AccessDecisionVoter,并通过安全对象访问参数(在本例中为MethodInvocation)

我想使用Spring Security保护我的服务层。正如文档中所解释的,我需要使用一个
MethodSecurityInterceptor
,它将检查是否允许方法调用

要决定是否允许对给定用户进行服务方法调用,对调用的方法影响所需的角色(使用
MethodSecurityMetadataSource
)对我来说是不够的,因为它还取决于传递给方法的参数。正如文档中所建议的,我可以编写一个自定义的
AccessDecisionVoter
,并通过安全对象访问参数(在本例中为
MethodInvocation

但是,我的授权逻辑在不同的方法中是不同的。例如,多个方法之间的参数可能不同,授权逻辑也会不同

我看到两种选择:

  • 我可以在
    AccessDecisionVoter
    中使用条件逻辑来确定调用的方法和要使用的授权逻辑,但这似乎是一个丑陋的解决方案
  • 我可以为每个要保护的方法定义一个
    MethodSecurityInterceptor
    。根据Spring文档,一个
    MethodSecurityInterceptor
    用于保护许多方法,因此我认为还有另一种方法
方法调用后的访问决策也存在同样的问题(使用
AfterInvocationProvider


有哪些替代方案?

我通过实现自己的
AccessDecisionManager
将访问决策委托给我的特殊界面
AccessDecisionStrategy

public interface AccessDecisionStrategy {

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

}
每个访问决策策略代表了不同的访问决策方式

您可以轻松地实施自己的策略(即使使用其他语言,例如Scala):

如您所见,我的
AccessDecisionManager
有一个策略图。manager使用的策略基于注释参数

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);
    }
}
您可以实施和配置任意数量的策略:

<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>
现在,您可以轻松地在策略代码中提取有趣的参数来做出决策:

假设我想获取类型为
Long
的参数number
0

MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);
MethodInvocationExtractor=newmethodinvocationextractor(methodInvocation);
Long id=extractor.getArg(0);

您可以基于Spring
@PreAuthorize(“”)
构造实现自己的方法安全注释

要将有关方法的额外信息(超出方法参数值)获取到SpEL评估上下文,您可以实现自己的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;
} 
现在,您可以创建自定义安全注释(如果需要,还可以在MySecurityExpressionHandler中创建额外的进程注释数据)


+1表示将对象强制转换为Decise()中的MethodInvocation的指针。Spring文档缺少这一重要信息……如果有人需要它,我添加了如何使用Java配置实现它(我的项目使用Spring引导)。请看我的答案。对于许多用例,不需要实现您自己的decisionmanager。相反,您可以直接编写一个方法并从
@Preauthorize
调用它,请参见以下答案:
<sec:global-method-security secured-annotations="enabled"
                            access-decision-manager-ref="methodSecurityAccessDecisionManager">
</sec:global-method-security>
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();
        }
    }

}
MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);
@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;
} 
<security:global-method-security
        pre-post-annotations="enabled">
        <security:expression-handler
            ref="mySecurityExpressionHandler" />
    </security:global-method-security>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#<someData>")
public @interface CustomSecurityAnnotation { ... }
@MyUserRoleCheck(MyAppRole.Admin)
public void someMethod()