首页 > web前端 > js教程 > 正文

Spring Security 6中单页应用(SPA)的CSRF令牌处理指南

碧海醫心
发布: 2025-09-30 14:40:02
原创
590人浏览过

Spring Security 6中单页应用(SPA)的CSRF令牌处理指南

本文详细阐述了在Spring Security 6环境下,单页应用(SPA)如何正确处理CSRF令牌以避免常见的“令牌比较失败”问题。针对Spring Security 6引入的BREACH攻击防护机制,我们指出客户端不应直接读取和设置XSRF-TOKEN cookie。相反,推荐的解决方案是后端提供一个专门的/csrf接口,供客户端获取处理后的CSRF令牌,并将其作为X-XSRF-TOKEN头发送,从而确保安全、高效地完成CSRF防护。

Spring Security 6中CSRF防护机制的演进

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方法会进行以下步骤:

  1. 对客户端发送的actualToken(即X-XSRF-TOKEN头的值)进行Base64解码。
  2. 将服务器内部维护的token(实际的CSRF令牌)进行UTF-8编码。
  3. 比较解码后的actualBytes长度与tokenBytes长度。如果actualBytes长度小于tokenBytes长度(例如,24 < 36),则直接返回null。这通常意味着客户端发送的令牌不是经过XOR处理的完整令牌。
  4. 如果长度匹配,它会尝试将actualBytes分解为随机字节和XOR加密后的CSRF字节,然后进行异或还原操作,最终得到原始CSRF令牌。

如果客户端直接发送了未经过XOR处理的原始令牌,那么上述步骤中的长度检查就会失败,或者异或还原会得到错误的结果,最终导致令牌比较失败。

正确的CSRF令牌获取与使用策略

为了与Spring Security 6的CSRF防护机制兼容,单页应用应遵循以下策略:

1. 后端提供专门的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接口时,例如,它会得到类似如下的响应:

AppMall应用商店
AppMall应用商店

AI应用商店,提供即时交付、按需付费的人工智能应用服务

AppMall应用商店 56
查看详情 AppMall应用商店
{
    "parameterName": "_csrf",
    "token": "pKpetTa2tv6yhmnko7ZvtaiP7CgX8cB5uBbR1G-ZR6NiKTpIkcltgALX0J-fslyGkJtbgpG_wRFxl_VUgCDm513_dMVUGQ15",
    "headerName": "X-XSRF-TOKEN"
}
登录后复制

同时,浏览器会自动接收并存储一个名为XSRF-TOKEN的HTTP Only cookie,其中包含原始的、未经过XOR处理的CSRF令牌。这个cookie由浏览器自动管理,客户端JavaScript无需直接访问。

2. 客户端获取并使用令牌

客户端(例如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 });
});
登录后复制

关键注意事项:

  • 浏览器自动处理Cookie: 客户端JavaScript代码不需要不应该尝试读取或操作XSRF-TOKEN cookie。浏览器会根据HTTP Only标志和同源策略自动处理这个cookie,并在每次请求时将其发送到服务器。
  • 获取令牌时机: 通常在应用启动时获取一次CSRF令牌。如果令牌有过期机制,可能需要在特定时间间隔或令牌验证失败时刷新。
  • HTTPS: 始终通过HTTPS传输数据,以防止中间人攻击和令牌泄露。
  • 安全性: 这种方法确保了CSRF令牌的生成和验证过程完全由Spring Security处理,客户端只负责获取并转发服务器提供的已处理令牌,从而有效抵御CSRF攻击和BREACH攻击。

总结

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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号