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

