You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux逻辑分支引发性能下降问题排查

解决Reactive WebFilter中Mono重复执行的问题

问题场景

我实现了如下AccountTokenFilter,用于验证请求令牌并将认证信息存入安全上下文:

@Component
@RequiredArgsConstructor
public class AccountTokenFilter implements WebFilter {
    private final TokenService tokenService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<Authentication> authMono = tokenService.verifyToken(exchange.getRequest())
                .map(AccountTokenAuthentication::new);
        return authMono.hasElement().flatMap(authenticated -> {
            if (authenticated) {
                return authMono.flatMap(auth -> chain.filter(exchange)
                        .contextWrite(context -> ReactiveSecurityContextHolder.withAuthentication(auth)));
            } else {
                return chain.filter(exchange);
            }
        });
    }
}

过滤器链配置如下:

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http
        .csrf().disable()
        .cors().and()
        .httpBasic().disable()
        .formLogin().disable()
        .logout().disable()
        .authorizeExchange(spec -> spec
                .pathMatchers(HttpMethod.POST, "/accounts", "/tokens").permitAll()
                .pathMatchers(HttpMethod.GET, "/tokens/verify").permitAll()
                .anyExchange().authenticated()
        )
        .addFilterAt(accountTokenFilter, SecurityWebFiltersOrder.AUTHENTICATION);
    return http.build();
}

目前遇到的问题是:tokenService.verifyToken()每次请求会被执行4次,预期仅执行一次。

问题原因

Reactor中的Mono是冷发布者,每一次订阅都会从头触发上游逻辑的执行。你的代码中,authMono被两次显式订阅:

  1. 调用authMono.hasElement()时触发第一次订阅
  2. 进入authenticated分支后调用authMono.flatMap()时触发第二次订阅
    再加上Spring Security内部可能的隐式订阅,最终导致verifyToken()被多次执行。

修复方案

方案1:缓存Mono结果,避免重复执行

通过cache()操作符缓存authMono的结果,确保无论被订阅多少次,上游逻辑仅执行一次:

@Component
@RequiredArgsConstructor
public class AccountTokenFilter implements WebFilter {
    private final TokenService tokenService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<Authentication> authMono = tokenService.verifyToken(exchange.getRequest())
                .map(AccountTokenAuthentication::new)
                .cache(); // 缓存结果,仅执行一次上游逻辑

        return authMono.hasElement().flatMap(authenticated -> {
            if (authenticated) {
                return authMono.flatMap(auth -> 
                    chain.filter(exchange)
                        .contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth))
                );
            } else {
                return chain.filter(exchange);
            }
        });
    }
}

方案2:重构代码,仅订阅一次

更优雅的方式是调整逻辑,让整个流程只订阅一次verifyToken()的Mono,从根源避免重复执行:

@Component
@RequiredArgsConstructor
public class AccountTokenFilter implements WebFilter {
    private final TokenService tokenService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return tokenService.verifyToken(exchange.getRequest())
                .map(AccountTokenAuthentication::new)
                // 有认证信息时,写入上下文后继续过滤器链
                .flatMap(auth -> chain.filter(exchange)
                        .contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth)))
                // 无认证信息时,直接继续过滤器链
                .switchIfEmpty(chain.filter(exchange));
    }
}

这种写法逻辑更简洁,且完全避免了多次订阅的问题,是推荐的解决方案。

额外排查点

如果修复后仍有重复执行的情况,可以检查:

  • 是否有多个AccountTokenFilter实例被注册到过滤器链中
  • tokenService.verifyToken()内部是否有导致重复执行的逻辑

内容的提问来源于stack exchange,提问作者Andrew Lalis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 07:36:20