Spring Security+GraphQL(Spring3.2.2)403权限问题咨询
问题场景
在Spring 3.2.2环境中整合Spring Security与Spring GraphQL时,遇到如下问题:
- 按照Spring GraphQL文档说明,Web GraphQL端点可通过HTTP URL安全机制保护,仅允许认证用户访问
- 常规Spring Security配置在测试Controller上验证正常,但
@QueryMapping接口请求时出现无提示403错误 - 调试发现授权检查失败,SecurityContext中的用户为
anonymous,而非安全过滤器中注入的认证用户 - 临时解决方案:将
/graphql/**设置为permitAll(),并通过AOP拦截请求做权限验证,但不确定该问题是Bug还是预期行为
原Security配置(未添加/graphql/**的permitAll()时):
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http // Enable CORS and disable CSRF // Setup authorization .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .requestMatchers("/swagger-ui/**").permitAll() .requestMatchers("/v3/api-docs/**").permitAll() .requestMatchers("/error").permitAll() .requestMatchers("/actuator/**").permitAll() .requestMatchers("/**").hasRole("LOGIN") .anyRequest() .authenticated()) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ).addFilterBefore( this.jwtTokenFilter, UsernamePasswordAuthenticationFilter.class ) .cors(cors -> cors.disable()) .csrf(csrf -> csrf.disable()) .build(); }
问题根源
定位后发现:@QueryMapping的解析逻辑被内部CompletableFuture异步包装,执行完成后SecurityContext被清空,后续内部过滤器做权限检查时,上下文已变为匿名上下文,最终返回403错误。
这是因为Spring Security默认将SecurityContext绑定到ThreadLocal,异步任务执行时如果没有正确传递上下文,子线程无法获取到父线程的认证信息,导致后续校验失败。
解决方案
方案1:配置SecurityContext传播策略
在application.properties或application.yml中添加配置,让SecurityContext可被子线程继承:
spring.security.strategy=MODE_INHERITABLETHREADLOCAL
该配置会将SecurityContext的存储策略改为InheritableThreadLocal,异步任务执行时能自动继承父线程的认证上下文。
方案2:GraphQL层面权限校验(替代方案)
如果方案1不生效,可保留/graphql/**的permitAll()配置,转而在GraphQL接口层面做权限控制:
- 直接在
@QueryMapping/@MutationMapping方法上添加Spring Security的@PreAuthorize注解,比如:
@QueryMapping @PreAuthorize("hasRole('LOGIN')") public Book getBookById(@Argument Long id) { // 业务逻辑 }
- 或者通过AOP拦截所有GraphQL映射方法,统一做权限校验,实现更细粒度的控制。
方案3:自定义GraphQL执行策略
自定义ExecutionStrategy,在执行查询时手动传递SecurityContext:
@Component public class ContextPropagatingExecutionStrategy extends AsyncExecutionStrategy { @Override protected CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { SecurityContext context = SecurityContextHolder.getContext(); return super.execute(executionContext, parameters) .whenComplete((result, throwable) -> SecurityContextHolder.setContext(context)); } }
将该自定义策略注册到Spring容器中,覆盖默认的异步执行策略。
补充说明
该问题并非Spring Security或Spring GraphQL的预期行为,属于异步处理时的上下文传递漏洞,后续版本可能会修复。优先推荐方案1,若无效再考虑方案2或3。
内容的提问来源于stack exchange,提问作者Ed .d

