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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 02:20:22