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

Spring Boot 3迁移后@PreAuthorize注解失效问题排查求助

问题:Spring Boot 3迁移后@PreAuthorize注解API调用401错误

问题背景

我们正将应用从Java 8 + Spring Boot 2迁移至Java 17 + Spring Boot 3,多数功能正常,但带有@PreAuthorize注解的API调用出现401认证失败。移除@PreAuthorize注解后接口可正常访问,且日志显示权限信息正确。

配置代码

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);

        http.csrf().disable();
        http.cors();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeHttpRequests()
            .requestMatchers("/api/**")
            .authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(new JwtAuthenticationConverter());

        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(
                properties.getJwt().getJwkSetUri()).build();

        return jwtDecoder;
    }
}

class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        return new JwtAuthenticationToken(jwt, new JwtAuthoritiesConverter().convert(jwt));
    }
    
}

class JwtAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

    @Override
    public Collection<GrantedAuthority> convert(Jwt jwt) {
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        grantedAuthorities.add(new SimpleGrantedAuthority("SOME_AUTHORITY"));

        return grantedAuthorities;
    }

}

示例接口方法

@PreAuthorize("hasAnyAuthority('SOME_AUTHORITY')")
@GetMapping("/someObjects") 
APIResponse<SomeObject> testMethod(JwtAuthenticationToken principal){
    
    List<GrantedAuthority> principalAuthorities= principal.getAuthorities().stream().collect(Collectors.toList());
    log.info("Authorities: " + principalAuthorities.toString());
    
    List<GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().collect(Collectors.toList());
    log.info("Security Context" + authorities.toString());
    
    return APIResponse.singleton(new SomeObject());
}

现象与日志

移除@PreAuthorize注解后(正常访问)

2023-06-28T09:48:27,618 INFO SomeController::testMethod(223): Authorities: [SOME_AUTHORITY]
2023-06-28T09:48:27,618 INFO SomeController: Security Context[SOME_AUTHORITY]

添加@PreAuthorize注解后(返回401)

2023-06-28T11:27:13,166 DEBUG o.s.s.o.s.r.w.a.BearerTokenAuthenticationFilter::doFilterInternal(143): Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@eb761cc7, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[SOME_AUTHORITY]]
...
2023-06-28T11:27:13,174 TRACE o.s.s.w.a.ExceptionTranslationFilter::handleAuthenticationException(184): Sending to authentication entry point since authentication failed
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

排查方向与原因解释

原因解释:SecurityContextHolder策略差异

  • MODE_THREADLOCAL:SecurityContext绑定到当前线程,Spring Security过滤器链和@PreAuthorize方法级权限校验在同一线程执行,能正常获取上下文。
  • MODE_INHERITABLETHREADLOCAL:原本用于让子线程继承父线程的SecurityContext,但Spring Boot 3(基于Spring Framework 6)中方法级安全校验的线程模型发生变化。当使用@PreAuthorize时,权限校验可能在不同线程执行,而InheritableThreadLocal的继承逻辑未覆盖该场景(如代理创建的线程上下文传递问题),导致校验线程无法获取父线程已设置的Authentication对象,抛出AuthenticationCredentialsNotFoundException。

排查方向

  • 检查方法级安全代理机制:Spring Boot 3默认使用CGLIB代理,可尝试显式配置@EnableMethodSecurity(proxyTargetClass = false)切换为JDK动态代理,验证是否解决问题。
  • 验证异步线程上下文传递:若应用存在异步处理,确认TaskExecutor是否配置了上下文传递,Spring Boot 3中TaskExecutor默认上下文传递行为可能调整,需确保SecurityContext能传递到异步线程。
  • 确认过滤器链执行顺序:检查BearerTokenAuthenticationFilter是否在方法级安全拦截器前执行,保证SecurityContext在校验前已正确设置。
  • 对比Spring Security版本差异:Spring Security 6在SecurityContext管理、方法安全校验流程上有调整,查看官方文档中@EnableMethodSecurity的配置细节,确认securedEnabled、jsr250Enabled等属性默认值是否变化。
  • 调试线程ID:在@PreAuthorize方法和过滤器中添加线程ID日志,对比两者线程ID是否一致,确认是否为线程上下文传递问题。

内容的提问来源于stack exchange,提问作者Chris G

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 00:15:10