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

Spring 3升级后CSRF令牌刷新失效,请求仍返回403问题求助

问题

我们生产环境的应用采用以下逻辑刷新CSRF令牌,该逻辑在Spring 2中可正常运行,核心是CSRF令牌过期时静默刷新以避免中断会话。但升级至Spring 3后,初始令牌能正常设置,刷新令牌后的请求仍返回403,且初始有效令牌与刷新后的令牌格式差异显著:

后端代码

private static final String CSRF_TOKEN_SETTER = "window.import.meta.env.CSRF_TOKEN=\"%s\";";

// 从请求设置初始令牌
@GetMapping(value = "/csrf.js", produces = "application/javascript; charset=utf-8")
public String csrf(HttpServletRequest request) {
    if (request.getAttribute("_csrf") instanceof CsrfToken token) {
        return String.format(CSRF_TOKEN_SETTER, token.getToken());
    }
    return "ERROR";
}
 
// Spring升级后失效的刷新接口
@GetMapping(value = "/refresh_csrf_token")
public ResponseEntity<String> refreshCsrfToken(HttpServletRequest request, HttpServletResponse response) {
    CsrfToken newToken = csrfTokenRepository.generateToken(request);
    csrfTokenRepository.saveToken(newToken, request, response);

    return ResponseEntity.ok(newToken.getToken());
}

前端逻辑

if (statusCodeIs403) {
    refreshCsrfTokenAndAppendToHeadersOfFutureRequests();
}

令牌格式对比

  • 有效初始令牌:sBkVwbeM-hgCeILgJFU6zADrNZkMpcnkG3i0vzqSMIR6MWis1Xgk9Y-6mCkvT-DWE3gO9WPeGPs4xvrJKUqCjQ7wCLEZVF6Z
  • 刷新后令牌:3c78e076-9431-4f70-97a8-0aa8760037a7

CsrfTokenRepository配置

@Bean
@Lazy
public CsrfTokenRepository csrfTokenRepository() {
    HttpSessionCsrfTokenRepository tokenRepository = new HttpSessionCsrfTokenRepository();
    tokenRepository.setHeaderName("X-CSRF-TOKEN");
    return tokenRepository;
}

请问是否遗漏令牌保存步骤?或是Spring 3的哪些变化导致了该问题?


分析与解决方案

核心问题原因

Spring 3对应的Spring Security 6.x版本对HttpSessionCsrfTokenRepository做了两处关键变更,直接导致你的刷新逻辑失效:

  1. 令牌生成规则变更:旧版本默认用SecureRandom生成长字符串令牌,新版本默认生成UUID格式令牌,这就是两种格式差异的原因,但格式本身不是403的直接诱因。
  2. 令牌验证绑定规则严格化:
    • 刷新接口用GET请求:Spring Security默认忽略GET请求的CSRF验证,导致生成新令牌时,会话上下文未正确初始化,新令牌无法与当前会话建立绑定关系。
    • 未验证旧令牌直接生成新令牌:Spring Security 6要求刷新令牌前必须先验证旧令牌的有效性,直接生成并保存的新令牌,不会被后续请求的验证逻辑认可。

修复步骤

  1. 将刷新接口改为POST请求
    只有POST请求会触发CSRF验证流程,确保会话与令牌上下文正确关联:

    @PostMapping(value = "/refresh_csrf_token")
    public ResponseEntity<String> refreshCsrfToken(HttpServletRequest request, HttpServletResponse response) {
        // 先读取旧令牌,Spring Security会自动完成有效性验证,无效则直接返回403
        CsrfToken existingToken = csrfTokenRepository.loadToken(request);
        if (existingToken == null) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid session");
        }
        // 生成并保存新令牌
        CsrfToken newToken = csrfTokenRepository.generateToken(request);
        csrfTokenRepository.saveToken(newToken, request, response);
        return ResponseEntity.ok(newToken.getToken());
    }
    
  2. 前端请求刷新接口时携带当前令牌
    调用刷新接口必须在请求头中带上当前有效的X-CSRF-TOKEN,否则Spring Security会拦截该POST请求:

    async function refreshCsrfTokenAndAppendToHeadersOfFutureRequests() {
        const currentToken = window.import.meta.env.CSRF_TOKEN;
        const response = await fetch('/refresh_csrf_token', {
            method: 'POST',
            headers: {
                'X-CSRF-TOKEN': currentToken,
                'Content-Type': 'application/json'
            }
        });
        if (response.ok) {
            window.import.meta.env.CSRF_TOKEN = await response.text();
        }
    }
    
  3. 保持令牌存储键默认值
    HttpSessionCsrfTokenRepository默认的会话存储键为org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN,不要自定义该键,否则验证时无法读取到新令牌。

可选:恢复旧令牌格式

如果需要保持旧版本的长字符串令牌格式,可以自定义令牌生成器:

@Bean
public CsrfTokenRepository csrfTokenRepository() {
    HttpSessionCsrfTokenRepository tokenRepository = new HttpSessionCsrfTokenRepository();
    tokenRepository.setHeaderName("X-CSRF-TOKEN");
    // 自定义令牌生成逻辑,生成与旧版本一致的长字符串
    tokenRepository.setTokenGenerator(() -> {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[32];
        random.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    });
    return tokenRepository;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:28:13