本文目录导读:

关于CSRF令牌的配置和使用,我可以为你提供一个比较全面的指导,CSRF(跨站请求伪造)是一种常见的Web安全漏洞,而使用CSRF令牌是防范这种攻击最核心的手段。
我将从基本原理、通用配置步骤、不同框架的实现方式以及常见问题和最佳实践这几个方面来为你解答。
核心原理一句话
服务器生成一个随机的、难以猜测的令牌(Token),并放在用户的会话(Session)里,在每次需要保护的表单或请求中,客户端都需要带上这个令牌,服务器在处理请求时,会比对请求中的令牌和会话中的令牌是否一致,如果不一致,则拒绝请求,由于攻击者无法得知用户会话中的令牌值,因此便无法伪造有效的请求。
通用配置与使用步骤(核心流程)
无论你使用哪种编程语言或框架,配置CSRF令牌的逻辑大体相同,分为以下三步:
第1步:服务端生成令牌并存储
- 时机:在用户会话(Session)开始时,或者用户访问包含表单的页面时。
- 操作:生成一个高熵值的随机字符串,通常由框架或安全库自动完成。
- 存储:将这个令牌存储在用户的服务端会话(如
$_SESSION、request.session)中,而不是直接放在Cookie里,有些实现也会将其放在加密的Cookie中(双重提交Cookie模式)。
第2步:将令牌嵌入到请求中(前端)
这是最关键的环节,你需要让所有会产生副作用的请求(主要是 POST、PUT、DELETE 请求)都带上这个令牌。
-
方法1:HTML表单(最常见)
- 在每个
form标签内,添加一个隐藏的input字段,其值为从服务器获取的CSRF令牌。 - 示例 (HTML):
<form action="/transfer_funds" method="POST"> <!-- 以下隐藏字段是无价之宝 --> <input type="hidden" name="_csrf_token" value="{{ csrf_token_from_server }}"> <input type="text" name="amount"> <input type="submit" value="转账"> </form>
- 在每个
-
方法2:AJAX / Fetch 请求
- 对于现代单页应用(SPA)或使用JS发起的请求,无法使用隐藏表单字段,常用的做法是:
- 从服务器获取令牌:通常在页面加载时,将CSRF令牌放在
<meta>标签中。<meta name="csrf-token" content="{{ csrf_token }}"> - 在请求头中添加:使用JavaScript读取meta标签的值,然后在每个非GET请求的HTTP头中携带它。
// 假设你使用 fetch fetch('/api/user/delete', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content') }, body: JSON.stringify({ userId: 123 }) });
- 从服务器获取令牌:通常在页面加载时,将CSRF令牌放在
- 对于现代单页应用(SPA)或使用JS发起的请求,无法使用隐藏表单字段,常用的做法是:
第3步:服务端验证令牌
- 时机:在服务器处理所有会产生副作用的请求(POST, PUT, DELETE, PATCH)时。
- 操作:从请求中提取令牌(从表单字段
_csrf_token或请求头X-CSRF-Token中获取),然后与服务端会话中存储的令牌进行比较。 - 结果:
- 匹配:请求合法,继续处理业务逻辑。
- 不匹配或缺失:直接拒绝请求(返回403 Forbidden错误),并记录安全日志。
主流框架的具体配置示例
为了方便你理解,我给出几个最常见的框架配置方式。
Spring Security (Java/Spring Boot)
Spring Security 默认启用CSRF保护。
- 配置:在
SecurityFilterChain中,默认是启用的,你可以选择关闭它(不推荐)或自定义。 - 使用:
- Thymeleaf模板:在
form中,Spring会自动添加_csrf隐藏字段。<form th:action="@{/transfer}" method="post"> <!-- 无需手动添加,Spring会自动注入,生成如下 --> <!-- <input type="hidden" name="_csrf" value="aaabbbcccdddeeefff" /> --> <input type="text" name="amount"/> </form> - AJAX请求:需要从
meta标签或Cookie中获取令牌,并放在请求头X-CSRF-TOKEN中。<!-- 在HTML head中 --> <meta name="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
- Thymeleaf模板:在
Django (Python)
Django 在中间件中内置了CSRF保护。
- 配置:确保
django.middleware.csrf.CsrfViewMiddleware在MIDDLEWARE设置中。 - 使用:
- Django模板:在
form中使用{% csrf_token %}模板标签。<form method="post"> {% csrf_token %} <!-- 这会在表单顶部生成一个隐藏的 <input> 字段 --> </form> - AJAX请求:需要在请求头中添加
X-CSRFToken,其值通常从Cookiecsrftoken中获取。// 使用jQuery的例子 var csrftoken = Cookies.get('csrftoken'); $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!this.crossDomain) { xhr.setRequestHeader('X-CSRFToken', csrftoken); } } }); - 特定视图豁免:在极少数情况下(如Webhook回调),可以使用
@csrf_exempt装饰器。
- Django模板:在
Express (Node.js) + 通用中间件
Node.js本身没有内置,但可以通过 csurf 或 csurf 的继任者(如 csurf-csurf)等中间件实现。
-
安装与配置:
npm install csurf
-
使用:
const csrfProtection = csurf({ cookie: true }); // 选项很多 const express = require('express'); const app = express(); app.use(csrfProtection); // 对全局或特定路由生效 // 在渲染表单的GET路由中,将令牌传递给模板 app.get('/form', function (req, res) { res.render('send', { csrfToken: req.csrfToken() }); });- 模板:
<form action="/process" method="POST"> <input type="hidden" name="_csrf" value="{{ csrfToken }}"> <input type="text" name="message"/> </form> - AJAX:将
_csrf的值放在请求头csrf-token中。
- 模板:
常见问题与最佳实践 (Gold Rules)
-
不要为GET请求添加CSRF保护,GET请求应该是幂等的(只读、不修改状态),对GET请求进行CSRF保护会破坏浏览器的正常行为,如缓存、书签、地址栏直接访问等。
-
令牌必须与用户会话强绑定,令牌必须存储在服务器端会话中,或者利用双重提交Cookie模式(Double Submit Cookie)进行验证,但绝对不能轻易地被攻击者通过跨域脚本(XSS)获取,如果网站存在XSS漏洞,CSRF保护会瞬间失效。
-
令牌必须具有时效性,设置令牌的过期时间(例如30分钟或1小时),防止令牌被无限期重用,用户登出后,令牌应立即失效。
-
令牌应足够随机,使用密码学安全的伪随机数生成器(CSPRNG)来生成令牌,避免使用可预测的序列号或时间戳。
-
同源检测是CSRF的第一道防线,不要完全依赖CSRF令牌,你必须检查请求的
Origin或Referer头,如果来源不是你的合法域名,即使令牌正确也应该拒绝,这是防御CSRF最基础、最有效的机制。 -
考虑“双重提交Cookie”模式,如果你的应用是完全前后端分离、没有服务器端Session的API服务(例如基于JWT的认证),可以使用双重提交Cookie模式:服务器设置一个随机Cookie,客户端在请求头中携带这个Cookie的值,服务器比较两者是否一致(或通过哈希验证),这种方式不需要服务器存储会话状态。
-
保护所有敏感操作,不仅是登录和支付,任何修改数据的操作(如更改邮箱、密码、权限、发布内容等)都应该受到保护。
-
妥善处理CSRF失败,当检测到CSRF失败时,不要只返回一个模糊的错误。必须记录详细的异常日志(包含IP、用户ID、请求URL、时间戳),用于将来可能的分析或追踪攻击,给用户一个友好的错误提示,并建议其刷新页面尝试。
配置CSRF令牌的核心是三步走:
- 服务端生成并存储:在一个安全的位置(Session)生成一个强随机值。
- 前端携带令牌:在所有会产生副作用的请求(POST、PUT、DELETE等)中,以隐藏表单字段或自定义HTTP头的方式,将这个值发回给服务器。
- 服务端验证并拒绝:在处理请求时,比较请求中的令牌与会话中的令牌,如果不匹配,直接拒绝并记录。
不同的框架(Spring Security、Django、Express等)都有自己的封装方式,但本质都是实现以上逻辑,在实际开发中,推荐尽可能使用框架自带的CSRF保护机制,而不是自己造轮子,因为自己实现的方案很可能存在安全漏洞。
保护好你的CSRF令牌,就是保护你用户的数据安全,如果你在具体实现中遇到问题(比如在某种特定架构下如何实现),可以告诉我你的技术栈,我可以提供更具体的配置示例。