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

Spring Boot Reactive应用JWT认证后用户存储问题求助

问题1:过滤器执行两次的解决办法

  • 替换自定义过滤器为认证成功处理器
    你当前自行解析JWT的方式既未验证签名等安全信息,又容易因过滤器重复触发导致重复执行。Spring Security OAuth2 Resource Server已完成JWT的验证与解析,直接利用原生的认证成功回调即可避免重复执行:

    修改SecurityConfiguration:

    @EnableWebFluxSecurity
    @EnableReactiveMethodSecurity
    @Slf4j
    @RequiredArgsConstructor
    public class SecurityConfiguration {
    
        private final UserSavingAuthenticationSuccessHandler successHandler;
    
        @Bean
        SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            http.authorizeExchange()
                    .anyExchange().authenticated()
                    .and()
                    .oauth2ResourceServer()
                    .jwt()
                    .authenticationSuccessHandler(successHandler);
    
            return http.build();
        }
    
    }
    
  • 实现ServerAuthenticationSuccessHandler处理用户存储
    该处理器仅在认证成功时触发一次,从Spring Security已解析的认证对象中获取用户信息:

    @Component
    @Slf4j
    @RequiredArgsConstructor
    public class UserSavingAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
    
        private final UserService userService;
    
        @Override
        public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
            if (authentication instanceof JwtAuthenticationToken jwtToken) {
                Jwt jwt = jwtToken.getToken();
                String email = jwt.getClaimAsString("email");
                String firstName = jwt.getClaimAsString("given_name");
                String lastName = jwt.getClaimAsString("family_name");
    
                return userService.existsByEmail(email)
                        .flatMap(exists -> {
                            if (exists) {
                                return Mono.empty();
                            }
                            User user = new User();
                            user.setEmail(email);
                            user.setFirstName(firstName);
                            user.setLastName(lastName);
                            log.info("Saving new user with email: {}", email);
                            return userService.save(user);
                        })
                        .then(webFilterExchange.getChain().filter(webFilterExchange.getExchange()));
            }
            return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
        }
    }
    

问题2:getAll()无法返回本次请求存储用户的解决办法

问题根源是你之前的代码使用toFuture().get()(阻塞操作破坏响应式上下文)和subscribe()(异步操作脱离请求链条),导致存储操作与请求流程未绑定,请求返回时数据可能还未写入MongoDB。

上述ServerAuthenticationSuccessHandler的实现中,通过响应式链式调用将存储操作与请求链条绑定:

  • 用flatMap串联“检查用户是否存在→保存新用户”的逻辑
  • 最后用then()确保存储操作完成后再继续处理请求

同时确保你的UserService和Repository是响应式实现:

// UserService示例
public interface UserService {
    Mono<Boolean> existsByEmail(String email);
    Mono<User> save(User user);
    Flux<User> getAll();
}

// Repository示例
public interface UserRepository extends ReactiveMongoRepository<User, String> {
    Mono<Boolean> existsByEmail(String email);
}

额外优化建议

  • 移除原UserSavingFilter,依赖Spring Security的JWT解析结果,避免重复造轮子且更安全
  • 永远不要在响应式代码中使用block()、toFuture().get()等阻塞操作,会引发性能问题和上下文丢失
  • 非必要不要用subscribe()触发异步操作,需等待结果的操作必须纳入响应式链条

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 21:48:32