Kodokon kodokon.com

在生产环境中保护 API 安全

在生产环境中强化一个 Node API:正确的 CORS、速率限制、安全响应头,以及针对主要 OWASP API 风险的防御。

11 分钟 · 3 题

在 Kodokon 中打开本课

OWASP 维护着一份专门针对 API 的 top 10。三大主要风险是:BOLA(Broken Object Level Authorization,对象级别授权失效 - 通过修改一个 id 来访问另一个用户的对象)、Broken Authentication(认证失效 - 校验不当的令牌、脆弱的密钥)和 Unrestricted Resource Consumption(不受限制的资源消耗 - 对速率、大小或时间没有任何限制)。请注意,这三者中没有一个能靠安装一个库来解决:它们是你设计方案的属性。让我们从贯穿全局的机制开始:CORS、速率限制和响应头。

JAVASCRIPT
import { createServer } from "node:http";

const allowed = new Set(["https://app.example.com"]);

const server = createServer((req, res) => {
  const origin = req.headers.origin;
  if (origin && allowed.has(origin)) {
    res.setHeader(
      "Access-Control-Allow-Origin",
      origin,
    );
    res.setHeader("Vary", "Origin");
  }
  if (req.method === "OPTIONS") {
    res.setHeader(
      "Access-Control-Allow-Methods",
      "GET,POST,PUT,DELETE",
    );
    res.setHeader(
      "Access-Control-Allow-Headers",
      "content-type,authorization",
    );
    res.setHeader("Access-Control-Max-Age", "86400");
    res.writeHead(204).end();
    return;
  }
  res.setHeader("Content-Type", "application/json");
  res.end(JSON.stringify({ ok: true }));
});

server.listen(3000);
手工实现的 CORS:允许列表与预检。

要清楚地理解 CORS 不是什么:它不是对服务器的保护。curl 或第三方后端会完全无视这些响应头;只有浏览器才会强制执行它们,目的是保护它的用户。三条专家规则:从一个允许列表中返回精确的来源(绝不要盲目地回显 Origin 请求头)、加上 Vary: Origin 以免污染中间缓存,以及要知道一旦请求携带了 cookie(credentials),* 这个通配符就会被浏览器拒绝。

JAVASCRIPT
const WINDOW_MS = 60_000;
const LIMIT = 100;
const hits = new Map();

function check(ip) {
  const now = Date.now();
  const entry = hits.get(ip);
  if (!entry || now - entry.start >= WINDOW_MS) {
    hits.set(ip, { start: now, count: 1 });
    return { ok: true, remaining: LIMIT - 1 };
  }
  entry.count += 1;
  const remaining = Math.max(LIMIT - entry.count, 0);
  return { ok: entry.count <= LIMIT, remaining };
}

console.log(check("203.0.113.7"));
内存中的固定窗口:如果 ok 为 false,就返回 429。

安全响应头只需几行代码,就能挡住一整类攻击:HSTS 会强制未来的访问使用 HTTPS,X-Content-Type-Options: nosniff 会阻止浏览器去猜测 MIME 类型,而一个严格的 CSPdefault-src 'none')会让一个不慎在浏览器中被渲染的 API 响应无法被利用。同时也要移除任何暴露你技术栈的响应头:Express 默认会添加 X-Powered-By - 用 app.disable('x-powered-by') 把它禁用。

JAVASCRIPT
function setSecurityHeaders(res) {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=63072000; includeSubDomains",
  );
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader(
    "Content-Security-Policy",
    "default-src 'none'; frame-ancestors 'none'",
  );
  res.setHeader("Cache-Control", "no-store");
}

export { setSecurityHeaders };
在每一个 API 响应上调用。

知识检测

确认你已牢记本课的重点内容。

  1. CORS 机制实际保护的是什么?
    • 保护服务器免受自动化请求(curl、脚本)
    • 保护浏览器用户免受未经授权的跨源读取
    • 保护 API 免受拒绝服务攻击
  2. 为什么 Access-Control-Allow-Origin: * 与发送 cookie 的请求不兼容?
    • 浏览器拒绝这种组合,以避免把已认证的会话暴露给任意站点
    • 服务器会自动返回一个 500 错误
    • 通配符会在服务端禁用 cookie
  3. 一个已认证的 API 把 GET /invoices/42 返回给了一个并不拥有该发票的用户。这属于哪个 OWASP API 风险?
    • 注入(Injection)
    • 对象级别授权失效(BOLA)
    • 安全配置错误(Security Misconfiguration)
    • 服务端请求伪造(Server-Side Request Forgery)