告别隐藏字段:现代 CSRF 保护,无需 Token

发布: (2025年12月26日 GMT+8 00:02)
4 min read
原文: Dev.to

Source: Dev.to

如果你曾经配置过针对跨站请求伪造(CSRF)攻击的防护,你可能还记得那套常规做法:生成唯一的令牌,将其嵌入隐藏的表单字段,并确保你的脚本发送正确的头部信息。在2025年,这个过程可以大幅简化,同时让你的代码更加简洁。

传统 CSRF 令牌

  • 状态同步 – 令牌要求服务器和客户端保持共享状态,增加了页面缓存的复杂性。
  • 标记膨胀 – 每个表单都需要额外的隐藏输入。
  • 调试痛点 – 过期令牌错误会消耗宝贵的开发时间。

这些缺点使得基于令牌的防护感觉像是不可避免的技术债务。

基于浏览器的保护与 Fetch Metadata

现代浏览器现在会发送一种特殊的头部,称为 Fetch Metadata。最关键的元素是 Sec-Fetch-Site 头部,它告诉服务器请求的真实来源。

请求来源头部值
同源表单提交Sec-Fetch-Site: same-origin
跨站请求(例如恶意站点)Sec-Fetch-Site: cross-site

浏览器保证此头部不能被 JavaScript 伪造或修改,服务器可以完全依赖它。

旧版浏览器的回退方案

如果浏览器未发送 Fetch Metadata,你可以回退检查 OriginReferer 头部。same-originnone(例如直接输入 URL)的请求被允许;其他请求则被拒绝。

实现示例

以下是 Node.js(Express)和 Python(Flask)的最小示例,用于拒绝对状态更改方法的跨站请求。

Express(Node.js)

// app.js
const express = require('express');
const app = express();

function csrfProtection(req, res, next) {
  const method = req.method.toUpperCase();
  const unsafeMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];

  if (!unsafeMethods.includes(method)) {
    return next(); // Safe method, no check needed
  }

  const site = req.get('Sec-Fetch-Site');
  if (site && site === 'cross-site') {
    return res.status(403).send('Forbidden: CSRF protection');
  }

  // Fallback for older browsers
  const origin = req.get('Origin') || req.get('Referer');
  if (origin && !origin.includes(req.get('Host'))) {
    return res.status(403).send('Forbidden: CSRF protection');
  }

  next();
}

app.use(express.json());
app.use(csrfProtection);

app.post('/api/data', (req, res) => {
  // Handle state‑changing request
  res.json({ status: 'success' });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Flask(Python)

# app.py
from flask import Flask, request, abort, jsonify

app = Flask(__name__)

def csrf_protect():
    unsafe_methods = {'POST', 'PUT', 'PATCH', 'DELETE'}
    if request.method not in unsafe_methods:
        return  # Safe method

    site = request.headers.get('Sec-Fetch-Site')
    if site == 'cross-site':
        abort(403, description='Forbidden: CSRF protection')

    # Fallback for older browsers
    origin = request.headers.get('Origin') or request.headers.get('Referer')
    if origin and request.host not in origin:
        abort(403, description='Forbidden: CSRF protection')

app.before_request(csrf_protect)

@app.route('/api/data', methods=['POST'])
def handle_data():
    # Process the request
    return jsonify(status='success')

if __name__ == '__main__':
    app.run(port=5000)

基于 Header 的 CSRF 防护优势

  • 无需生成或存储令牌 – 消除会话端状态并减少标记。
  • 缓存友好 – 表单可以缓存而无需担心令牌过期。
  • 调试更简便 – 错误仅表现为标准 HTTP 状态码。
  • 性能提升 – 传输字节更少,服务器端处理更少。

行业认可

OWASP 项目已将 Fetch Metadata 视为经典 anti‑CSRF 令牌的可行替代方案,将其从实验状态提升为推荐实践。

结论

浏览器正变得越来越智能,能够自动处理常规的安全任务。通过将 CSRF 保护从隐藏字段和令牌转移到可靠的请求元数据,您可以在保持相同安全水平的同时,实现更简洁、更轻量的代码库。这标志着向更简洁、更易维护的网络迈出了一步。

Back to Blog

相关文章

阅读更多 »

了解 JSON Web Token (JWT)

在创建 Web 应用程序的某个时刻,我们需要为系统开发一个认证解决方案。有多种策略可以实现,例如 aute...