如何避免Spring WebFlux集成Spring Security时JwtTokenFilter重复调用
问题根因
- Spring WebFlux 默认会自动收集容器内所有实现
WebFilter接口的Bean,将其挂载到全局Web请求链路中,所有进入应用的请求都会触发该过滤器执行一次 - 如果你在Spring Security的响应式过滤链配置中,又手动将同一个
WebFilter实例加入Security的专属过滤链,请求走完全局Web链路进入Security处理阶段时,就会触发第二次执行 - 传统Servlet场景的解决方案无法直接适配的核心原因:Servlet场景可通过
FilterRegistrationBean关闭容器层对过滤器的自动注册,但WebFlux没有对应的容器层注册开关,所有WebFilter类型的Bean默认都会被全局链路收集
可落地的解决方案(保留Spring依赖注入能力)
推荐方案:拆分业务逻辑与过滤器接口
这种方式从根源上避免双重注册,同时完全保留依赖注入能力:
- 把原过滤器类中的JWT校验、认证信息注入逻辑抽离为普通Spring Bean,该Bean不实现
WebFilter接口,自然不会被全局Web链路自动收集,仍可通过Spring容器注入所需的JwtTokenProvider依赖 - 在Spring Security的过滤链配置类中,注入该逻辑Bean,通过匿名
WebFilter形式将逻辑适配为Security需要的过滤器实例,手动挂载到Security链的对应认证节点
示例代码:
// 纯业务逻辑Bean,不实现WebFilter,不会被全局Web链收集 @Component class JwtAuthenticationService( private val tokenProvider: JwtTokenProvider ) { private val HEADER_PREFIX = "Bearer " fun applyAuthentication(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> { val token = resolveToken(exchange.request) return if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { val authentication = tokenProvider.getAuthentication(token) chain.filter(exchange) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)) } else { chain.filter(exchange) } } private fun resolveToken(request: ServerHttpRequest): String? { val bearerToken = request.headers.getFirst(HttpHeaders.AUTHORIZATION) return if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) { bearerToken.substring(HEADER_PREFIX.length) } else null } }
Security配置类的注册逻辑:
@Configuration @EnableWebFluxSecurity class SecurityConfig( private val jwtAuthenticationService: JwtAuthenticationService ) { @Bean fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http .authorizeExchange { exchange -> exchange.pathMatchers("/login", "/register").permitAll() .anyExchange().authenticated() } .csrf { it.disable() } // 将逻辑适配为过滤器,挂载到Security认证流程的对应排序位置 .addFilterAt( WebFilter { exchange, chain -> jwtAuthenticationService.applyAuthentication(exchange, chain) }, SecurityWebFiltersOrder.AUTHENTICATION ) .build() } }
次选方案(不推荐)
如果坚持保留原带@Component注解、实现WebFilter接口的写法,就不要再在Security配置中手动注册该过滤器,通过@Order注解调整其执行顺序对齐Security的认证节点即可。
- 缺点:全局Web链路的执行时机和Security内部的过滤顺序无法完全保障对齐,容易出现权限校验早于JWT认证逻辑执行、导致拦截失效的问题,稳定性不足。
现有代码的潜在问题优化
- 接口返回值适配:原
filter方法定义返回Mono<Void?>不符合WebFlux的接口规范,调整为Mono<Void>即可,避免空值导致的响应式链路中断 - 空判断优化:令牌解析逻辑中的强制非空断言
bearerToken!!可以移除,结合前面的StringUtils.hasText判断即可安全取值,避免硬编码非空带来的运行时空指针风险
内容的提问来源于stack exchange,提问作者Imran pasha
相关产品推荐
相关产品推荐

