您现在的位置是:网站首页 > 防御 CSRF 的常见方法(如 Token 验证)文章详情
防御 CSRF 的常见方法(如 Token 验证)
陈川
【
前端安全
】
8068人已围观
5085字
防御 CSRF 的常见方法(如 Token 验证)
CSRF(跨站请求伪造)是一种常见的网络攻击方式,攻击者利用用户已登录的身份,在用户不知情的情况下执行恶意操作。防御 CSRF 的方法有多种,其中 Token 验证是最常用的手段之一。其他方法还包括 SameSite Cookie、双重 Cookie 验证等。
Token 验证的原理
Token 验证的核心思想是在请求中携带一个随机生成的令牌(Token),服务器通过验证该令牌的合法性来判断请求是否可信。Token 通常存储在用户的会话(Session)或 Cookie 中,并在每次请求时由前端发送给服务器。
Token 的生成和验证流程如下:
- 用户登录时,服务器生成一个随机 Token 并存储在 Session 中。
- 服务器将 Token 返回给前端,前端通常将其存储在表单的隐藏字段或 HTTP 请求头中。
- 用户提交请求时,前端将 Token 一并发送给服务器。
- 服务器验证 Token 是否与 Session 中的 Token 一致,若一致则处理请求,否则拒绝。
Token 验证的实现示例
以下是一个基于 Express 和前端 JavaScript 的 Token 验证实现示例:
后端代码(Node.js + Express)
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));
// 生成 CSRF Token 的中间件
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(16).toString('hex');
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// 表单页面
app.get('/form', (req, res) => {
res.send(`
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="${res.locals.csrfToken}">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`);
});
// 提交处理
app.post('/submit', (req, res) => {
const { _csrf, data } = req.body;
if (_csrf !== req.session.csrfToken) {
return res.status(403).send('CSRF Token 验证失败');
}
// 处理合法请求
res.send(`提交成功: ${data}`);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
前端代码(AJAX 请求示例)
// 从 Cookie 或 Meta 标签中获取 CSRF Token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// 发起 AJAX 请求时携带 Token
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ key: 'value' })
});
SameSite Cookie 属性
除了 Token 验证,现代浏览器还支持通过 Cookie 的 SameSite 属性来防御 CSRF。SameSite 有三个可选值:
Strict
: Cookie 仅在同站请求中发送。Lax
: Cookie 在跨站 GET 请求中发送,但 POST 请求不发送(默认值)。None
: 关闭 SameSite 限制(需要同时设置 Secure 属性)。
示例设置:
// Express 中设置 SameSite Cookie
app.use(session({
secret: 'your-secret-key',
cookie: {
secure: true,
sameSite: 'strict'
}
}));
双重 Cookie 验证
双重 Cookie 验证是另一种防御 CSRF 的方法,其原理是:
- 前端在请求时从 Cookie 中读取某个值(如
csrf_token
)。 - 将该值作为参数或请求头(如
X-CSRF-Token
)发送给服务器。 - 服务器比较两者是否一致。
示例实现:
// 前端代码
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const csrfToken = getCookie('csrf_token');
fetch('/api/data', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
}
});
// 后端验证
app.post('/api/data', (req, res) => {
const cookieToken = req.cookies.csrf_token;
const headerToken = req.headers['x-csrf-token'];
if (cookieToken && cookieToken === headerToken) {
// 验证通过
} else {
res.status(403).send('CSRF 验证失败');
}
});
Token 存储的最佳实践
Token 的存储方式直接影响安全性:
- 表单隐藏字段:适用于传统表单提交。
<input type="hidden" name="_csrf" value="token-value">
- Meta 标签:适用于单页应用(SPA)。
<meta name="csrf-token" content="token-value">
- HTTP 头:适用于 AJAX 请求。
headers: { 'X-CSRF-Token': 'token-value' }
避免将 Token 存储在 localStorage 或 sessionStorage 中,因为这些存储方式可能被 XSS 攻击利用。
Token 的更新策略
为了提高安全性,Token 应该定期更新:
- 每次登录后生成新 Token。
- 重要操作(如修改密码)前刷新 Token。
- 设置 Token 过期时间。
示例 Token 刷新逻辑:
app.post('/refresh-token', (req, res) => {
if (req.session.csrfToken === req.body.oldToken) {
req.session.csrfToken = crypto.randomBytes(16).toString('hex');
res.json({ newToken: req.session.csrfToken });
} else {
res.status(403).send('Invalid token');
}
});
其他防御措施的配合使用
虽然 Token 验证是有效的 CSRF 防御手段,但建议与其他安全措施配合使用:
- 验证 HTTP Referer 头:检查请求是否来自合法域名。
app.use((req, res, next) => { const referer = req.get('Referer'); if (referer && !referer.startsWith('https://yourdomain.com')) { return res.status(403).send('Invalid request source'); } next(); });
- 关键操作要求二次验证:如短信验证码、密码确认等。
- 限制敏感操作的 HTTP 方法:如修改数据只允许 POST 请求。
常见问题与解决方案
-
Token 泄露风险:
- 配合 HTTPS 使用,防止 Token 被中间人攻击窃取。
- 避免在 URL 中传递 Token(可能被日志记录)。
-
多标签页问题:
- 如果用户在多个标签页登录,应确保每个标签页使用独立的 Token 或实现 Token 同步机制。
-
API 兼容性问题:
- 对于对外开放的 API,可以考虑使用 OAuth 2.0 等授权机制替代 CSRF Token。
框架内置的 CSRF 防护
许多现代框架已内置 CSRF 防护:
- Django: 自动为表单添加
{% csrf_token %}
标签。 - Ruby on Rails: 通过
protect_from_forgery
方法默认启用。 - Spring Security: 配置
csrf().disable()
可关闭(不推荐)。
以 Django 为例:
<form method="post">
{% csrf_token %}
<input type="text" name="username">
</form>
性能与安全性的平衡
虽然 CSRF 防护会增加一定的开发复杂度和性能开销,但通过以下方式可以优化:
- 对静态资源禁用 CSRF 检查。
- 使用高效的 Token 生成算法(如 UUID 而非加密随机数)。
- 在负载均衡环境下,确保 Session 共享或使用无状态的 Token 验证方案。
上一篇: CSRF 攻击的危害
下一篇: SameSite Cookie 机制