【发布时间】:2021-06-21 09:13:11
【问题描述】:
我正在编写一个库,它使用 Spring Security 和方法安全性来检查用户是否被许可执行特定操作。这是对通常基于角色的安全性的补充,这会导致问题。
注释看起来就像他们在这个测试类中所做的那样:
@RestController
class TestController {
@RolesAllowed("ROLE_USER")
@Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
@RolesAllowed("ROLE_USER")
@Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
@RolesAllowed("ROLE_USER")
@Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
处理注释似乎很简单,因为您添加了customMethodSecurityDataSource:
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
@Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
但问题在于 Spring 的实现:
@Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
首先处理我的自定义元数据源,一旦找到它识别的注释,它就会停止处理。具体来说,在这个 if 块中:
if (attributes != null && !attributes.isEmpty()) {
break;
}
结果是我的LicenceDecisionVoter投弃权票;毕竟,可能还有其他检查角色的注释处理器。并且因为没有更多的属性可以投票,所以只返回ACCESS_ABSTAIN,并且根据 Spring 的默认和推荐配置,拒绝访问。从不检查角色。
除了对 Spring 自己的注解处理器(如 @Secured 和 JSR-250 注解)实施扫描之外,我还有其他选择吗?
或者为了这个特定目的首先使用 Spring Security 是错误的?
【问题讨论】:
-
澄清一下——你为什么不使用
@PreAuthorize/@PostAuthorize? -
因为我认为任何许可问题都可以简化为布尔值,因此您只需要一个标签。因此,如果许可证允许 40 个用户,您需要检查
isAllowedToAddUser,或类似的东西。然而,@Preauthorize并不能解决问题,因为一旦 Spring 找到它识别的注解,它就会停止处理。因此,我必须构建@Preauthorize注释,使其涵盖基于角色的访问和许可访问。看起来……不优雅。 -
在
@PreAuthorize中写入适当的安全表达式,例如@PreAuthorize("hasRole('USER') and @licenseChecker.isLicensed('a')"。不需要自定义选民(现在建议不要这样做),但使用适当的表达方式。 -
我对 Spring 的最大问题是过多的文章和 SO 问题在较新的 Spring 版本中不能很好地老化。那么有什么反对自定义选民的呢?我会检查表达式,但它可能需要对代码进行根本性的重写。你知道,只要你改变一件事......
-
为什么需要重写?您已经获得了所需的逻辑(即在您的 Voter 中)。您只需将该选民设为一个 bean(而不是真正的
RoleVoter)并调用该方法(它应该返回一个布尔值)。反对选民的最大理由是他们过于简单化。您可以编写非常复杂的安全表达式(如 IP 地址检查、IP 范围、角色/权限等),这对于选民来说是不可能的。
标签: java spring spring-boot spring-security annotations