
spring security 6引入了增强的csrf防护机制,特别是针对breach攻击(browser reconnaissance and exfiltration via adaptive compression of hypertext)进行了优化。这一改变影响了单页应用(spa)客户端与后端进行csrf令牌交互的方式。在之前的版本中,许多spa框架或自定义逻辑会直接从xsrf-token cookie中读取令牌,然后将其复制到请求头x-xsrf-token中。然而,在spring security 6中,这种做法可能导致csrf令牌验证失败,表现为xorcsrftokenrequestattributehandler::gettokenvalue方法返回null。
导致此问题的原因在于,Spring Security 6为了抵御BREACH攻击,对CSRF令牌的生成和验证过程进行了修改。它不再仅仅是一个简单的随机字符串,而是可能经过XOR加密处理,将实际的CSRF令牌与随机字节进行异或操作,然后进行Base64编码。因此,直接从cookie中获取的“原始”令牌与系统期望进行比较的“处理后”令牌不再匹配。
当客户端尝试将从XSRF-TOKEN cookie中读取的原始值直接作为X-XSRF-TOKEN头发送时,Spring Security后端的XorCsrfTokenRequestAttributeHandler会尝试解码并解异或这个令牌。其核心逻辑getTokenValue方法会进行以下步骤:
如果客户端直接发送了未经过XOR处理的原始令牌,那么上述步骤中的长度检查就会失败,或者异或还原会得到错误的结果,最终导致令牌比较失败。
为了与Spring Security 6的CSRF防护机制兼容,单页应用应遵循以下策略:
客户端不应直接从cookie中获取令牌。相反,后端应该提供一个专用的RESTful接口,当客户端首次加载应用或需要刷新令牌时,通过GET请求访问该接口。此接口将返回一个包含CSRF令牌详细信息的JSON对象。
Spring Boot 后端示例代码:
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CsrfController {
/**
* 提供一个接口供前端获取CSRF令牌信息。
* Spring Security会自动将CsrfToken注入到方法参数中,
* 并确保相应的原始令牌已通过cookie发送给客户端。
*
* @param csrfToken Spring Security自动注入的CSRF令牌对象
* @return 包含令牌名称、值和头名称的CsrfToken对象
*/
@GetMapping("/csrf")
public CsrfToken csrfToken(CsrfToken csrfToken) {
return csrfToken;
}
}当客户端访问/csrf接口时,例如,它会得到类似如下的响应:
{
"parameterName": "_csrf",
"token": "pKpetTa2tv6yhmnko7ZvtaiP7CgX8cB5uBbR1G-ZR6NiKTpIkcltgALX0J-fslyGkJtbgpG_wRFxl_VUgCDm513_dMVUGQ15",
"headerName": "X-XSRF-TOKEN"
}同时,浏览器会自动接收并存储一个名为XSRF-TOKEN的HTTP Only cookie,其中包含原始的、未经过XOR处理的CSRF令牌。这个cookie由浏览器自动管理,客户端JavaScript无需直接访问。
客户端(例如Svelte、React、Vue等SPA框架)在发送需要CSRF保护的请求(如POST、PUT、DELETE)之前,应首先向/csrf接口发起GET请求,获取上述JSON响应中的token值和headerName。
Svelte/JavaScript 客户端概念代码示例:
// 假设您正在使用fetch API
let csrfTokenValue = '';
let csrfHeaderName = 'X-XSRF-TOKEN'; // 默认值,但最好从后端响应获取
/**
* 从后端获取CSRF令牌。
* 通常在应用初始化时调用一次,或在令牌过期时刷新。
*/
async function fetchCsrfToken() {
try {
const response = await fetch('/csrf');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
csrfTokenValue = data.token;
csrfHeaderName = data.headerName; // 确保使用后端指定的头名称
console.log('CSRF Token fetched:', csrfTokenValue);
} catch (error) {
console.error('Failed to fetch CSRF token:', error);
// 处理错误,例如重定向到登录页或显示错误消息
}
}
/**
* 发送一个带有CSRF令牌的POST请求。
* @param {string} url - 请求的URL
* @param {object} data - 请求体数据
*/
async function sendPostRequest(url, data) {
if (!csrfTokenValue) {
// 如果令牌未获取,尝试获取或提示错误
console.warn('CSRF Token is not available. Attempting to fetch...');
await fetchCsrfToken();
if (!csrfTokenValue) {
console.error('CSRF Token still not available after fetch attempt. Cannot send request.');
return;
}
}
try {
const headers = {
'Content-Type': 'application/json',
// 将从/csrf接口获取的令牌值设置到相应的请求头中
[csrfHeaderName]: csrfTokenValue
};
const response = await fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log('Request successful:', responseData);
return responseData;
} catch (error) {
console.error('Error sending POST request:', error);
// 处理请求错误
}
}
// 示例用法:
// 在应用启动时获取令牌
fetchCsrfToken().then(() => {
// 之后可以发送POST请求
sendPostRequest('/api/some-resource', { name: 'example', value: 123 });
});关键注意事项:
Spring Security 6在CSRF防护方面带来了重要的改进,特别是通过XorCsrfTokenRequestAttributeHandler增强了对BREACH攻击的防御。对于单页应用而言,这意味着传统的直接读取XSRF-TOKEN cookie并将其设置到请求头的方法不再适用。正确的做法是:后端暴露一个/csrf接口,返回包含编码后令牌的CsrfToken对象;客户端通过GET请求获取此令牌,并将其设置到后续POST、PUT、DELETE等请求的X-XSRF-TOKEN头中。遵循这一模式,可以确保您的Spring Boot应用在Spring Security 6环境下拥有健壮且符合最新安全标准的CSRF防护。
以上就是Spring Security 6中单页应用(SPA)的CSRF令牌处理指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号