认证过滤器:AuthenticationWebFilter
授权过滤器:AuthorizationWebFilter
避免歧义:此处的认证不是指的登录。是指在请求接口时,此请求通过的spring-security中的两个主要过滤器。
基本流程:请求首先通过AuthenticationWebFilter,以认证是否携带token,且token是否有效。
然后通过AuthorizationWebFilter,来检查该用户会否包含访问该接口的权限。
AuthenticationWebFilter.java (认证过滤器)
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return this.requiresAuthenticationMatcher.matches(exchange).filter((matchResult) -> {
return matchResult.isMatch();
}).flatMap((matchResult) -> {
return this.authenticationConverter.convert(exchange);
}).switchIfEmpty(chain.filter(exchange).then(Mono.empty())).flatMap((token) -> {
return this.authenticate(exchange, chain, token);
});
}
- 筛选出所有需要认证的路径。(requiresAuthenticationMatcher)
- 从exchange请求对象中获取token并转化成Authentication对象。(authenticationConverter)
- 如果Authentication对象为空,则直接通过过滤器,否则对Authentication中的token进行验证。(authenticate())
- authenticate方法验证token的主要逻辑是:
-- 使用authenticationManager对象的authenticate方法进行验证
-- 如果验证成功则调用onAuthenticationSuccess方法,将Authentication对象存入请求的上下文中(context)。
-- 如果验证失败则authenticationFailureHandler对象的onAuthenticationFailure方法,默认是返回401。
注:
- 这里面需要注意的一点是:如果你的某个接口权限是permit,但是如果访问的时候携带了一个过期或非法的token,这时候是无法通过的。对于permit的接口,只有完全不携带token时才能正常访问。
- 因为没等到进入授权过滤器,就被认证过滤器处理了。
- 如果想要解决这个问题,可以考虑修改这里的逻辑。
AuthorizationWebFilter.java (授权过滤器)
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return ((Mono)ReactiveSecurityContextHolder.getContext().filter((c) -> {
return c.getAuthentication() != null;
}).map(SecurityContext::getAuthentication).as((authentication) -> {
return this.accessDecisionManager.verify(authentication, exchange);
})).switchIfEmpty(chain.filter(exchange));
}
- 从请求上下文(context)中拿到Authentication对象(因为认证过滤器之后会有相应过滤器对不包含token的请求做处理,所以到这里都是有Authentication对象的)
- 使用accessDecisionManager对象的verify方法对请求做鉴权。
- accessDecisionManager对象的类名是ReactiveAuthorizationManager<? super ServerWebExchange>
包含验证权限的具体逻辑,默认的逻辑是【通过优先】
- 通过优先:如果权限A和权限B都包含同一个接口,用户包含其中任意一个权限即可通过验证。
- 拒绝优先:如果权限A和权限B都包含同一个path,用户必须同时有权限A和权限B才能访问这个接口。(拒绝优先听着好像更安全,但是实际很蠢,而且不符合RABC模型)
- 注:
-- 如果希望更改为【拒绝优先】的逻辑,需要重写此类。(后面提供两种模式代码比对)
-- accessDecisionManager对象是AuthorizationWebFilter初始化的时候指定的,不支持动态替换。
如果需要做【动态的权限映射】可以重写AuthorizationWebFilter,并提供accessDecisionManager对象的set方法。
- 静态的权限路径映射:是指程序启动时载入,程序运行时不可更改,修改后需重启程序。
- 动态的权限路径映射:可程序启动时载入,并且程序运行时可以修改,修改后需要重新set accessDecisionManager对象。
通过优先和拒绝优先的代码比较
默认的accessDecisionManager对象具体的实现类是
DelegatingReactiveAuthorizationManager.class
其中包含成员变量mappings(权限路径的映射列表)
mappings是常量,在该类构造方法中指定。
通过优先
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings).concatMap((mapping) -> {
return mapping.getMatcher().matches(exchange).filter(MatchResult::isMatch).map((r) -> {
return r.getVariables();
}).flatMap((variables) -> {
return ((ReactiveAuthorizationManager)mapping.getEntry()).check(authentication, new AuthorizationContext(exchange, variables));
});
}).next().defaultIfEmpty(new AuthorizationDecision(false));
}
拒绝优先
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, ServerWebExchange exchange) {
return Flux.fromIterable(this.mappings).concatMap((mapping) -> {
return mapping.getMatcher().matches(exchange).filter(ServerWebExchangeMatcher.MatchResult::isMatch).map((r) -> {
return r.getVariables();
}).flatMap((variables) -> {
return mapping.getEntry().check(authentication, new AuthorizationContext(exchange, variables));
});
})
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.filter(authorizationDecision -> !authorizationDecision.isGranted())
.next()
.defaultIfEmpty(new AuthorizationDecision(true))
;
}