浏览器安全模型:同源、跨源与响应头

Helmet 和 CORS 都在影响浏览器的行为。要理解它们,第一步不是记 API,而是理解浏览器为何不完全信任页面中的 JavaScript。

1. 什么是 Origin

一个 Origin(源)由三部分组成:

协议 + 主机名 + 端口

例如,以 https://www.example.com:443 为基准:

地址 是否同源 原因
https://www.example.com/users 三部分相同
http://www.example.com/users 协议不同
https://api.example.com/users 主机名不同
https://www.example.com:8443/users 端口不同

路径不参与 Origin 判断。/users/orders 可以同源;子域名却不是天然同源。

2. 同源策略限制的是什么

同源策略的主要目标是防止一个恶意网站随意读取用户在另一个网站中的敏感数据。假设用户已经登录网上银行,恶意页面不能仅凭浏览器里现存的 Cookie 就随意读取余额接口的返回值。

需要区分三个动作:

  1. 浏览器是否发出请求;
  2. 服务器是否处理请求;
  3. 浏览器是否把响应交给页面 JavaScript。

跨源请求经常已经到达服务器并返回 200,只是浏览器不允许 JavaScript 读取。因此:

服务器返回 200 ≠ 前端一定能读取响应
CORS 报错 ≠ 请求一定没到服务器

这也是为什么 Postman 或 curl 正常,而浏览器失败:前两者不执行浏览器同源策略。

3. 不同资源受到的限制并不相同

浏览器历史上允许页面跨源加载某些资源,例如 <img><script><link> 和导航链接,但“能加载”不等于“JavaScript 能读取内容”。现代浏览器又叠加了 CSP、CORP、COEP 等规则,所以还要看响应头和页面策略。

常见情况:

行为 典型控制机制
fetch() 读取跨源 JSON CORS、页面 CSP connect-src
加载跨源图片 CSP img-src、CORP;画布读取还涉及 CORS
加载脚本 CSP script-src、MIME 类型、CORS(模块脚本)
页面被 iframe 嵌入 CSP frame-ancestorsX-Frame-Options
新窗口是否能访问 window.opener COOP

4. 简单请求和预检请求

某些跨源请求可以直接发送,例如满足条件的 GET,浏览器在响应阶段检查 Access-Control-Allow-Origin。更复杂或潜在副作用更大的请求,浏览器通常先发送 OPTIONS 预检。

例如前端发送:

await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: "Bearer token-value",
  },
  body: JSON.stringify({ name: "Alice" }),
});

application/jsonAuthorization 会使这个跨源请求通常需要预检。浏览器先询问后端是否允许该 Origin、方法和 Header,许可后才发送 POST。

注意:浏览器的 CORS 预检不是业务登录。预检通常不携带业务请求所依赖的凭据,因此不要让 JWT 校验先把所有 OPTIONS 都拒绝掉。

5. 响应头为何能够改变页面行为

HTTP 响应头不只是给开发者看的元数据。浏览器会把部分响应头当作强制执行的安全策略:

所以“后端只返回 JSON”也不代表响应头无意义。不过,某个响应头是否控制整个页面,要看它出现在哪一个响应上。例如 CSP 出现在 JSON API 响应上,一般不会反过来控制另一个由 Nginx 返回的 index.html

6. Same-origin、same-site 与跨站

Origin 和 Site 不是同一概念。www.example.comapi.example.com 是不同 Origin,但通常属于同一 Site。Cookie 的 SameSite、CORP 的 same-site 与 CORS 的 Origin 判断不能混用。

对初学者最实用的做法是:

7. 如何观察

查看真实响应:

curl -i https://api.example.com/users

模拟浏览器携带 Origin:

curl -i https://api.example.com/users \
  -H "Origin: https://www.example.com"

模拟预检:

curl -i -X OPTIONS https://api.example.com/users \
  -H "Origin: https://www.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: authorization,content-type"

curl 只展示服务器返回了什么,不会替浏览器判定整个页面的 CSP 或 CORS 是否成立。最终仍应在真实浏览器中测试。