Spring WebFlux中ReactiveSecurityContextHolder获取Authentication为null求助
问题描述
在Spring Boot WebFlux应用中,使用Spring Security实现了WebFilter,将Authentication存入ReactiveSecurityContextHolder,但获取时始终返回null。
复现代码
Spring Boot启动类
@EnableWebFlux @SpringBootApplication @EnableWebFluxSecurity @EnableReactiveMethodSecurity @Configuration public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http .csrf(csrfSpec -> csrfSpec.disable()) .authorizeExchange(auth -> auth.pathMatchers(HttpMethod.GET, "/**") .authenticated()) .httpBasic(httpBasicSpec -> httpBasicSpec.disable()) .addFilterBefore(customWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION) .build(); } public WebFilter customWebFilter() { return (exchange, chain) -> { Authentication authenticatedToken = new PreAuthenticatedAuthenticationToken("User_PreAuthenticated", ""); authenticatedToken.setAuthenticated(true); return chain.filter(exchange) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticatedToken)); }; } }
获取Authentication的代码(返回null)
// 此处Authentication始终为null Authentication authentication = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .toFuture().getNow(null);
更新补充场景
应用中使用graphql-java,需要在GraphQL DataFetcher处理查询时获取当前用户信息。Spring MVC环境下可通过ThreadLocal的SecurityContext获取,但WebFlux响应式环境中无法直接复用该方式。
GraphQL查询配置及DataFetcher代码
public List<TypeRuntimeWiring.Builder> buildWiring() { return Arrays.asList(newTypeWiring("Query").dataFetcher("getUserName", getCurrentUser())); } private DataFetcher getCurrentUser() { return dataFetchingEnvironment -> { String currentUser = "NOT_AVAILABLE"; Authentication authentication = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .toFuture().getNow(null); // 此处Authentication始终为null if (authentication != null) { Object principal = authentication.getPrincipal(); if (principal != null && principal instanceof User) { currentUser = ((User) principal).getUsername(); } } return currentUser; }; }
解决方案
核心原因
- 上下文传递时机错误:WebFilter中
contextWrite是在chain.filter(exchange)执行后写入上下文,导致后续请求处理环节无法拿到已写入的认证信息。 - 脱离响应式上下文链:使用
toFuture().getNow(null)这种同步阻塞方式获取认证信息,会脱离Reactor的响应式上下文,无法读取到之前存入的Authentication。 - GraphQL DataFetcher类型不匹配:普通DataFetcher的执行线程不在WebFlux的响应式上下文链中,无法继承ReactiveSecurityContextHolder的上下文。
具体修复步骤
1. 修正WebFilter的上下文写入逻辑
确保上下文写入在请求处理链执行前完成,保证后续环节能继承上下文:
public WebFilter customWebFilter() { return (exchange, chain) -> { Authentication authenticatedToken = new PreAuthenticatedAuthenticationToken("User_PreAuthenticated", ""); authenticatedToken.setAuthenticated(true); // 先写入上下文,再执行后续请求链 return Mono.defer(() -> chain.filter(exchange)) .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticatedToken)); }; }
2. 改用ReactiveDataFetcher适配响应式上下文
将普通DataFetcher替换为ReactiveDataFetcher,让其接入WebFlux的响应式上下文链:
private ReactiveDataFetcher<String> getCurrentUser() { return dataFetchingEnvironment -> ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .map(auth -> { if (auth != null && auth.getPrincipal() instanceof User) { return ((User) auth.getPrincipal()).getUsername(); } return "NOT_AVAILABLE"; }) .defaultIfEmpty("NOT_AVAILABLE"); }
3. 更新GraphQL Wiring配置
确保Wiring中使用响应式DataFetcher:
public List<TypeRuntimeWiring.Builder> buildWiring() { return Arrays.asList(newTypeWiring("Query") .dataFetcher("getUserName", getCurrentUser())); }
关键注意事项
- 禁止在响应式流中使用
block()、toFuture().get()等同步阻塞方法,这会破坏上下文传递并引发线程问题。 - ReactiveSecurityContextHolder的上下文仅存在于当前Reactor流中,脱离该流的代码无法读取到认证信息。
- 若使用
spring-boot-starter-graphql,需确保DataFetcher为响应式类型,才能自动继承WebFlux的上下文。
内容的提问来源于stack exchange,提问作者Amit Chudasama
相关产品推荐
相关产品推荐

