您现在的位置是:网站首页 > 防御 CSRF 的常见方法(如 Token 验证)文章详情

防御 CSRF 的常见方法(如 Token 验证)

防御 CSRF 的常见方法(如 Token 验证)

CSRF(跨站请求伪造)是一种常见的网络攻击方式,攻击者利用用户已登录的身份,在用户不知情的情况下执行恶意操作。防御 CSRF 的方法有多种,其中 Token 验证是最常用的手段之一。其他方法还包括 SameSite Cookie、双重 Cookie 验证等。

Token 验证的原理

Token 验证的核心思想是在请求中携带一个随机生成的令牌(Token),服务器通过验证该令牌的合法性来判断请求是否可信。Token 通常存储在用户的会话(Session)或 Cookie 中,并在每次请求时由前端发送给服务器。

Token 的生成和验证流程如下:

  1. 用户登录时,服务器生成一个随机 Token 并存储在 Session 中。
  2. 服务器将 Token 返回给前端,前端通常将其存储在表单的隐藏字段或 HTTP 请求头中。
  3. 用户提交请求时,前端将 Token 一并发送给服务器。
  4. 服务器验证 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 的方法,其原理是:

  1. 前端在请求时从 Cookie 中读取某个值(如 csrf_token)。
  2. 将该值作为参数或请求头(如 X-CSRF-Token)发送给服务器。
  3. 服务器比较两者是否一致。

示例实现:

// 前端代码
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 的存储方式直接影响安全性:

  1. 表单隐藏字段:适用于传统表单提交。
    <input type="hidden" name="_csrf" value="token-value">
    
  2. Meta 标签:适用于单页应用(SPA)。
    <meta name="csrf-token" content="token-value">
    
  3. 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 防御手段,但建议与其他安全措施配合使用:

  1. 验证 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();
    });
    
  2. 关键操作要求二次验证:如短信验证码、密码确认等。
  3. 限制敏感操作的 HTTP 方法:如修改数据只允许 POST 请求。

常见问题与解决方案

  1. Token 泄露风险

    • 配合 HTTPS 使用,防止 Token 被中间人攻击窃取。
    • 避免在 URL 中传递 Token(可能被日志记录)。
  2. 多标签页问题

    • 如果用户在多个标签页登录,应确保每个标签页使用独立的 Token 或实现 Token 同步机制。
  3. 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 验证方案。

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步