CSRF Token 是防御 CSRF 攻击最有效的方法之一,它通过验证请求的合法性来防止跨站请求伪造。
工作原理
1. Token 生成
- 服务器在用户会话中生成随机 Token
- Token 必须足够随机(建议至少 128 位)
- Token 可以有时效性
- Token 与用户会话绑定
2. Token 传递
- Token 通过表单隐藏字段传递
- 或通过自定义请求头传递(如
X-CSRF-Token) - Token 也可以存储在 Cookie 中(双重提交)
3. Token 验证
- 服务器收到请求后验证 Token
- 检查请求中的 Token 是否与会话中的匹配
- 验证 Token 是否过期
- 验证 Token 是否已被使用(可选)
实现流程
shell用户访问页面 → 服务器生成 CSRF Token → Token 存储在会话中 ↓ Token 添加到表单/请求头 → 用户提交表单/发送请求 ↓ 服务器验证 Token → Token 匹配 → 执行操作 ↓ Token 不匹配 → 拒绝请求
代码实现
后端实现(Node.js + Express)
javascriptconst express = require('express'); const crypto = require('crypto'); const session = require('express-session'); const app = express(); app.use(session({ secret: 'your-secret-key', resave: false, saveUninitialized: true })); // 生成 CSRF Token function generateCSRFToken() { return crypto.randomBytes(32).toString('hex'); } // 中间件:生成 Token app.use((req, res, next) => { if (!req.session.csrfToken) { req.session.csrfToken = generateCSRFToken(); } res.locals.csrfToken = req.session.csrfToken; next(); }); // 中间件:验证 Token function validateCSRFToken(req, res, next) { const token = req.body._csrf || req.headers['x-csrf-token']; if (!token || token !== req.session.csrfToken) { return res.status(403).json({ error: 'Invalid CSRF token' }); } next(); } // 受保护的路由 app.post('/transfer', validateCSRFToken, (req, res) => { // 处理转账逻辑 res.json({ success: true }); }); app.listen(3000);
前端实现
html<!-- 表单提交 --> <form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="{{ csrfToken }}"> <input type="number" name="amount" placeholder="Amount"> <button type="submit">Transfer</button> </form> <!-- AJAX 请求 --> <script> const csrfToken = '{{ csrfToken }}'; fetch('/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ amount: 100 }) }) .then(response => response.json()) .then(data => console.log(data)); </script>
Spring Security 实现
java@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated(); } }
Django 实现
python# settings.py MIDDLEWARE = [ 'django.middleware.csrf.CsrfViewMiddleware', # ... other middleware ] # 模板中使用 <form method="post"> {% csrf_token %} <input type="text" name="amount"> <button type="submit">Submit</button> </form> # 视图中验证 from django.views.decorators.csrf import csrf_protect @csrf_protect def transfer(request): if request.method == 'POST': # 处理转账逻辑 pass
Token 生成算法
使用加密安全的随机数生成器
javascript// Node.js const crypto = require('crypto'); const token = crypto.randomBytes(32).toString('hex'); // Python import secrets token = secrets.token_hex(32) // Java import java.security.SecureRandom; import java.math.BigInteger; SecureRandom random = new SecureRandom(); String token = new BigInteger(130, random).toString(32);
安全注意事项
1. Token 随机性
- 使用加密安全的随机数生成器
- Token 长度至少 128 位(32 字节)
- 避免使用可预测的 Token
2. Token 时效性
- Token 应该有过期时间
- 建议设置合理的过期时间(如 1-2 小时)
- 敏感操作可以使用一次性 Token
3. Token 存储
- Token 存储在服务器会话中
- 不要在客户端存储敏感信息
- 使用 HttpOnly Cookie 存储 Token(双重提交)
4. Token 传输
- 使用 HTTPS 传输 Token
- 避免在 URL 中传递 Token
- 使用 POST 方法提交表单
5. Token 验证
- 验证 Token 是否匹配
- 验证 Token 是否过期
- 验证请求来源(可选)
常见问题
1. Token 失效
- 会话过期导致 Token 失效
- 解决:自动刷新 Token 或延长会话时间
2. 多标签页问题
- 多个标签页共享同一个 Token
- 解决:每个标签页使用独立 Token 或共享 Token
3. AJAX 请求
- 需要在请求头中添加 Token
- 解决:使用拦截器自动添加 Token
4. 文件上传
- 文件上传无法使用表单 Token
- 解决:使用请求头或预签名 URL
最佳实践
- 使用框架内置的 CSRF 保护:如 Spring Security、Django
- Token 与会话绑定:每个会话使用独立 Token
- 设置合理的过期时间:平衡安全性和用户体验
- 记录 Token 使用情况:便于审计和监控
- 定期更新 Token:降低 Token 泄露风险
- 配合其他防护措施:如 SameSite Cookie、Origin 验证
总结
CSRF Token 通过验证请求的合法性有效防止 CSRF 攻击。正确实现 CSRF Token 需要注意 Token 的生成、存储、传输和验证等各个环节,确保整个流程的安全性。使用框架内置的 CSRF 保护功能可以大大简化实现过程。