5月28日 06:58

REST API 的 CSRF 防护怎么做?认证方式不同策略完全不同

REST API 的 CSRF 防护和传统 Web 应用有明显差异。核心问题在于:REST API 可能被浏览器、移动应用、第三方服务等多种客户端调用,认证方式也不统一,防护策略必须因地制宜。

REST API 为什么仍需关注 CSRF

很多人认为 REST API 只用 JSON 就安全了,但事实并非如此。只要你的 API 满足以下条件,CSRF 风险就存在:

  • 使用 Cookie 进行身份认证(浏览器会自动携带)
  • 允许跨域请求(CORS 配置宽松)
  • 接受 application/json 以外的 Content-Type

攻击者的思路很直接:构造一个恶意页面,让已登录用户在不知情的情况下向你的 API 发起请求。如果认证靠 Cookie,浏览器会自动带上,服务端无法区分请求来源。

关键判断:你的认证方式决定防护策略

Token 认证(JWT / OAuth)— 天然免疫

Token 放在 Authorization 头中,浏览器不会自动发送,攻击者无法在跨站请求中携带。这是 REST API 最推荐的认证方式:

javascript
// 服务端验证 function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) return res.status(401).json({ error: "未提供认证令牌" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { res.status(401).json({ error: "令牌无效或已过期" }); } }

需要注意:Token 存储在 localStorage 有 XSS 风险,存 HttpOnly Cookie 又会回到 CSRF 问题。生产中推荐短期 Token + 内存存储,刷新 Token 放 HttpOnly Cookie 并配合 CSRF 防护。

Cookie 认证在前后端分离架构中仍然常见,尤其是需要与旧系统兼容时。这种场景下 CSRF 防护不可省略。

最简单的第一道防线,设置 SameSite 限制跨站 Cookie 发送:

javascript
app.use(session({ secret: process.env.SESSION_SECRET, cookie: { httpOnly: true, secure: true, sameSite: "strict" // 严格模式,完全禁止跨站发送 } }));

strict 最安全但会影响从外部链接进入的体验,lax 是更常见的折中选择——允许 GET 请求的顶级导航携带 Cookie,阻止跨站 POST 请求。

局限:并非所有浏览器都完整支持 SameSite(虽然主流浏览器已跟上),且移动端 WebView 行为不一致。不能作为唯一防线。

手段二:CSRF Token + 自定义请求头

这是最经典的防护方案,双重验证确保请求来源可信:

javascript
// 生成并下发 CSRF Token app.get("/api/csrf-token", (req, res) => { const token = crypto.randomBytes(32).toString("hex"); req.session.csrfToken = token; res.json({ csrfToken: token }); }); // 验证:通过自定义请求头传递 Token function csrfGuard(req, res, next) { // 安全方法跳过 if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const headerToken = req.headers["x-csrf-token"]; if (!headerToken || headerToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } next(); }

前端配合:

javascript
// 页面初始化时获取 Token,后续请求放在自定义头中 const csrfToken = await fetch("/api/csrf-token").then(r => r.json()); await fetch("/api/transfer", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken.csrfToken }, body: JSON.stringify({ to: "user123", amount: 100 }) });

为什么用自定义头而非表单字段:跨域请求中浏览器不允许自定义头,攻击者无法通过表单或 iframe 伪造带 X-CSRF-Token 头的请求。这比把 Token 放在请求体中更可靠。

手段三:Origin / Referer 头验证

作为辅助手段,验证请求来源是否合法:

javascript
function originGuard(req, res, next) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const origin = req.headers.origin || req.headers.referer; const allowed = ["https://example.com", "https://app.example.com"]; if (!origin || !allowed.some(o => origin.startsWith(o))) { return res.status(403).json({ error: "请求来源非法" }); } next(); }

注意:Origin 头在某些旧浏览器中可能缺失,Referer 可能被隐私策略截断。只能作为补充,不能作为唯一防线。

手段四:CORS 严格配置

CORS 配置不当会直接暴露 API:

javascript
app.use(cors({ origin: (origin, callback) => { const allowed = ["https://example.com", "https://app.example.com"]; // 移动端请求可能无 origin,需要其他认证方式保障 if (!origin || allowed.includes(origin)) { callback(null, true); } else { callback(new Error("CORS 拒绝")); } }, credentials: true, // 允许携带 Cookie methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "X-CSRF-Token"] }));

关键点credentials: trueorigin 不能用 *,必须明确指定。Access-Control-Allow-Headers 必须包含 X-CSRF-Token,否则浏览器会阻止预检请求通过。

混合客户端场景的分层防护

实际项目中,API 往往同时服务浏览器和移动端,认证方式混合:

javascript
function layeredAuth(req, res, next) { const authHeader = req.headers.authorization; if (authHeader?.startsWith("Bearer ")) { // JWT 认证 — 移动端/第三方,无需 CSRF 防护 try { req.user = jwt.verify(authHeader.slice(7), JWT_SECRET); return next(); } catch { return res.status(401).json({ error: "令牌无效" }); } } // Cookie 认证 — 浏览器端,需要 CSRF 防护 if (req.session?.userId) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const csrfToken = req.headers["x-csrf-token"]; if (!csrfToken || csrfToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } req.user = { id: req.session.userId }; return next(); } res.status(401).json({ error: "需要身份认证" }); }

核心原则:根据认证方式决定是否启用 CSRF 防护,Token 认证跳过,Cookie 认证必检。

面试回答要点

面试中回答这个问题,抓住三个层次:

  1. 先说判断依据:REST API 是否需要 CSRF 防护,取决于认证方式。Token 认证天然免疫,Cookie 认证必须防护。
  2. 再说防护手段:SameSite Cookie 是基线,CSRF Token + 自定义请求头是核心,Origin 验证和 CORS 配置是补充,多层组合最可靠。
  3. 最后说实践:混合客户端场景下,按认证方式分层处理,Token 走 JWT 验证、Cookie 走 CSRF 校验,不要一刀切。
标签:CSRF