{
  "schemaVersion": "0.11.0",
  "canonical": "https://www.pystone.net/notes/web-cors-explained/",
  "atlas": "https://www.pystone.net/?node=web-cors-explained#knowledge-atlas",
  "markdown": "https://www.pystone.net/notes/web-cors-explained.md",
  "context": "https://www.pystone.net/notes/web-cors-explained.context.json",
  "knowledgeVersion": "224c990773de.5fa8af6e39fa",
  "build": {
    "siteCommit": "224c990773de166d23a886306577dd90379529ce",
    "notesCommit": "5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a",
    "builtAt": "1970-01-01T00:00:00.000Z",
    "version": "224c990773de.5fa8af6e39fa"
  },
  "id": "note:web-cors-explained",
  "slug": "web-cors-explained",
  "title": "Web开发中的CORS",
  "type": "note",
  "visibility": "public",
  "idStability": "rename-stable",
  "author": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "publisher": {
    "name": "Perrin Yong",
    "profile": "https://www.pystone.net/profile/"
  },
  "aliases": [],
  "summary": "Web开发中的CORS 相关概念",
  "contentRole": "unspecified",
  "isMoc": false,
  "mocRecognition": "none",
  "generated": false,
  "attribution": "unspecified",
  "domain": "10-计算机、信息技术与工程",
  "tags": [],
  "mocs": [],
  "contentHash": "fb1a6a72e59e6cee50779390e05cd0a50be48c421b8acd8d20e5eaac560451df",
  "assets": [
    {
      "reference": "assets/image-20250625193435226.png",
      "url": "/media/46e8c57ed21e30421992.png",
      "mediaType": "image/png",
      "contentHash": "46e8c57ed21e304219924c84dc9ae5adadb57237686028d9e57931c3ad474aaa",
      "byteLength": 179798,
      "width": 3068,
      "height": 846
    }
  ],
  "headings": [
    {
      "depth": 1,
      "text": "Web开发中的CORS",
      "anchor": "web开发中的cors",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#web%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84cors"
    },
    {
      "depth": 2,
      "text": "相关概念",
      "anchor": "相关概念",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E7%9B%B8%E5%85%B3%E6%A6%82%E5%BF%B5"
    },
    {
      "depth": 3,
      "text": "Same-Origin Security Policy（SOP）",
      "anchor": "same-origin-security-policysop",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#same-origin-security-policysop"
    },
    {
      "depth": 3,
      "text": "在 SOP 之上如何“合法跨域”——CORS 基本原则",
      "anchor": "在-sop-之上如何合法跨域cors-基本原则",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E5%9C%A8-sop-%E4%B9%8B%E4%B8%8A%E5%A6%82%E4%BD%95%E5%90%88%E6%B3%95%E8%B7%A8%E5%9F%9Fcors-%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%88%99"
    },
    {
      "depth": 3,
      "text": "规则设计的原因",
      "anchor": "规则设计的原因",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E8%A7%84%E5%88%99%E8%AE%BE%E8%AE%A1%E7%9A%84%E5%8E%9F%E5%9B%A0"
    },
    {
      "depth": 4,
      "text": "为什么必须这样设计（本质原因）",
      "anchor": "为什么必须这样设计本质原因",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BF%85%E9%A1%BB%E8%BF%99%E6%A0%B7%E8%AE%BE%E8%AE%A1%E6%9C%AC%E8%B4%A8%E5%8E%9F%E5%9B%A0"
    },
    {
      "depth": 4,
      "text": "如果不设这条规则，会发生什么问题",
      "anchor": "如果不设这条规则会发生什么问题",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E5%A6%82%E6%9E%9C%E4%B8%8D%E8%AE%BE%E8%BF%99%E6%9D%A1%E8%A7%84%E5%88%99%E4%BC%9A%E5%8F%91%E7%94%9F%E4%BB%80%E4%B9%88%E9%97%AE%E9%A2%98"
    },
    {
      "depth": 3,
      "text": "核心响应头速查表",
      "anchor": "核心响应头速查表",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E6%A0%B8%E5%BF%83%E5%93%8D%E5%BA%94%E5%A4%B4%E9%80%9F%E6%9F%A5%E8%A1%A8"
    },
    {
      "depth": 3,
      "text": "典型配置方案",
      "anchor": "典型配置方案",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E5%85%B8%E5%9E%8B%E9%85%8D%E7%BD%AE%E6%96%B9%E6%A1%88"
    },
    {
      "depth": 4,
      "text": "阿里云 OSS Bucket（控制台示例）",
      "anchor": "阿里云-oss-bucket控制台示例",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E9%98%BF%E9%87%8C%E4%BA%91-oss-bucket%E6%8E%A7%E5%88%B6%E5%8F%B0%E7%A4%BA%E4%BE%8B"
    },
    {
      "depth": 4,
      "text": "Nginx 反向代理",
      "anchor": "nginx-反向代理",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#nginx-%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86"
    },
    {
      "depth": 4,
      "text": "Node.js / Express",
      "anchor": "nodejs-express",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#nodejs-express"
    },
    {
      "depth": 2,
      "text": "浏览器与小程序平台的差异",
      "anchor": "浏览器与小程序平台的差异",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8E%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%B9%B3%E5%8F%B0%E7%9A%84%E5%B7%AE%E5%BC%82"
    },
    {
      "depth": 3,
      "text": "小程序平台",
      "anchor": "小程序平台",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%B9%B3%E5%8F%B0"
    },
    {
      "depth": 3,
      "text": "浏览器平台",
      "anchor": "浏览器平台",
      "citation": "https://www.pystone.net/notes/web-cors-explained/#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%B9%B3%E5%8F%B0"
    }
  ],
  "claims": [],
  "outgoing": [],
  "incoming": [
    {
      "id": "note:game-graphics-and-runtime",
      "title": "游戏图形与运行时",
      "url": "https://www.pystone.net/notes/game-graphics-and-runtime/",
      "atlas": "https://www.pystone.net/?node=game-graphics-and-runtime#knowledge-atlas",
      "label": "游戏图形与运行时",
      "origin": "explicit",
      "humanReviewed": true,
      "context": "WebGL中的“Web开发中的CORS”导航项",
      "citation": "https://www.pystone.net/notes/game-graphics-and-runtime/#webgl"
    }
  ],
  "contentMarkdown": "# Web开发中的CORS\n## 相关概念\n\n### Same-Origin Security Policy（SOP）\n**同源策略 (Same-Origin Policy, SOP)**\n\n| 要点         | 说明                                                                                                    |\n| ---------- | ----------------------------------------------------------------------------------------------------- |\n| **定义**     | 浏览器只允许脚本访问与其页面 *协议 scheme*、*主机 host*、*端口 port* 完全一致的资源。默认禁止跨域请求（协议/域名/端口任一不同即跨域）                      |\n| **意义**     | 把每一个 *Origin* 隔离成沙箱，阻止恶意站点窃读或修改另一站点的受信数据（Cookies、表单、银行信息等）。([portswigger.net][2])                     |\n| **若无 SOP** | 任何页面都可在用户登录状态下偷偷：• 读取邮箱 / 网银 API 返回内容 → 隐私泄露• 执行受信操作（转账、删数据） → CSRF / 账户被盗• 探测内网资产 → 越权扫描 |\n\n**作用范围**：DOM、`localStorage`、`IndexedDB`、`Cookies`、AJAX/Fetch 等。\n\n**XMLHttpRequest / Fetch API 的 CORS 行为**\n   * 默认 `mode: \"cors\"` 会触发浏览器的同源检查并自动处理预检。\n   * 若使用 `withCredentials = true` 或 `credentials: \"include\"`，服务器必须返回匹配的 `Access-Control-Allow-Credentials: true`，且 `Access-Control-Allow-Origin` 不能是 `*`。\n### 在 SOP 之上如何“合法跨域”——CORS 基本原则\n**CORS** (Cross-Origin Resource Sharing) is a system, consisting of transmitting [HTTP headers](https://developer.mozilla.org/en-US/docs/Glossary/HTTP_header), that determines whether browsers block frontend JavaScript code from accessing responses for cross-origin requests.\n\n**CORS**只是对 SOP 的“**有条件放行**”机制：若页面需访问位于不同源（协议、主机或端口不完全一致）的资源，则必须由**目标服务端**在 HTTP 响应中明确回送 **CORS（Cross-Origin Resource Sharing）** 相关响应头，授权当前页面的 Origin 进行跨域交互。否则，即使网络链路连通，浏览器也会在收到响应前强制拦截并抛出 “No ‘Access-Control-Allow-Origin’ header” 错误。\n\n| 步骤                        | 目的                                                                     |\n| ------------------------- | ---------------------------------------------------------------------- |\n| **① 浏览器附带 `Origin:` 请求头** | 告诉目标服务器：“谁在跨域访问你”。                                                     |\n| **② 目标服务器返回 CORS 响应头**    | 显式声明哪些外域可访问、可用哪些方法、是否允许携带凭证。                                           |\n| **③ 浏览器校验**               | 只有当 `Access-Control-Allow-Origin` 等字段与请求匹配时，才把响应交给前端脚本；否则网络层收到但被浏览器丢弃。 |\n\n> 结论：**配置点始终在目标域（后端 / OSS / CDN），而不是在发起请求的前端。**\n\n---\n\n### 规则设计的原因\n#### 为什么必须这样设计（本质原因）\n\n1. **用户凭据是“环境权柄”（ambient authority）**\n    浏览器会在同源或被允许的情况下自动带上 Cookie、HTTP Auth、客户端证书、HSTS 等。若不限制跨源**读取**：\n\n- 任何第三方页面都能在你登录银行、邮箱、云存储后台时，用你的凭据去调这些站点的接口，并把**响应内容**读回本页、再外传走（隐私和账户直接失守）。\n\n2. **保护内网与本机服务**\n    很多设备/面板只在内网开放（路由器 `192.168.x.x`、各类管理面板、`127.0.0.1` 上的开发服务、云实例的元数据接口 `169.254.169.254`）。\n    若不限制，恶意网站可以从你的浏览器直接**探测端口、读取配置、窃取密钥**。\n\n3. **最小必要暴露、由资源所有者决定**\n    让“是否共享数据”的决定权在**资源拥有者**（目标服务器）手里，而不是由发起方网页单方面决定。目标服务能精确控制：允许哪些 **Origin**、哪些 **方法/头**、是否允许**携带凭据**。\n\n4. **降低历史兼容造成的攻击面**\n    早期网页允许 `<img>/<script>` 跨源加载但**不暴露响应体**（只能执行脚本/显示图片），这是为了功能而做的折中。SOP + CORS 在此基础上补足“读响应体”时的授权步骤，尽量不破坏旧能力。\n\n#### 如果不设这条规则，会发生什么问题\n\n- **跨站数据窃取（最严重）**\n    任意站点都能直接 `fetch('https://mail.example.com/api/messages')` 并**读取**邮件列表（因为浏览器自动带上你的登录 Cookie）。这比传统 CSRF 更致命：CSRF通常**发得出去**但**读不回来**，而取消 SOP/CORS 限制后就能**读回来**了。\n\n- **账户接管与敏感信息外泄**\n    响应里常包含 CSRF token、JWT、临时密钥、个人信息，攻击者读到后即可进一步横向移动或完全接管账户。\n\n- **内网/本机探测与利用**\n    网页可枚举内网地址与端口，读取返回内容，推断服务版本并利用已知漏洞；还能读取云主机元数据服务拿到凭据。\n\n- **任意跨源请求伪造 + 可见回显**\n    不仅能改数据（转账/删除/变更配置），还能看到操作结果，显著提升攻击自动化和稳定性。\n\n- **大规模指纹识别与跟踪**\n    跨站可读将允许网页拼接多个站点的细粒度响应差异做浏览器/账号指纹，严重放大隐私风险。\n\n\n### 核心响应头速查表\n\n| Header                             | 作用               | 示例值                                |\n| ---------------------------------- | ---------------- | ---------------------------------- |\n| `Access-Control-Allow-Origin`      | 允许的 Origin       | `https://yourgame.com` 或 `*`       |\n| `Access-Control-Allow-Methods`     | 允许的 HTTP 方法      | `GET,POST,PUT,OPTIONS`             |\n| `Access-Control-Allow-Headers`     | 允许的自定义请求头        | `Content-Type,Authorization` 或 `*` |\n| `Access-Control-Allow-Credentials` | 是否允许 Cookie / 令牌 | `true`（配合具体 Origin，不能是 `*`）        |\n| `Access-Control-Max-Age`           | 预检结果缓存秒数         | `3600`                             |\n| `Access-Control-Expose-Headers`    | 前端可读的额外响应头       | `ETag,x-oss-request-id`            |\n\n---\n\n### 典型配置方案\n\n#### 阿里云 OSS Bucket（控制台示例）\n\n在使用 **阿里云 OSS** 直传文件的场景中，OSS 服务即为“目标域”，需启用 Bucket-级别的 CORS 规则。推荐采用**最小可用授权**原则，仅开放实际需要的域名、方法与头部。\n\n|配置项|建议设置|说明|\n|---|---|---|\n|**AllowedOrigin**|`https://<业务域名>``http://localhost:8080`（调试）|必填；多域名可多行列出，必须带协议与端口|\n|**AllowedMethod**|`POST` `PUT` `OPTIONS`|若仅上传，可按需保留 `POST/PUT`；`OPTIONS` 为浏览器预检必选|\n|**AllowedHeader**|`*` 或列出 `Content-Type,Authorization`|建议开发期先放宽为 `*`，上线前收紧|\n|**ExposeHeader**|`ETag,x-oss-request-id`|供前端读取响应头|\n|**MaxAgeSeconds**|`3600`|预检结果在浏览器侧缓存时长|\n|**凭证请求**|**需开启** `Access-Control-Allow-Credentials:true`（由 OSS 自动返回）并确保 `AllowedOrigin` 不为 `*`|仅当前端使用 `withCredentials:true / credentials:'include'` 时适用|\n\n**控制台快速配置步骤**\n1. 登录 OSS 控制台 → 选择 Bucket。\n2. 进入 **数据安全 › 跨域设置**，点击 **创建规则**。\n3. 按上表填写，确认保存后即时生效。\n\n**CLI / 自动化示例（ossutil）**\n\n```html\n<CORSConfiguration>\n  <CORSRule>\n    <AllowedOrigin>http://localhost:8080</AllowedOrigin>\n    <AllowedOrigin>https://yourgame.com</AllowedOrigin>\n    <AllowedMethod>POST</AllowedMethod>\n    <AllowedMethod>PUT</AllowedMethod>\n    <AllowedMethod>OPTIONS</AllowedMethod>\n    <AllowedHeader>*</AllowedHeader>\n    <ExposeHeader>ETag</ExposeHeader>\n    <ExposeHeader>x-oss-request-id</ExposeHeader>\n    <MaxAgeSeconds>3600</MaxAgeSeconds>\n  </CORSRule>\n</CORSConfiguration>\n```\n\n推送命令：`ossutil cors --method put oss://your-bucket cors.xml`。([help.aliyun.com][3])\n\n> **带凭证上传**：浏览器需 `withCredentials=true`，并确保 `AllowedOrigin` 为具体域名，OSS 会自动加 `Access-Control-Allow-Credentials:true`。\n\n---\n\n#### Nginx 反向代理\n\n```nginx\nlocation /api/ {\n    add_header Access-Control-Allow-Origin https://yourgame.com;\n    add_header Access-Control-Allow-Methods \"GET, POST, PUT, OPTIONS\";\n    add_header Access-Control-Allow-Headers \"*\";\n    add_header Access-Control-Allow-Credentials true;\n    if ($request_method = OPTIONS) { return 204; }\n}\n```\n\n#### Node.js / Express\n\n```js\nconst cors = require('cors');\napp.use(cors({\n  origin: ['https://yourgame.com', 'http://localhost:8080'],\n  methods: ['GET', 'POST', 'PUT'],\n  allowedHeaders: ['Content-Type', 'Authorization'],\n  credentials: true,\n  maxAge: 3600\n}));\n```\n\n\n注意:\n* 如果服务器随意返回 `Access-Control-Allow-Origin: *` 并允许凭证，会暴露会话给任何站点，存在 CSRF/数据泄露风险。\n* 因此服务器端 CORS 配置应尽量**最小化许可范围**。\n\n## 浏览器与小程序平台的差异\n\n| 特性               | 微信小程序运行时                                                                     | 浏览器 (WebGL/H5)                             |\n| ---------------- | ---------------------------------------------------------------------------- | ------------------------------------------ |\n| 网络 API           | `wx.request()` ➜ **原生 TCP/HTTPS**，内部实现近似 iOS/Android 的 `NSURLSession/OkHttp` | `XMLHttpRequest` / `fetch` ➜ **浏览器网络线程**   |\n| 是否发送 `Origin:` 头 | 不发送（所以服务器根本不知道这是谁跨来的域）                                                       | 必须发送；浏览器用它比对 `Access-Control-Allow-Origin` |\n| 是否受同源策略          | **不受** ——「跨域」概念对它不存在                                                         | **受** —— 默认禁止跨源，除非服务器通过 CORS 放行            |\n| 客户端白名单机制         | 「request 合法域名」写在微信后台，微信*本地*先检查                                               | 无                                          |\n| 服务器需否配置 CORS     | **不用**                                                                       | **必须**（否则浏览器在收到响应前就拦截）                     |\n\n![](assets/image-20250625193435226.png)\n\n\n### 小程序平台\n微信小游戏运行在微信的环境中，其网络请求是通过微信的客户端（即微信App）发起的，而不是直接由浏览器发起。微信小游戏使用`wx.request` API发送请求，这个API有以下特点：\n- **非浏览器环境**：微信小游戏的JavaScript运行环境是微信自己提供的，不是浏览器，因此不会受到浏览器的同源策略（Same-Origin Policy）限制。\n- **域名白名单（客户端拦截机制）**：微信小游戏要求开发者在小程序或小游戏的后台配置请求的合法域名（即request合法域名）。微信客户端在发起请求时会检查目标域名是否在合法域名列表中，如果不在，则请求会被微信客户端阻止。这个机制是微信自己实现的，与浏览器的CORS机制无关。\n- **不涉及CORS**：由于请求是由微信客户端（原生应用）发起的，而不是浏览器，所以不会触发浏览器的CORS检查。因此，即使阿里云OSS没有配置CORS规则，只要域名在微信的合法域名列表中，请求就能成功。\n\n\n### 浏览器平台\n**遵循W3C规范**：浏览器严格执行 [CORS标准](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)\n在浏览器环境中，当你的Web应用通过`XMLHttpRequest`或`fetch`向不同的域（如阿里云OSS的域名）发起请求时，浏览器会强制执行同源策略，并检查CORS响应头。\n\n- **CORS机制**：浏览器会首先发送一个预检请求（OPTIONS请求）到目标服务器（阿里云OSS），询问是否允许来自`http://localhost:8080`的请求。服务器必须返回`Access-Control-Allow-Origin`响应头，并且该头部的值需要包含当前域（或`*`），浏览器才会允许实际的请求。\n\n- **OSS配置**：阿里云OSS作为资源服务器，必须配置CORS规则以响应预检请求，即返回适当的`Access-Control-Allow-Origin`等头部。如果没有配置，浏览器就会报错，如你遇到的错误。\n"
}
