Spring Webflux中如何通过SecurityContextHolder获取JWT相关SecurityContext
问题解决:WebFlux中无法通过SecurityContextHolder获取SecurityContext
核心原因
Spring WebFlux采用响应式编程模型,传统SecurityContextHolder基于ThreadLocal实现,仅适配Servlet的线程绑定机制,在响应式环境中无法正常存储和读取安全上下文。响应式场景下必须使用ReactiveSecurityContextHolder来获取上下文信息。
修改步骤
1. 替换业务代码中的上下文获取方式
将业务代码里的:
SecurityContext ctx = SecurityContextHolder.getContext();
替换为响应式的获取逻辑:
import org.springframework.security.core.context.ReactiveSecurityContextHolder; import reactor.core.publisher.Mono; import org.springframework.security.oauth2.jwt.Jwt; // 在响应式方法中使用 Mono<SecurityContext> securityContextMono = ReactiveSecurityContextHolder.getContext(); // 示例:获取JWT及用户权限 securityContextMono .map(SecurityContext::getAuthentication) .filter(Authentication::isAuthenticated) .map(Authentication::getPrincipal) .cast(Jwt.class) .subscribe(jwt -> { String userId = jwt.getClaim("sub"); Collection<GrantedAuthority> authorities = jwt.getAuthorities(); // 业务逻辑处理 });
如果是在@RestController的接口中,推荐直接通过方法参数注入Authentication或SecurityContext,更简洁且符合响应式规范:
import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import org.springframework.security.oauth2.jwt.Jwt; @RestController public class ProtectedController { @GetMapping("/api/v3/protected/user-info") public Mono<String> getCurrentUserInfo(Authentication authentication) { Jwt jwt = (Jwt) authentication.getPrincipal(); return Mono.just("当前用户ID:" + jwt.getClaim("sub") + ",权限:" + authentication.getAuthorities()); } }
2. 清理Security配置中的冗余项
你的WebSecurityConfig同时配置了authenticationManager(am)和oauth2ResourceServer.jwt(...),但Spring Security的OAuth2资源服务器JWT认证会自动生成ReactiveAuthenticationManager,手动配置可能导致上下文存储冲突。建议移除冗余配置,简化为:
@Bean public SecurityWebFilterChain filterChain(final ServerHttpSecurity http) { return http .cors(Customizer.withDefaults()) .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) .formLogin(ServerHttpSecurity.FormLoginSpec::disable) .csrf(ServerHttpSecurity.CsrfSpec::disable) .authorizeExchange(e -> { e.pathMatchers("/api/v3/protected/**").hasAuthority("MY_CLAIMS") .pathMatchers("/api/**").permitAll() .pathMatchers("/docs/**").permitAll() .pathMatchers("/actuator/**").permitAll(); }) .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt( jwt -> jwt.jwtAuthenticationConverter(getJwtAuthenticationConverter()))) .build(); }
3. 优化自定义权限转换器的兼容性
将CustomJwtGrantedAuthoritiesConverter中的ArrayList强转改为更通用的List类型,避免类型限制:
@SuppressWarnings("unchecked") public Collection<GrantedAuthority> convert(final Jwt jwt) { return this.authoritiesClaimNames.stream().map(claimName -> { final var claims = jwt.getClaim(claimName); if (claims instanceof String) { return List.of((String) claims); } else if (claims instanceof List) { return (List<String>) claims; } else { return Collections.<String>emptyList(); } }).flatMap(Collection::stream) .map(SimpleGrantedAuthority::new) .toList(); }
关键注意点
- 响应式环境中,安全上下文存储在Reactor的
Context中,而非ThreadLocal,禁止在响应式流外部调用ReactiveSecurityContextHolder.getContext().block(),这会破坏响应式模型并导致上下文丢失。 - 确保所有需要获取安全上下文的逻辑都在响应式流(
Mono/Flux)中处理。
内容的提问来源于stack exchange,提问作者Jiinxy
相关产品推荐
相关产品推荐

