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

Spring Webflux自定义WebFilter导致开放端点均返回HTTP 200问题排查

问题

我正在开发Spring Boot Webflux应用,自定义了一个WebFilter用于基于请求中的Principal对象实现即时用户创建。该Filter在需要认证的端点上工作正常,但对于无需认证的端点(匹配/foo/**模式),无论端点是否存在,都会返回HTTP 200状态码,且端点似乎未被调用。

我的安全配置如下:

@Bean
public SecurityWebFilterChain securityWebFilterChain(
        ServerHttpSecurity http) {
    return http.cors(cors -> cors.configurationSource(swx -> corsConfiguration()))
            .csrf(ServerHttpSecurity.CsrfSpec::disable)
            .securityMatcher(new NegatedServerWebExchangeMatcher(
                    ServerWebExchangeMatchers.pathMatchers("/actuator/**")))
            .authorizeExchange((authorize) -> authorize
                    .pathMatchers("/foo/**")
                    .permitAll()
                    .anyExchange()
                    .authenticated()
            )
            .oauth2ResourceServer(ors -> ors.jwt(jwt -> {}))
            .build();
}

我的WebFilter代码如下:

@Component
public class JITUserFilter implements WebFilter {

    private static final Logger logger = LoggerFactory.getLogger(JITUserFilter.class);

    private final UserService userService;

    public JITUserFilter(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange,
                             WebFilterChain webFilterChain) {
        return serverWebExchange.getPrincipal().flatMap(p -> {
            Optional<String> email = Optional.empty();
            if (p instanceof JwtAuthenticationToken) {
                email = Optional.of(((JwtAuthenticationToken)p).getToken().getClaimAsString("https://my-app.example.com/email"));
            }
            Optional<User> userOpt = userService.findUserByExternalId(p.getName());
            if (userOpt.isEmpty()) {
                logger.info("No existing user found; creating new with externalId {}", p.getName());
                User user = new User();
                user.setExternalId(p.getName());
                user.setEmail(email.orElse(null));
                userService.createUser(user);
            }
            return webFilterChain.filter(serverWebExchange);
        });
    }
}

我在/foo/bar路径下有一个POST端点,预期被请求时会记录日志并返回模拟响应,但向该端点发送POST请求时仅得到200 OK响应,请问这是什么原因?

分析与解决方案

核心原因

你的JITUserFilter存在两个关键问题:

  • 未处理Principal为空的情况:无需认证的/foo/**端点请求中没有Principal对象,serverWebExchange.getPrincipal()会返回空Mono。你只通过flatMap处理了有Principal的场景,空Mono不会执行后续逻辑,也不会调用webFilterChain.filter(serverWebExchange),导致请求被“挂起”,Webflux最终默认返回200 OK,但实际端点从未被执行。
  • 潜在阻塞风险:如果userService.findUserByExternalId和userService.createUser是同步阻塞方法(比如基于JDBC的调用),会破坏Webflux的非阻塞特性,可能引发线程池阻塞问题。

修复步骤

  1. 补充Principal为空的分支处理:使用switchIfEmpty确保无论是否存在Principal,请求都能传递到后续过滤器和端点:
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
                         WebFilterChain webFilterChain) {
    return serverWebExchange.getPrincipal()
            .flatMap(p -> {
                Optional<String> email = Optional.empty();
                if (p instanceof JwtAuthenticationToken) {
                    email = Optional.of(((JwtAuthenticationToken)p).getToken().getClaimAsString("https://my-app.example.com/email"));
                }
                Optional<User> userOpt = userService.findUserByExternalId(p.getName());
                if (userOpt.isEmpty()) {
                    logger.info("No existing user found; creating new with externalId {}", p.getName());
                    User user = new User();
                    user.setExternalId(p.getName());
                    user.setEmail(email.orElse(null));
                    userService.createUser(user);
                }
                return webFilterChain.filter(serverWebExchange);
            })
            // 处理无Principal的情况,继续请求链
            .switchIfEmpty(webFilterChain.filter(serverWebExchange));
}
  1. 适配非阻塞调用(推荐):如果userService的方法是阻塞式的,需要用Mono.fromCallable包装,符合Webflux的非阻塞模型:
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
                         WebFilterChain webFilterChain) {
    return serverWebExchange.getPrincipal()
            .flatMap(p -> {
                Optional<String> email = Optional.empty();
                if (p instanceof JwtAuthenticationToken) {
                    email = Optional.of(((JwtAuthenticationToken)p).getToken().getClaimAsString("https://my-app.example.com/email"));
                }
                // 包装阻塞查询
                return Mono.fromCallable(() -> userService.findUserByExternalId(p.getName()))
                        .flatMap(userOpt -> {
                            if (userOpt.isEmpty()) {
                                // 包装阻塞创建操作
                                return Mono.fromCallable(() -> {
                                    User user = new User();
                                    user.setExternalId(p.getName());
                                    user.setEmail(email.orElse(null));
                                    return userService.createUser(user);
                                });
                            }
                            return Mono.empty();
                        })
                        // 完成用户操作后继续请求链
                        .then(webFilterChain.filter(serverWebExchange));
            })
            .switchIfEmpty(webFilterChain.filter(serverWebExchange));
}
  1. 安全配置验证:当前安全配置中permitAll()已正确放行/foo/**,securityMatcher仅排除了/actuator/**,对目标端点无影响,这部分无需修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 16:24:56