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

Spring Security 6下CSRF Token校验失败问题求助

问题

我正在开发一个基于Svelte前端、Spring Boot 3(3.0.6版本)后端的应用,为防范BREACH攻击配置了Spring Security的相关防护机制。

前端侧,我原本的做法是在发送POST请求前读取XSRF-TOKEN Cookie的值,直接复制到X-XSRF-TOKEN请求头中。但调试发现,CsrfFilter能从请求头提取Token值,但XorCsrfTokenRequestAttributeHandler::getTokenValue方法在接收两个相同字符串(原始Token)时返回null。

查看该方法代码:

// example call: actualToken = token = "b4b40051-9f64-4d8e-9092-cc84cc769ae0"
private static String getTokenValue(String actualToken, String token) {
    byte[] actualBytes;
    try {
        actualBytes = Base64.getUrlDecoder().decode(actualToken);
    }
    catch (Exception ex) {
        return null;
    }

    byte[] tokenBytes = Utf8.encode(token);
    int tokenSize = tokenBytes.length;
    if (actualBytes.length < tokenSize) {
        // 24 < 36 so we arrive here
        return null;
    }

    // extract token and random bytes
    int randomBytesSize = actualBytes.length - tokenSize;
    byte[] xoredCsrf = new byte[tokenSize];
    byte[] randomBytes = new byte[randomBytesSize];

    System.arraycopy(actualBytes, 0, randomBytes, 0, randomBytesSize);
    System.arraycopy(actualBytes, randomBytesSize, xoredCsrf, 0, tokenSize);

    byte[] csrfBytes = xorCsrf(randomBytes, xoredCsrf);
    return Utf8.decode(csrfBytes);
}

正常情况下该方法应返回与token相同的值,且equalsConstantTime(csrfToken.getToken(), actualToken)返回true。请问需要对XSRF-TOKEN Cookie的值做何种处理后,再复制到X-XSRF-TOKEN请求头中?

解决方案

直接传原始Token会导致getTokenValue方法解码失败或长度校验不通过,因为该方法期望的是经过XOR混淆+URL安全Base64编码后的字符串。你需要在前端执行以下步骤处理Token:

  1. 读取原始Token并转字节数组:从XSRF-TOKEN Cookie获取原始字符串,转为UTF-8编码的字节数组(记为csrfBytes)。
  2. 生成随机字节数组:生成一段任意长度的随机字节(比如16字节,用于混淆),记为randomBytes。
  3. 执行XOR混淆:遍历csrfBytes的每个字节,与randomBytes[i % randomBytes.length]做按位异或运算,得到混淆后的xoredCsrfBytes。
  4. 拼接字节数组:将randomBytes和xoredCsrfBytes按顺序拼接成一个新的字节数组combinedBytes。
  5. URL安全Base64编码:对combinedBytes执行URL安全的Base64编码(去除填充符=),得到最终的Token字符串。
  6. 设置请求头:将编码后的字符串放入X-XSRF-TOKEN请求头中发送。

示例Svelte处理代码

// 读取指定Cookie
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

// 处理CSRF Token
async function prepareCsrfToken() {
    const rawCsrfToken = getCookie('XSRF-TOKEN');
    if (!rawCsrfToken) return '';

    // 转UTF-8字节数组
    const csrfBytes = new TextEncoder().encode(rawCsrfToken);
    // 生成16字节随机数
    const randomBytes = crypto.getRandomValues(new Uint8Array(16));
    // 执行XOR混淆
    const xoredCsrfBytes = new Uint8Array(csrfBytes.length);
    for (let i = 0; i < csrfBytes.length; i++) {
        xoredCsrfBytes[i] = csrfBytes[i] ^ randomBytes[i % randomBytes.length];
    }
    // 拼接随机字节与混淆后的Token
    const combinedBytes = new Uint8Array([...randomBytes, ...xoredCsrfBytes]);
    // URL安全Base64编码(移除填充)
    const encodedToken = btoa(String.fromCharCode(...combinedBytes))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    
    return encodedToken;
}

// 发送POST请求示例
async function sendPostRequest(url, data) {
    const csrfToken = await prepareCsrfToken();
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-XSRF-TOKEN': csrfToken
        },
        body: JSON.stringify(data)
    });
    return response;
}

原理说明

XorCsrfTokenRequestAttributeHandler的设计目标是防范BREACH攻击:通过随机字节与原始Token做XOR混淆,再编码传输,避免Token在请求头中以明文形式出现,从而降低BREACH攻击的利用风险。

方法内部会先解码请求头中的字符串,拆分出随机字节和混淆后的Token字节,再通过XOR还原出原始Token,与后端存储的Token做一致性校验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:02:46