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

已认证用户随机触发AuthenticationCredentialsNotFoundException问题排查

Spring Security OAuth2 偶发401错误:认证成功后SecurityContext丢失

问题现象

使用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();
    }
}

问题根源分析

这种偶发的上下文丢失问题,核心原因是线程上下文传递异常:

  1. Spring Security默认通过ThreadLocal存储SecurityContext,若请求处理中存在异步操作(如@Async方法、线程池任务),子线程不会自动继承父线程的ThreadLocal上下文,导致方法校验时SecurityContext为空。
  2. 部分场景下,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 03:18:11