7

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

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

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

我看到两个选项:

  • 我可以在中使用条件逻辑AccessDecisionVoter来确定调用的方法和要使用的授权逻辑,但这似乎是一个丑陋的解决方案。
  • 我可以为MethodSecurityInterceptor每种方法定义一个来保护。根据 Spring 文档, aMethodSecurityInterceptor用于保护许多方法,所以它让我想到还有另一种方法。

方法调用后的访问决策也存在同样的问题(使用AfterInvocationProvider)。

有哪些替代方案?

4

2 回答 2

4

我通过实现我自己AccessDecisionManager的将访问决定委托给我的特殊接口来实现这一点AccessDecisionStrategy

public interface AccessDecisionStrategy {

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

}

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

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

public class SomeStrategy implements AccessDecisionStrategy { ...

如您所见,我AccessDecisionManager有一张战略地图。经理使用的策略基于注释参数。

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);
    }
}

现在,当我想保护我的方法时,我将@Secured注释与作为策略名称的参数放在一起:

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

您可以根据需要实施和配置任意数量的策略:

<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>

要注入该访问决策管理器,请键入:

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

我还实现了辅助类来处理MethodInvocation参数:

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();
        }
    }

}

现在,您可以轻松地在策略代码中提取有趣的参数来做出决策:

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

MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);
于 2012-11-14T14:40:17.510 回答
3

您可以基于 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;
} 

并在您的global-method-security声明中注册

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

现在您可以创建自定义安全注释(如果需要,还可以在 MySecurityExpressionHandler 中创建额外的进程注释数据)

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

例如,您可以创建自定义注释来检查用户角色而不会弄乱字符串:

@MyUserRoleCheck(MyAppRole.Admin)
public void someMethod()
于 2012-11-16T16:25:41.050 回答