You need to enable JavaScript to run this app. 如何避免Spring WebFlux集成Spring Security时JwtTokenFilter重复调用
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Spring WebFlux集成Spring Security时JwtTokenFilter重复调用

问题根因
  • Spring WebFlux 默认会自动收集容器内所有实现WebFilter接口的Bean,将其挂载到全局Web请求链路中,所有进入应用的请求都会触发该过滤器执行一次
  • 如果你在Spring Security的响应式过滤链配置中,又手动将同一个WebFilter实例加入Security的专属过滤链,请求走完全局Web链路进入Security处理阶段时,就会触发第二次执行
  • 传统Servlet场景的解决方案无法直接适配的核心原因:Servlet场景可通过FilterRegistrationBean关闭容器层对过滤器的自动注册,但WebFlux没有对应的容器层注册开关,所有WebFilter类型的Bean默认都会被全局链路收集
可落地的解决方案(保留Spring依赖注入能力)

推荐方案:拆分业务逻辑与过滤器接口

这种方式从根源上避免双重注册,同时完全保留依赖注入能力:

  1. 把原过滤器类中的JWT校验、认证信息注入逻辑抽离为普通Spring Bean,该Bean不实现WebFilter接口,自然不会被全局Web链路自动收集,仍可通过Spring容器注入所需的JwtTokenProvider依赖
  2. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.06 02:06:02