我想使用Spring Security保护我的服务层。如文档中所述,我需要使用MethodSecurityInterceptor
来检查是否允许方法调用。
要确定是否允许给定用户进行服务方法调用,影响调用方法所需的角色(使用MethodSecurityMetadataSource
)对我来说是不够的,因为它还取决于传递给方法的参数。正如文档中所建议的,我可以编写自定义AccessDecisionVoter
并通过安全对象访问参数(在本例中为MethodInvocation
)。
但是,我的授权逻辑在各种方法中都有所不同。例如,多个方法之间的参数可能不同,授权逻辑也不同。
我看到两个选项:
AccessDecisionVoter
中使用条件逻辑来确定要使用的调用方法和授权逻辑,但这似乎是一个难看的解决方案。MethodSecurityInterceptor
来保护。根据Spring文档,MethodSecurityInterceptor
用于保护许多方法,因此它让我觉得还有另一种方法。在方法调用(使用AfterInvocationProvider
)之后存在相同的访问决策问题。
有哪些替代方案?
答案 0 :(得分:4)
我通过实现自己的AccessDecisionManager
来实现这一目标,该AccessDecisionStrategy
将访问决策委托给我的特殊界面public interface AccessDecisionStrategy {
void decide(Authentication authentication, MethodInvocation methodInvocation, ConfigAttribute configAttribute);
}
:
public class SomeStrategy implements AccessDecisionStrategy { ...
每个访问决策策略都代表了不同的访问决策方式。
您可以轻松实现自己的策略(即使是其他语言 - 例如Scala):
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);
:
{{1}}
答案 1 :(得分: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()