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

Spring SecurityContextHolder无法维持认证状态问题求助

问题分析与解决方案

你的问题核心是认证状态无法跨请求持久化,根源在于:

  • SecurityContextHolder 默认通过 ThreadLocal 存储认证信息,仅在当前请求线程内有效,后续请求是全新线程,自然无法读取之前的认证数据。
  • 你手动完成了认证流程,但未将认证结果持久化到会话(或通过令牌传递),导致Spring Security在后续请求中无法识别用户身份。

方案一:基于HttpSession的会话式认证(适合传统Web场景)

如果你的系统依赖会话机制,可以修改登录端点,将认证信息存入HttpSession,让Spring Security的默认机制自动加载:

修改登录控制器代码,注入HttpServletRequest获取会话:

@RestController
@RequestMapping("/api/account/login")
public class LoginRestController {

    @Autowired
    AuthenticationManager authenticationManager;

    @PostMapping("")
    public ResponseEntity<String> login(@RequestBody LoginPayload payload, HttpServletRequest request) {
        try {
            Authentication auth = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(payload.getEmail(), payload.getPassword()));

            // 将认证信息存入HttpSession,供后续请求加载
            SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
            securityContext.setAuthentication(auth);
            request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
            
            return new ResponseEntity<>("用户已认证", HttpStatus.OK);
        } catch (Exception e) {
            SecurityContextHolder.getContext().setAuthentication(null);
            return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED);
        }
    }
}

你的SecurityConfig已经启用了会话管理(默认配置),后续请求会自动从HttpSession中加载SecurityContext,无需额外修改配置。

方案二:基于JWT的无状态认证(适合RESTful API场景)

如果是纯REST接口,推荐使用无状态的JWT令牌实现跨请求身份验证,步骤如下:

1. 添加JWT依赖(以jjwt为例)

在pom.xml中加入依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2. 编写JWT工具类

@Component
public class JwtTokenUtil {
    // 生产环境建议通过环境变量配置密钥
    private String secretKey = "your-secure-secret-key";
    private long expirationMs = 86400000; // 令牌有效期24小时

    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expirationMs))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getExpiration()
                .before(new Date());
    }
}

3. 修改登录端点返回JWT令牌

@RestController
@RequestMapping("/api/account/login")
public class LoginRestController {

    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    JwtTokenUtil jwtTokenUtil;
    @Autowired
    UserDetailsService userDetailsService;

    @PostMapping("")
    public ResponseEntity<Map<String, String>> login(@RequestBody LoginPayload payload) {
        try {
            Authentication auth = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(payload.getEmail(), payload.getPassword()));
            
            String token = jwtTokenUtil.generateToken(auth);
            Map<String, String> response = new HashMap<>();
            response.put("token", token);
            return new ResponseEntity<>(response, HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(Map.of("error", e.getMessage()), HttpStatus.UNAUTHORIZED);
        }
    }
}

4. 添加JWT认证过滤器

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        String token = null;
        String username = null;

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            token = authHeader.substring(7);
            username = jwtTokenUtil.extractUsername(token);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (jwtTokenUtil.validateToken(token, userDetails)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }
}

5. 修改SecurityConfig适配JWT

@Configuration
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/api/account/register", "/api/account/register/**").permitAll()
                        .requestMatchers("/api/account/login").permitAll()
                        .requestMatchers("/api/aziende/exists").permitAll()
                        .requestMatchers("/api", "/api/**").authenticated()
                        .requestMatchers("/app", "/app/**").authenticated()
                        .requestMatchers("/", "/**").permitAll()
                )
                // 禁用表单登录和默认登出,改用JWT
                .formLogin(form -> form.disable())
                .logout(logout -> logout.disable())
                // 设置为无状态会话
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 添加JWT过滤器到认证流程前
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    AuthenticationManager authenticationManager(
            AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
}

关键注意事项

  • 会话式认证需要前端保持会话(如携带Cookie),适合前后端不分离的场景。
  • JWT认证是无状态的,前端需在每次请求的Authorization头中携带Bearer <token>,适合前后端分离的REST API。
  • 生产环境中,JWT密钥必须通过环境变量配置,禁止硬编码;令牌过期时间需根据业务需求调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 02:50:27