已认证用户随机触发AuthenticationCredentialsNotFoundException问题排查
问题现象
使用Spring Security OAuth2实现用户认证,大部分请求正常响应,但随机在不同端点返回401错误,抛出以下异常:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:333)
从请求日志可见,OAuth2AuthenticationProcessingFilter已成功完成用户认证,但后续执行方法级权限校验时,SecurityContext中却找不到认证信息:
DEBUG o.s.s.o.p.a.OAuth2AuthenticationProcessingFilter - Authentication success: OAuth2Authentication [Principal=3025598727402, Credentials=[PROTECTED], Authenticated=true, Details=remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>, Granted Authorities=[FOURNISSEUR]] ... TRACE o.s.s.w.a.ExceptionTranslationFilter - Sending to authentication entry point since authentication failed org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
代码与配置
端点示例(含方法级权限校验)
@GetMapping(value = "/getAllNotifications") @PreAuthorize("@authorizationService.isGlnEqual(#gencodDeclarant, authentication.name)") public NotificationDto getAllNotifications(@RequestParam("gencodDeclarant") String gencodDeclarant) { return notificationService.getAllNotifications(gencodDeclarant); }
环境依赖
- spring-boot v2.5.7
- spring-security-oauth2 v2.3.3.RELEASE
- spring-security-jwt v1.0.7.RELEASE
资源服务器配置
@Override public void configure(HttpSecurity http) throws Exception { http.csrf() .disable() .requestMatchers() .and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/services/**/public/**") .permitAll() .antMatchers("/services/**") .authenticated(); }
方法安全配置
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return getOAuth2MethodSecurityExpressionHandler(); } @Bean public OAuth2MethodSecurityExpressionHandler getOAuth2MethodSecurityExpressionHandler() { return new OAuth2MethodSecurityExpressionHandler(); } }
问题根源分析
这种偶发的上下文丢失问题,核心原因是线程上下文传递异常:
- Spring Security默认通过
ThreadLocal存储SecurityContext,若请求处理中存在异步操作(如@Async方法、线程池任务),子线程不会自动继承父线程的ThreadLocal上下文,导致方法校验时SecurityContext为空。 - 部分场景下,
SecurityContextPersistenceFilter在请求结束阶段过早清理SecurityContext,但后续方法级校验逻辑仍在执行。
解决方案
1. 异步任务的上下文传递配置
若项目使用异步方法或自定义线程池,需为线程池添加TaskDecorator,手动传递SecurityContext:
@Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); executor.setThreadNamePrefix("Async-"); // 复制父线程SecurityContext到子线程 executor.setTaskDecorator(runnable -> { SecurityContext context = SecurityContextHolder.getContext(); return () -> { try { SecurityContextHolder.setContext(context); runnable.run(); } finally { SecurityContextHolder.clearContext(); } }; }); executor.initialize(); return executor; } }
2. 切换SecurityContext存储策略
将默认的MODE_THREADLOCAL改为MODE_INHERITABLETHREADLOCAL,让子线程自动继承父线程上下文(仅适用于直接创建子线程的场景,线程池场景仍需结合上述配置):
@Configuration public class SecurityContextConfig { @PostConstruct public void initSecurityContextStrategy() { SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL); } }
3. 调整过滤器执行顺序
确保SecurityContextPersistenceFilter在OAuth2AuthenticationProcessingFilter之前执行,避免过早清理上下文:
@Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .requestMatchers() .and() .addFilterBefore( new SecurityContextPersistenceFilter(new HttpSessionSecurityContextRepository()), OAuth2AuthenticationProcessingFilter.class ) .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/services/**/public/**").permitAll() .antMatchers("/services/**").authenticated(); }
4. 升级依赖版本
当前使用的spring-security-oauth2 v2.3.3.RELEASE已停止维护,存在已知的上下文传递bug。建议升级到Spring Boot 2.5.x兼容的最新版本,或者迁移到Spring Security 5.x官方推荐的spring-security-oauth2-resource-server模块。
验证方式
在出现问题的端点添加日志,打印SecurityContextHolder.getContext()的状态,同时排查请求链路中是否存在异步调用,确认上下文传递是否正常。
内容的提问来源于stack exchange,提问作者gb2

