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

SpringBoot3.3.5升级后遇401:SecurityContext无Authentication对象

SpringBoot 3.3.5(适配Spring Security 6)升级后SecurityContext认证对象丢失导致401问题解决

问题现象

升级后系统出现401错误,报错信息为:An Authentication object was not found in the SecurityContext。日志显示认证对象已标记authenticated=true,但后续过滤器链执行时,SecurityContext被重新创建为无认证对象的实例,最终授权环节失败。

问题根源分析

  1. SecurityContextRepository配置冲突:配置了DelegatingSecurityContextRepository同时包含RequestAttributeSecurityContextRepository和HttpSessionSecurityContextRepository,但系统设置为SessionCreationPolicy.STATELESS,HttpSession不存在,后续过滤器尝试从Session获取SecurityContext失败,重新创建空上下文。
  2. SecurityContextHolder操作不当:在CustomAuthenticationFilter和CustomAuthenticationProvider中重复设置MODE_INHERITABLETHREADLOCAL策略,且Provider中调用SecurityContextHolder.clearContext()清空了已设置的认证对象,未同步到请求属性中。
  3. 认证上下文未持久化:认证完成后仅设置了SecurityContextHolder,但未将上下文保存到配置的RequestAttributeSecurityContextRepository,导致后续过滤器无法从正确位置获取认证信息。

修复方案

1. 修正SecurityContextRepository配置

无状态场景下直接使用RequestAttributeSecurityContextRepository即可,无需混合Session存储:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth.requestMatchers("/", "/**/*.html", "/**/*.css", "/**/*.js"
                            , "/ecafPostapid/**", "/ecafPostpaid/healthtest/checktest","/ecafPostpaid/ecafPostpaid/health"
                    ).permitAll().anyRequest().authenticated())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .exceptionHandling(ex -> ex.authenticationEntryPoint(this.ecafAuthenticationEntryPoint))
            .anonymous(anonymous -> anonymous.disable())
            .securityContext(securityContext -> securityContext
                    .securityContextRepository(new RequestAttributeSecurityContextRepository()));

    httpSecurity.addFilterBefore(new CustomAuthenticationFilter(this.authenticationManager, this.encDecAlgorithm, this.environment, this.filterManager), UsernamePasswordAuthenticationFilter.class);

    return httpSecurity.build();
}

2. 调整CustomAuthenticationFilter逻辑

移除不必要的策略设置,认证完成后将上下文保存到Repository:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {
    try {
        request.setAttribute("startTime", System.currentTimeMillis());
        MDC.put("requestID", request.getHeader("X-eCAF-RequestId"));
        CustomAuthentication authentication = new CustomAuthentication();
        String stringToken = request.getHeader("x-access-token");
        String source = Objects.toString(request.getHeader("source"));

        authentication.setPrincipal(stringToken);
        authentication.setSource(source);

        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);

        Authentication authenticated = this.authenticationManager.authenticate(authentication);
        context.setAuthentication(authenticated);
        
        // 将认证上下文保存到请求属性中
        SecurityContextRepository repository = new RequestAttributeSecurityContextRepository();
        repository.saveContext(context, request, response);

        LOGGER.info("The Authentication {}", authenticated);
        chain.doFilter(request, response);
    } catch (Exception e) {
        MDC.clear();
        throw e;
    } finally {
        SecurityContextHolder.clearContext();
        MDC.clear();
    }
}

3. 修正CustomAuthenticationProvider逻辑

移除直接操作SecurityContextHolder的代码,Provider仅负责返回已认证对象:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken userNameToken = new UsernamePasswordAuthenticationToken(
            jwtToken.getPrincipal(), jwtToken.getCredentials(), jwtToken.getAuthorities());
    jwtPayload.setToken(accessToken);
    userNameToken.setDetails(jwtPayload);

    userNameToken.setAuthenticated(true);
    if (userNameToken.isAuthenticated()) {
        ((CustomAuthentication) authentication).setAuthenticated(true);
    }
    return userNameToken;
}

关键说明

  • 在Spring Security 6中,无状态场景下推荐使用RequestAttributeSecurityContextRepository,将SecurityContext存储在请求属性中,避免Session相关问题。
  • 自定义认证过滤器中,认证完成后必须将SecurityContext保存到配置的Repository,后续过滤器才能正确获取认证信息。
  • AuthenticationProvider的职责是验证请求并返回已认证对象,不应直接操作SecurityContextHolder,该操作应由过滤器或框架处理。

内容的提问来源于stack exchange,提问作者Pragati Yadav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:54:52