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

Spring Boot3+Security6:Mono/Flux端点返回403 Forbidden问题排查

解决Spring Boot 3 + Spring Security 6响应式端点JWT认证403问题

核心原因

非响应式端点依赖Servlet栈的Spring Security配置,而响应式端点需要WebFlux栈的Reactive配置,两套体系不能混用。只替换UserDetailsService为ReactiveUserDetailsService不足以解决问题,必须整套切换响应式配置逻辑。

具体解决步骤

  1. 调整项目依赖与配置

    • 纯响应式项目:移除spring-boot-starter-web,仅保留spring-boot-starter-webflux和spring-boot-starter-security;
    • 混合栈项目(同时支持Servlet/Reactive端点):保留两个依赖,在application.yml中添加配置:
      spring:
        main:
          web-application-type: reactive
      
  2. 替换为响应式Security配置类
    将原Servlet栈的SecurityFilterChain替换为Reactive栈的ReactiveSecurityWebFilterChain,示例代码:

    @Configuration
    @EnableWebFluxSecurity
    public class SecurityConfig {
    
        @Bean
        public ReactiveSecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            http
                // 配置公开访问路径
                .authorizeExchange(exchanges -> exchanges
                    .pathMatchers("/login", "/register").permitAll()
                    .anyExchange().authenticated()
                )
                // 配置JWT资源服务器逻辑
                .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(jwt -> jwt
                        .jwtDecoder(jwtDecoder())
                        .jwtAuthenticationConverter(jwtAuthenticationConverter())
                    )
                )
                .csrf(ServerHttpSecurity.CsrfSpec::disable); // 前后端分离场景禁用CSRF
            
            return http.build();
        }
    
        // 自定义JWT解码器(根据实际密钥/配置调整)
        @Bean
        public ReactiveJwtDecoder jwtDecoder() {
            // 示例:使用对称密钥解码JWT
            SecretKey secretKey = Keys.hmacShaKeyFor("your-jwt-secret-xxx".getBytes(StandardCharsets.UTF_8));
            return NimbusReactiveJwtDecoder.withSecretKey(secretKey).build();
        }
    
        // 自定义JWT权限映射逻辑
        @Bean
        public Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {
            JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
            authoritiesConverter.setAuthorityPrefix("ROLE_");
            authoritiesConverter.setAuthoritiesClaimName("roles");
    
            return jwt -> Mono.just(
                new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt))
            );
        }
    }
    
  3. 正确实现ReactiveUserDetailsService
    确保类实现org.springframework.security.core.userdetails.ReactiveUserDetailsService接口,示例:

    @Service
    public class ReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService {
    
        private final UserRepository userRepository;
        private final PasswordEncoder passwordEncoder;
    
        public ReactiveUserDetailsServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
            this.userRepository = userRepository;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public Mono<UserDetails> findByUsername(String username) {
            return userRepository.findByUsername(username)
                .switchIfEmpty(Mono.error(new UsernameNotFoundException("用户不存在")))
                .map(user -> User.withUsername(user.getUsername())
                    .password(user.getPassword())
                    .authorities(user.getRoles().stream().map(SimpleGrantedAuthority::new).toList())
                    .accountExpired(!user.isAccountNonExpired())
                    .accountLocked(!user.isAccountNonLocked())
                    .credentialsExpired(!user.isCredentialsNonExpired())
                    .disabled(!user.isEnabled())
                    .build());
        }
    }
    
  4. 检查响应式控制器实现
    确保控制器方法返回Mono/Flux,避免混用Servlet特有的API,示例:

    @RestController
    @RequestMapping("/api/user")
    public class UserController {
    
        @GetMapping("/me")
        public Mono<ResponseEntity<UserDTO>> getCurrentUser(Authentication authentication) {
            UserDTO dto = UserDTO.builder()
                .username(authentication.getName())
                .roles(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList())
                .build();
            return Mono.just(ResponseEntity.ok(dto));
        }
    }
    
  5. 调试排查(若仍有403)
    添加日志跟踪认证流程:

    • 在ReactiveUserDetailsServiceImpl的findByUsername方法中添加日志,确认是否被调用;
    • 自定义ReactiveAuthenticationManager打印认证结果:
      @Bean
      public ReactiveAuthenticationManager reactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
          UserDetailsRepositoryReactiveAuthenticationManager manager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
          manager.setPasswordEncoder(passwordEncoder);
          manager.setAuthenticationSuccessHandler((auth, exchange) -> {
              System.out.println("认证成功:" + auth.getName());
              return Mono.empty();
          });
          manager.setAuthenticationFailureHandler((e, exchange) -> {
              System.err.println("认证失败:" + e.getMessage());
              return Mono.empty();
          });
          return manager;
      }
      

    在springSecurityFilterChain中注入该Bean,验证认证环节是否正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:25:01