自定义 Delegating ReactiveAuthorizationManager 以验证所有匹配规则,而不是放弃第一个匹配的规则
Customize DelegatingReactiveAuthorizationManager to verify all matching rules, instead of giving up on the first that matches
我有一个授权模型相当复杂的应用程序。
在数据库中,我可以拥有指定特定端点所需权限的条目,并在应用程序启动时加载它们。
示例(方法、模式、权限):
GET
- /api/service1/someEntity
- GET_SERVICE1_SOME_ENTITY_AUTHORITY
GET
- /api/service2/someEntity
- GET_SERVICE2_SOME_ENTITY_AUTHORITY
GET
- /api/service1/**
- GET_SERVICE1_ANYTHING_AUTHORITY
null
- /**
- DO_ANYTHING
(管理员)
在我加载这些规则之前,我将它们从最具体到最一般排序,因此它们最终的顺序与上面完全相同。我这样加载它们(对于每个排序顺序):
http.authorizeExchange()
.pathMatchers(permission.getMethod(), permission.getPattern())
.hasAuthority(permission.getAuthority());
现在的问题是,当我有用户:ADMIN
和权限 DO_ANYTHING
时,由于 spring 安全的工作方式,它实际上无法做任何事情.
假设 admin
想要访问 /api/service1/someEntity
。
Spring 将匹配第 1 条规则并检查 admin
是否具有权限 GET_SERVICE1_SOME_ENTITY_AUTHORITY
并且结果证明他没有。此时,Spring 将拒绝访问。但我想让它做的是检查其他匹配规则(数字 3 和 4)。
我找到了 the code responsible for this:
public final class DelegatingReactiveAuthorizationManager implements ReactiveAuthorizationManager<ServerWebExchange> {
// ...
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings).concatMap((mapping) -> mapping.getMatcher().matches(exchange)
.filter(MatchResult::isMatch).map(MatchResult::getVariables).flatMap((variables) -> {
logger.debug(LogMessage.of(() -> "Checking authorization on '"
+ exchange.getRequest().getPath().pathWithinApplication() + "' using "
+ mapping.getEntry()));
return mapping.getEntry().check(authentication, new AuthorizationContext(exchange, variables));
})).next().defaultIfEmpty(new AuthorizationDecision(false));
}
// ...
}
不幸的是,我不知道如何用我的替换这个实现。
tldr: 我希望 spring 安全验证与请求路径匹配的所有安全规则,而不是拒绝对第一个匹配的规则的访问。
好的,我想出了一个解决方案,虽然有点老套,但它满足了我的需要。
我已经创建了自己的 ReactiveAuthorizationManager<ServerWebExchange>
实现,它与 Spring 的 DelegatingReactiveAuthorizationManager
几乎相同,但有一个额外的过滤器,可以完成这项工作:
public class MyAuthorizationManager implements ReactiveAuthorizationManager<ServerWebExchange> {
private final List<ServerWebExchangeMatcherEntry<ReactiveAuthorizationManager<AuthorizationContext>>> mappings;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings)
.concatMap((mapping) -> mapping.getMatcher()
.matches(exchange)
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
.map(ServerWebExchangeMatcher.MatchResult::getVariables)
.flatMap((variables) -> {
log.debug(LogMessage.of(() -> "Checking authorization on '"
+ exchange.getRequest()
.getPath()
.pathWithinApplication() + "' using "
+ mapping.getEntry()).toString());
return mapping.getEntry()
.check(authentication, new AuthorizationContext(exchange, variables));
}).filter(AuthorizationDecision::isGranted) /* <- this is what I needed */)
.next()
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
我是这样注册的:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
List<Permission> permissions = repo.getPermissions()
MyAuthorizationManager.Builder authBuilder = MyAuthorizationManager.builder();
permissions.forEach(permission -> {
// I create the mappings here, so matcher + auth manager for that matcher
authBuilder.add(
ServerWebExchangeMatchers.pathMatchers(permission.getMethod(), permission.getPattern()),
AuthorityReactiveAuthorizationManager.hasAuthority(permission.getAuthority())
);
});
http.authorizeExchange().anyExchange().permitAll()
.and()
.httpBasic()
.and()
.formLogin().disable()
.anonymous().disable();
// it will be before the default authorization filter, bu actually it doesn't really matter
http.addFilterBefore(new AuthorizationWebFilter(authBuilder.build()), SecurityWebFiltersOrder.AUTHORIZATION);
return http.build();
}
我已经离开 .authorizeExchange().anyExchange().permitAll()
因为它设置了一些默认的东西,比如入口点、异常 Web 过滤器,我不想复制更多代码并完全放弃默认管理器。
我有一个授权模型相当复杂的应用程序。 在数据库中,我可以拥有指定特定端点所需权限的条目,并在应用程序启动时加载它们。
示例(方法、模式、权限):
GET
-/api/service1/someEntity
-GET_SERVICE1_SOME_ENTITY_AUTHORITY
GET
-/api/service2/someEntity
-GET_SERVICE2_SOME_ENTITY_AUTHORITY
GET
-/api/service1/**
-GET_SERVICE1_ANYTHING_AUTHORITY
null
-/**
-DO_ANYTHING
(管理员)
在我加载这些规则之前,我将它们从最具体到最一般排序,因此它们最终的顺序与上面完全相同。我这样加载它们(对于每个排序顺序):
http.authorizeExchange()
.pathMatchers(permission.getMethod(), permission.getPattern())
.hasAuthority(permission.getAuthority());
现在的问题是,当我有用户:ADMIN
和权限 DO_ANYTHING
时,由于 spring 安全的工作方式,它实际上无法做任何事情.
假设 admin
想要访问 /api/service1/someEntity
。
Spring 将匹配第 1 条规则并检查 admin
是否具有权限 GET_SERVICE1_SOME_ENTITY_AUTHORITY
并且结果证明他没有。此时,Spring 将拒绝访问。但我想让它做的是检查其他匹配规则(数字 3 和 4)。
我找到了 the code responsible for this:
public final class DelegatingReactiveAuthorizationManager implements ReactiveAuthorizationManager<ServerWebExchange> {
// ...
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings).concatMap((mapping) -> mapping.getMatcher().matches(exchange)
.filter(MatchResult::isMatch).map(MatchResult::getVariables).flatMap((variables) -> {
logger.debug(LogMessage.of(() -> "Checking authorization on '"
+ exchange.getRequest().getPath().pathWithinApplication() + "' using "
+ mapping.getEntry()));
return mapping.getEntry().check(authentication, new AuthorizationContext(exchange, variables));
})).next().defaultIfEmpty(new AuthorizationDecision(false));
}
// ...
}
不幸的是,我不知道如何用我的替换这个实现。
tldr: 我希望 spring 安全验证与请求路径匹配的所有安全规则,而不是拒绝对第一个匹配的规则的访问。
好的,我想出了一个解决方案,虽然有点老套,但它满足了我的需要。
我已经创建了自己的 ReactiveAuthorizationManager<ServerWebExchange>
实现,它与 Spring 的 DelegatingReactiveAuthorizationManager
几乎相同,但有一个额外的过滤器,可以完成这项工作:
public class MyAuthorizationManager implements ReactiveAuthorizationManager<ServerWebExchange> {
private final List<ServerWebExchangeMatcherEntry<ReactiveAuthorizationManager<AuthorizationContext>>> mappings;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings)
.concatMap((mapping) -> mapping.getMatcher()
.matches(exchange)
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
.map(ServerWebExchangeMatcher.MatchResult::getVariables)
.flatMap((variables) -> {
log.debug(LogMessage.of(() -> "Checking authorization on '"
+ exchange.getRequest()
.getPath()
.pathWithinApplication() + "' using "
+ mapping.getEntry()).toString());
return mapping.getEntry()
.check(authentication, new AuthorizationContext(exchange, variables));
}).filter(AuthorizationDecision::isGranted) /* <- this is what I needed */)
.next()
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
我是这样注册的:
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
List<Permission> permissions = repo.getPermissions()
MyAuthorizationManager.Builder authBuilder = MyAuthorizationManager.builder();
permissions.forEach(permission -> {
// I create the mappings here, so matcher + auth manager for that matcher
authBuilder.add(
ServerWebExchangeMatchers.pathMatchers(permission.getMethod(), permission.getPattern()),
AuthorityReactiveAuthorizationManager.hasAuthority(permission.getAuthority())
);
});
http.authorizeExchange().anyExchange().permitAll()
.and()
.httpBasic()
.and()
.formLogin().disable()
.anonymous().disable();
// it will be before the default authorization filter, bu actually it doesn't really matter
http.addFilterBefore(new AuthorizationWebFilter(authBuilder.build()), SecurityWebFiltersOrder.AUTHORIZATION);
return http.build();
}
我已经离开 .authorizeExchange().anyExchange().permitAll()
因为它设置了一些默认的东西,比如入口点、异常 Web 过滤器,我不想复制更多代码并完全放弃默认管理器。