如何为WebFlux Security配置两步认证过滤器链
Spring WebFlux 多认证链路解耦实现方案
需求说明
需要支持两类并行生效的认证场景,服务端校验时任意一种认证通过即可放行请求:
- 信任系统场景:令牌由预配置的过滤器自动注入请求上下文
- 类Postman测试场景:从内部应用签发令牌用于接口结果验证,该场景仅允许在企业内网环境下生效
初始演示实现代码如下:
RemoteAddressSource remoteAddressSource = new WebFluxRemoteAddressSource(exchange); return validateNotBlank(token) .switchIfEmpty(Mono.defer(() -> iafTokenValidator.validateToken(remoteAddressSource) .onErrorResume(AuthenticationFailedException.class, e -> tfTokenValidator.validateToken(remoteAddressSource)))) .onErrorResume(AuthenticationFailedException.class, e -> sendErrorResponse(e, exchange)) .switchIfEmpty(chain);
现存问题
- 两类认证逻辑强耦合,没有拆分为独立校验单元,后续扩展、维护成本高
- 认证失败时返回的错误信息始终为第二种认证方式的报错,无法准确反馈高优先级认证校验失败的真实原因
- 期望通过标准过滤器链配置实现需求,但未找到Spring WebFlux生态下的适配实现路径
落地实现
1. 抽象统一认证器接口
统一所有认证方式的执行规范,支持按优先级排序执行:
/** * WebFlux响应式认证器统一接口 */ public interface ReactiveAuthenticator { /** * 执行认证校验 * @return 认证通过返回认证主体信息,失败抛出AuthenticationFailedException */ Mono<Authentication> authenticate(ServerWebExchange exchange, RemoteAddressSource remoteAddressSource); /** * 认证器执行优先级,数值越小优先级越高 */ int getOrder(); }
2. 拆分独立认证实现类
将两类认证逻辑拆分为完全独立的实现类,互不依赖:
/** * 信任系统场景认证器,优先级最高 */ @Component public class IafSystemAuthenticator implements ReactiveAuthenticator { private final IafTokenValidator iafTokenValidator; public IafSystemAuthenticator(IafTokenValidator iafTokenValidator) { this.iafTokenValidator = iafTokenValidator; } @Override public Mono<Authentication> authenticate(ServerWebExchange exchange, RemoteAddressSource remoteAddressSource) { String injectedToken = exchange.getRequest().getHeaders().getFirst("X-Injected-Auth-Token"); return validateNotBlank(injectedToken) .then(iafTokenValidator.validateToken(remoteAddressSource)); } @Override public int getOrder() { return 0; } }
/** * 内网测试场景认证器,优先级次于信任系统认证 */ @Component public class IntranetTestAuthenticator implements ReactiveAuthenticator { private final TfTokenValidator tfTokenValidator; private final IntranetRangeMatcher intranetRangeMatcher; public IntranetTestAuthenticator(TfTokenValidator tfTokenValidator, IntranetRangeMatcher intranetRangeMatcher) { this.tfTokenValidator = tfTokenValidator; this.intranetRangeMatcher = intranetRangeMatcher; } @Override public Mono<Authentication> authenticate(ServerWebExchange exchange, RemoteAddressSource remoteAddressSource) { // 前置校验:非内网环境直接拦截,不进入令牌校验逻辑 return Mono.just(remoteAddressSource.getRemoteAddress()) .filter(intranetRangeMatcher::isInRange) .switchIfEmpty(Mono.error(new AuthenticationFailedException("测试场景认证仅允许在内网环境使用"))) .then(tfTokenValidator.validateToken(remoteAddressSource)); } @Override public int getOrder() { return 1; } }
3. 实现统一认证过滤器
按优先级顺序依次执行认证逻辑,收集所有认证阶段的异常,所有认证方式均失败时返回对应场景的错误信息,解决错误透传问题:
@Component public class MultiAuthWebFilter implements WebFilter { private final List<ReactiveAuthenticator> authenticators; private final ObjectMapper objectMapper; // Spring会自动注入所有ReactiveAuthenticator实现,并按@Order注解排序 public MultiAuthWebFilter(List<ReactiveAuthenticator> authenticators, ObjectMapper objectMapper) { // 兜底按getOrder返回值排序,避免注入顺序异常 this.authenticators = authenticators.stream() .sorted(Comparator.comparingInt(ReactiveAuthenticator::getOrder)) .toList(); this.objectMapper = objectMapper; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { RemoteAddressSource remoteAddressSource = new WebFluxRemoteAddressSource(exchange); List<AuthenticationFailedException> authErrors = new ArrayList<>(); // 按优先级串行尝试认证 Mono<Authentication> authExecuteChain = authenticators.stream() .reduce( Mono.<Authentication>empty(), (currentAuthMono, authenticator) -> currentAuthMono .switchIfEmpty(Mono.defer(() -> authenticator.authenticate(exchange, remoteAddressSource) .onErrorResume(AuthenticationFailedException.class, e -> { authErrors.add(e); return Mono.empty(); }))), Mono::or ); return authExecuteChain .flatMap(authInfo -> { // 认证通过,将认证信息写入上下文后放行 exchange.getAttributes().put(WebFilterExchange.AUTHENTICATION_ATTR, authInfo); return chain.filter(exchange); }) .switchIfEmpty(Mono.defer(() -> { // 所有认证均失败,返回最高优先级认证场景的错误(可根据业务规则调整错误选择逻辑) AuthenticationFailedException targetError = authErrors.get(0); return writeUnauthorizedResponse(targetError, exchange); })); } private Mono<Void> writeUnauthorizedResponse(AuthenticationFailedException ex, ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); Map<String, Object> respBody = Map.of( "code", HttpStatus.UNAUTHORIZED.value(), "msg", ex.getMessage() ); try { byte[] respBytes = objectMapper.writeValueAsBytes(respBody); DataBuffer buffer = response.bufferFactory().wrap(respBytes); return response.writeWith(Mono.just(buffer)); } catch (JsonProcessingException e) { return response.setComplete(); } } }
配置说明
- 将
MultiAuthWebFilter注册到Spring WebFlux过滤器链,顺序放在所有业务接口拦截逻辑之前即可,不需要修改原有TokenValidator的内部实现 - 后续新增认证方式时,仅需要新增
ReactiveAuthenticator接口的实现类,配置对应优先级即可,不需要修改现有认证逻辑,符合开闭原则 - 错误返回逻辑可根据业务规则调整:比如识别到请求携带信任系统注入的专属请求头时,直接返回信任系统认证的错误;识别为内网测试请求时返回测试场景错误,当前默认返回最高优先级认证的错误,已解决原有实现始终返回最后一个认证器错误的问题
内容的提问来源于stack exchange,提问作者omer keynan
相关产品推荐
相关产品推荐

