cross-origin-cors-reference/same-origin-simulator

GitHub: cross-origin-cors-reference/same-origin-simulator

一个在本地启动真实跨域 HTTP 请求的交互式演练场,通过并排对比服务器视图与浏览器视图来教学同源策略和 CORS 机制。

Stars: 0 | Forks: 0

# same-origin-simulator 一个用于演示同源策略的交互式演练场。它会在几个本地回环 origin 上启动真实的 HTTP 服务器,从你自己的浏览器在它们之间发起**真正的**跨域请求,并向你并排展示通常无法同时看到的两个内容: - **实际通过网络传输的内容**,由接收服务器报告 —— 包括 Fetch API 完全向 JavaScript 隐藏的 `OPTIONS` preflight;以及 - **`fetch()` 被允许交给你代码的内容** —— 通常要少得多,在失败时则什么都没有。 这种对比正是核心所在。几乎每一个因 CORS 而浪费掉的小时,都是因为人们假设这两个视图是同一个视图。 ``` same-origin-simulator 1.0.0 front-end http://localhost:8080 same server http://127.0.0.1:8080 <- a different origin, on purpose API http://localhost:8081 bound: app, api, app-v6, api-v6 open http://localhost:8080 and pick a scenario press Ctrl-C to stop ``` ## 目录 - [为什么会有这个工具](#why-this-exists) - [环境要求](#requirements) - [运行方式](#running-it) - [用法与参数](#usage-and-flags) - [CORS 的实际工作原理](#how-cors-actually-works) - [场景说明](#the-scenarios) - [示例解析](#worked-example) - [架构:为什么需要三个 origin](#architecture-why-three-origins) - [自由操作与控制 API](#free-play-and-the-control-api) - [退出代码](#exit-codes) - [安全说明](#security-note) - [局限性以及它特意不包含的内容](#limitations-and-what-this-deliberately-is-not) - [测试](#tests) - [延伸阅读](#further-reading) - [贡献指南](#contributing) - [许可证](#licence) ## 为什么会有这个工具 CORS 的教学通常很糟糕,主要原因在于:证据分散在两个地方,而没人同时查看这两处。 浏览器告诉你 `Access to fetch has been blocked by CORS policy`(访问被 CORS 策略阻止)。你的服务器日志却显示 `200 OK`。两者都是对的。请求确实发送了,处理器运行了,副作用产生了,响应也返回了 —— 然后浏览器却拒绝将其交给你的脚本。 这种失败是刻意不透明的:`fetch()` 会以一个不带任何状态、头部或主体的简单 `TypeError` 拒绝,因为泄露跨域响应的状态本身就是一种信息泄露。 当涉及 preflight 时情况会更糟,因为失败的那个请求你的代码根本没有编写。你无法记录它,拦截它,或在 promise 中看到它。它只存在于网络面板和服务器自己的日志中。 这个工具将这两部分放在了同一个屏幕上。API 服务器会记录它接收到的每一个请求 —— 包括 preflight —— 页面会将该日志展示在你代码接收到的 `Response` 对象旁边。阅读一个场景大约需要一分钟;两三次之后,这些机制通常就不再神秘了。 它是一种教学工具,而非诊断工具。所有操作都在本地回环上进行,不会向任何地方发送数据,而且服务器特意提供了你在生产环境中绝不会部署的错误配置方式。 ## 环境要求 - Python 3.9 或更高版本。 - 一个浏览器。 仅此而已。无需第三方包,无需构建步骤,无需网络访问。Python 端仅使用标准库,前端是作为纯文件提供的原生 HTML、CSS 和 JavaScript。 ## 运行方式 克隆代码仓库并直接运行启动器: ``` git clone same-origin-simulator cd same-origin-simulator python3 simulate ``` 你的浏览器会打开 `http://localhost:8080`。从左侧选择一个场景,按下 **Run this scenario**,然后阅读那五个面板。 如果你更愿意输入 `simulate` 而不是 `python3 simulate`,可以将其标记为可执行文件并放到你的 `PATH` 中: ``` chmod +x simulate ln -s "$PWD/simulate" ~/.local/bin/simulate ``` 想要停止它,请按 Ctrl-C。 **在使用时打开浏览器的开发者工具网络面板。** 模拟器向你展示的是服务器的视图;网络面板向你展示的是浏览器的视图。在两者中看到同一个 preflight 才能让你真正理解它。 ## 用法与参数 ``` usage: simulate [-h] [--port N] [--api-port N] [--host HOST] [--no-browser] [--log-level {quiet,info,debug}] [--version] ``` | 参数 | 默认值 | 作用 | | --- | --- | --- | | `--port N` | `8080` | 前端 origin 的端口。该端口的 `127.0.0.1` 变体将成为第二个 origin。 | | `--api-port N` | `8081` | 可配置 API origin 的端口。必须与 `--port` 不同;这两个 origin 之所以不同,仅仅是因为它们的端口不同。 | | `--host HOST` | `127.0.0.1` | 要绑定的回环接口。仅接受 `localhost`、`127.0.0.1` 和 `::1` —— 参见[安全说明](#security-note)。 | | `--no-browser` | off | 启动时不打开浏览器窗口。在通过 SSH 或在容器中使用时很有用。 | | `--log-level` | `info` | `quiet` 除了错误外不打印任何内容。`info` 打印启动横幅。`debug` 还会将两个服务器处理的每个 HTTP 请求记录到 stderr。 | | `--version` | | 打印版本并退出。 | | `-h`, `--help` | | 完整帮助,包括 origin 映射和退出代码。 | 如果端口被占用,你会收到一条具体的提示和建议的替代方案,而不是一个 traceback: ``` $ python3 simulate --port 8080 simulate: error: port 8080 is already in use ([Errno 98] Address already in use). Try a different front-end port, for example: python3 simulate --port 8090 --api-port 8091 ``` ## CORS 的实际工作原理 这个模型分为四个部分。工具中的每个场景都是其中之一的实例。 **1. origin 是 `(scheme, host, port)`,按原样进行比较。** 不是解析后的结果。`http://localhost:8080` 和 `http://127.0.0.1:8080` 是不同的 origin,尽管它们是同一台机器上的同一个套接字,因为 origin 比较发生在 host *字符串*上,从不查阅 DNS。同一 host 上的 `http` 和 `https` 是不同的。端口 `8080` 和端口 `8081` 是不同的。没有部分匹配,没有子域名规则,也没有“同一台机器”的概念。 **2. 策略管的是读取,而不是发送。** 这是让人意想不到的部分。浏览器会发送你的跨域请求。服务器接收到它并运行其处理器。响应返回。*然后* 浏览器检查该响应是否授予了你的 origin 读取权限,如果没有,它会在你的代码看到它之前将其完全丢弃。一个被阻止的 `POST` 依然会创建记录。 **3. 某些请求会先触发 preflight。** 只有当以下所有条件都满足时,请求才是 *simple*(简单请求) —— 直接发送,无 preflight: - 方法是 `GET`、`HEAD` 或 `POST`; - 每个由开发者设置的 header 都在 CORS 安全列表中(`Accept`、`Accept-Language`、`Content-Language`、`Content-Type`、`Range`,加上客户端提示); - 如果设置了 `Content-Type`,其值是 `application/x-www-form-urlencoded`、`multipart/form-data` 或 `text/plain` 之一; - body 不是 `ReadableStream`,并且 `XMLHttpRequest` upload 对象上没有监听器。 违反其中任何一条,浏览器都会首先发送一个携带 `Access-Control-Request-Method` 以及(如果相关)`Access-Control-Request-Headers` 的 `OPTIONS` 请求。服务器必须以 **2xx** 状态码和匹配的 `Access-Control-Allow-Methods` / `Access-Control-Allow-Headers` 进行响应。无论 header 多么正确,3xx 或 4xx 都会导致 preflight 失败,并且 preflight 期间永远不会遵循重定向。 这个界限并非随意设定的。HTML 表单已经可以在没有任何 JavaScript 的情况下跨域 `POST` 这三种内容类型,因此对它们进行 preflight 不会保护任何尚未暴露的内容。`application/json` 无法由普通表单生成,因此需要额外的往返。 preflight 按 origin、URL、方法和 header 集进行缓存,持续 `Access-Control-Max-Age` 秒。浏览器会对该值进行限制(截断)而不是拒绝它:Chromium 为 7200 秒,Firefox 为 86400 秒,Safari 为 600 秒。 **preflight 请求从不携带凭据**,即使它们授权的后续请求会携带。如果你的身份验证中间件在 CORS 处理之前运行,它将看到一个匿名的 `OPTIONS` 并拒绝它 —— 这是生产环境中最常见的 preflight 错误。 **4. 凭据提高了门槛,且响应会被过滤。** 当请求携带 cookie 或 TLS 客户端证书时: - `Access-Control-Allow-Origin: *` 会被拒绝。服务器必须指明一个具体的 origin。通配符加上现有的 cookie 将意味着“用户访问的任何站点都可以读取该用户的已验证数据”。 - `Access-Control-Allow-Headers: *` 和 `Access-Control-Allow-Methods: *` 会按字面意思被读取,作为一个名为 `*` 的 header 或方法。 - 此外还需要 `Access-Control-Allow-Credentials: true`。 而且无论是否涉及凭据,你的脚本只能读取 CORS 安全列表中的*响应* header —— `Cache-Control`、`Content-Language`、`Content-Length`、`Content-Type`、`Expires`、`Last-Modified`、`Pragma` —— 除非服务器在 `Access-Control-Expose-Headers` 中指明了其他的。在你的代码接触到 `Headers` 对象之前,所有其他的 header 都会被静默剔除,返回 `null` 而不是报错。 还有一个会在生产环境中坑人的规则:每当 `Access-Control-Allow-Origin` 是根据请求的 `Origin` 计算得出时,响应必须同时携带 `Vary: Origin`,否则共享缓存最终可能会将一个 origin 的 `ACAO` 值交给另一个不同的 origin。 ## 场景说明 十五个一键课程。每个课程都会重新配置 API origin,发起真实请求,并解释发生了什么以及为什么。 ### 基础内容 | 场景 | 教学内容 | | --- | --- | | **Simple GET, allowed** | 最基础的跨域读取成功示例:一个请求,无 preflight,`ACAO` 回显 origin,以及必须伴随的 `Vary: Origin`。 | | **Simple GET, blocked** | 没有 `ACAO` 的相同请求。服务器响应 `200` 和完整的主体,而 JavaScript 依然只得到一个 `TypeError`。最清晰地证明了该策略阻止的是读取,而不是发送。 | ### 触发 preflight 的条件 | 场景 | 教学内容 | | --- | --- | | **Custom request header** | `X-Request-Id` 不在安全列表中,因此一个普通的 `GET` 不再是简单请求。JavaScript 编写了一个请求,网络上却出现了两个。 | | **`Content-Type: application/json`** | header 在安全列表中;但*值*不在。为什么说“POST 是一个简单方法”是个陷阱。 | | **`DELETE`** | `GET`/`HEAD`/`POST` 之外的方法无条件触发 preflight,即使不涉及任何 header。 | ### 凭据 | 场景 | 教学内容 | | --- | --- | | **Credentialed request meets a wildcard** | `ACAO: *` 和 `ACAC: true` 各自单独看都是有效的,但放在一起会被拒绝,正是因为涉及了凭据。解释了该规则旨在防止的带有读取权限的 CSRF 原理。 | | **Credentialed request done correctly** | 全都缺一不可的三个部分:一个具体的允许列表 origin、`ACAC: true`,以及 `Vary: Origin`。 | ### preflight 失败的情况 | 场景 | 教学内容 | | --- | --- | | **Preflight returns 403** | 完美的 CORS header,非 2xx 状态码,彻底失败。这是身份验证中间件排在 CORS 处理之前导致的典型 bug —— 在 curl 中能用,在 Postman 中能用,只在浏览器中失败。 | | **Preflight is redirected (301)** | preflight 期间永远不会遵循重定向。在实际环境中,这通常是由 HTTPS 升级、尾部斜杠规范化和负载均衡器规范化引起的 —— 这对服务器端测试来说都是不可见的。 | ### preflight 缓存 | 场景 | 教学内容 | | --- | --- | | **Max-Age caching** | 连续发起两次相同的请求,你会看到第二次跳过了 preflight:两次 `fetch()` 调用产生了三条网络记录。这是可以直接观察到的,而不仅仅是断言,也是该系列中最精彩的部分。涵盖了不同浏览器的限制规则。 | ### 读取响应 | 场景 | 教学内容 | | --- | --- | | **Reading a header without Expose-Headers** | `X-Total-Count: 137` 确确实实显示在原始 header 面板中,但 `response.headers.get()` 返回 `null`。没有错误,没有警告。为什么分页计数和速率限制 header 会不见。 | ### 边缘情况 | 场景 | 教学内容 | | --- | --- | | **`null` origin from a sandboxed iframe** | 一个处于不透明 origin 中的真正沙盒化的 iframe 发送 `Origin: null`,而 API 被设置为信任它。演示了包含 `null` 的允许列表实际上是在向所有人授予访问权限,而在代码审查时看起来却很局限。 | | **Opaque response from `mode: 'noors'`** | `fetch()` resolve 了,但 `type: 'opaque'`,`status: 0` 且没有任何可读内容。为什么“只需加上 `no-cors`”是这方面最糟糕的建议。同时也展示了 `no-cors` 的 `GET` 请求根本不会发送 `Origin` header。 | | **localhost vs 127.0.0.1** | 同一个服务器,同一个端口,同样的字节 —— 但请求却被阻止了,因为 host 字符串不同。 | | **Duplicate `Access-Control-Allow-Origin`** | 应用程序设置了它,代理也设置了它,两者都没错,但这一对合并成了 `origin, origin`,导致无法匹配任何内容。原始 header 面板显示了这两行字段。 | ## 示例解析 选择 **Max-Age caching** 并按下 Run。五个面板将会被填充。 **面板 1 —— 运行的 JavaScript 代码。** 真实、可复制,且是实际执行的代码路径: ``` // Executed in a document served from http://localhost:8080 try { for (let i = 0; i < 2; i++) { const response = await fetch("http://localhost:8081/api/widgets", { method: "GET", mode: "cors", credentials: "omit", headers: { "X-Request-Id": "cached" } }); console.log(response.status, await response.text()); } } catch (error) { // A CORS failure lands here as a bare TypeError: // no status, no headers, no body. console.error(error.name, error.message); } ``` **面板 2 —— 实际通过网络传输的内容。** 由 API 服务器报告,而不是由页面报告: ``` # Kind Method Path 发送的 Origin Preflight 请求 Status 1 PREFLIGHT OPTIONS /api/widgets http://localhost:8080 method GET; headers x-request-id 204 2 ACTUAL GET /api/widgets http://localhost:8080 not applicable 200 3 ACTUAL GET /api/widgets http://localhost:8080 not applicable 200 3 request(s) reached the API origin: 1 preflight, 2 actual. JavaScript called fetch() 2 time(s). Only the first fetch paid for a preflight — the rest were served from the browser's preflight cache, which is what Access-Control-Max-Age buys you. ``` **面板 3 —— 原始响应 header**,完全按照发出的原样: ``` Access-Control-Allow-Origin: http://localhost:8080 Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: X-Request-Id Access-Control-Max-Age: 600 Vary: Origin Cache-Control: no-store ``` **面板 4 —— JavaScript 接收到的内容。** `response.type` 为 `cors`,`status` 为 `200`,`ok` 为 `true`,以及浏览器愿意暴露的 header 列表。 **面板 5 —— 结论**,加上你的浏览器应该显示的控制台文本,以及指向相关背景阅读的链接。 现在运行 **Simple GET, blocked** 并对比面板 2 和面板 4:网络上是一个健康的 `200 OK`,而在你的代码中却是一个简单的 `TypeError: Failed to fetch`。 ## 架构:为什么需要三个 origin ``` +---------------------------------------+ browser | simulate (one Python process) | +-------------------------+ | | | http://localhost:8080 |<---->| app server 127.0.0.1:8080 + [::1] | | index.html, app.js | | www/ + /__origins.json | | | | + /__scenarios.json | | +-------------------+ | | | | | iframe | | | | | | http://127.0.0.1 |--+----->| (same server, different origin) | | +-------------------+ | | | | +-------------------+ | | | | | sandboxed iframe | | | | | | opaque origin |--+----->| api server 127.0.0.1:8081 + [::1] | | +-------------------+ | | /api/* CORS per config | | | | /__control/* config + wire log | +-------------------------+ +---------------------------------------+ ``` **为什么需要单独的 API 端口。** 只有当 scheme、host 或 port 不同时,两个 origin 才会不同。在第二个端口上提供 API 是在一台机器上创建真正的跨域关系的最简单方法,无需修改 hosts 文件,无需 TLS,也无需 DNS。 **为什么需要 `127.0.0.1` origin。** 同一个服务器,通过它的另一个名称访问,就是一个免费的第二个 origin。这使得 localhost 与 127.0.0.1 的课程变得可演示,而不仅仅是口头说说 —— 你可以看到针对同一个允许列表,一个成功而另一个失败。来自它的请求是在指向该 origin 的隐藏 iframe 中发起的,因此页面无需重新加载。 **为什么需要沙盒化的 iframe。** 带有 `sandbox="allow-scripts"` 但没有 `allow-same-origin` 会将文档置于一个不透明的 origin 中,而来自不透明 origin 的请求会携带 `Origin: null`。这是真正的浏览器行为,而不是模拟:这正是攻击者用三行 HTML 产生 `null` origin 的方式。 **为什么需要同时支持 IPv4 和 IPv6 回环。** 在许多系统上,`localhost` 会在 `127.0.0.1` 之前解析为 `::1`。如果仅绑定 `127.0.0.1`,将导致前端在一个名称下可访问,而在另一个名称下不可访问,这将破坏该工具试图教授的课程。因此,每个服务器都绑定两者,提供四个侦听套接字且每个套接字各占用一个线程。如果 `::1` 绑定失败,启动器会说明情况并继续使用 IPv4。 **为什么控制通道始终对 CORS 开放。** `/__control/*` 无条件回显任何 origin 并忽略场景配置。如果它遵循配置,选择一个“被阻止”的场景就会将你拒之门外,导致你无法选择下一个场景的控制权限。它也被排除在网络日志之外,因此日志只显示课程相关的内容。 ## 自由操作与控制 API 在场景列表下方是一个手动驱动 API origin 的表单:`ACAO` 模式(缺失、通配符、回显、静态允许列表、反射任何内容、信任-null)、`ACAC`、`Allow-Methods`、`Allow-Headers`、`Expose-Headers`、`Max-Age`、preflight 状态码、人工延迟、重复的 `ACAO`,以及 `Vary: Origin` —— 还有请求本身:发起的 origin、方法、路径、额外 header、`fetch` 模式、凭据模式和重复次数。 同一个控制通道是一个普通的 HTTP API,因此你可以在浏览器监视的同时使用 curl 驱动它: | Endpoint | 方法 | 用途 | | --- | --- | --- | | `/__control/config` | `GET` | 读取实时配置。 | | `/__control/config` | `POST` | 替换它。未知的键和超出范围的值将被拒绝并返回 `400` 和一条消息。这是完全替换,而不是合并。 | | `/__control/log?since=N` | `GET` | 序列 `N` 之后接收到的每个 `/api/` 请求,以及发出的确切响应 header 行。 | | `/__control/reset` | `POST` | 清除日志。 | ``` $ curl -s -X POST http://localhost:8081/__control/config \ -H 'Content-Type: application/json' \ -d '{"acao_mode":"echo","allow_methods":"PUT, OPTIONS","max_age":600}' > /dev/null $ curl -s -D - -o /dev/null -X OPTIONS http://localhost:8081/api/widgets \ -H 'Origin: http://localhost:8080' \ -H 'Access-Control-Request-Method: PUT' HTTP/1.1 204 No Content Access-Control-Allow-Origin: http://localhost:8080 Access-Control-Allow-Methods: PUT, OPTIONS Access-Control-Max-Age: 600 Vary: Origin Cache-Control: no-store ``` 像这样在 curl 中重现一个 preflight,是确定 CORS 问题出在服务器还是浏览器的标准方法。如果 curl 显示了正确的 header 而浏览器仍然阻止请求,那么差异就出在浏览器添加的某些东西上 —— 凭据、重定向,或者来自代理的重复 header。 配置键:`acao_mode`、`allowlist`、`acac`、`allow_methods`、`allow_headers`、`expose_headers`、`max_age`、`preflight_status`、`redirect_location`、`delay_ms`、`duplicate_acao`、`vary_origin`。 API 接口本身故意设计得很简单:`/api/` 下的任何路径都会响应 `200` 以及一个描述它看到了什么的简短 JSON body,外加一个故意不在安全列表中的 `X-Total-Count: 137` header,用于 Expose-Headers 课程。 ## 退出代码 | 代码 | 含义 | | --- | --- | | `0` | 正常关闭。 | | `1` | 意外错误,例如缺少 `www/` 目录。 | | `2` | 错误的用法:未知的参数、超出范围的端口、相同的 `--port` 和 `--api-port`,或者非回环的 `--host`。 | | `3` | 所需的端口已被占用。消息会指明该端口并建议一个空闲的替代端口。 | ## 安全说明 **这些服务器仅绑定到回环接口,启动器拒绝以其他方式运行。** 向 `--host` 传递 `localhost`、`127.0.0.1` 或 `::1` 以外的任何内容都会以代码 `2` 退出,而不是进行绑定。 这种限制并非偶然。这个工具的工作是提供*故意配置错误*的 CORS 配置:反射任何 origin、信任 `null`、带有凭据的通配符。这些正是 CORS 安全审计旨在查找的确切错误配置。如果在可路由的接口上暴露,它们将是真正的漏洞而不是课程,因此该工具不为你提供该选项。 相关要点: - **它不是代理。** 它从不向远程主机转发任何内容,不发起外部连接,也没有上游允许列表,因为它没有上游。它不能用于绕过真实服务器的 CORS 策略,如果你来这里是为了寻找这个,答案是这个修复应该在服务器上进行。 - **没有任何数据离开你的机器。** 没有遥测,没有 CDN,没有字体,没有分析。前端由磁盘提供,涉及的唯一主机是回环地址。 - **不要将其指向任何你不拥有的东西。** 虽然没有相关的功能,但为了避免疑虑:只探测你被授权测试的主机。 - **这些场景配置是反面教材。** 包含 `reflect`、`null_trusted` 和带有凭据的通配符是为了让你安全地看到它们失败,而不是作为模板。 ## 局限性以及它特意不包含的内容 - **仅限 HTTP,在回环上。** 没有 TLS。这意味着无法演示 `SameSite=None; Secure` cookie 要求,因为 `Secure` cookie 不会通过普通 HTTP 发送 —— 涉及凭据的场景在关键处进行了说明。 - **它无法读取你的控制台。** 页面无权访问其自己的开发者工具控制台;这是浏览器的隐私边界,这是正确的。结论面板引用了预期 Chrome 和 Firefox 会打印的消息,并要求你进行比较,而不是假装已经捕获了它。 - **preflight 缓存是浏览器的,而不是该工具的。** Max-Age 场景依赖于你浏览器的 preflight 缓存。在开发者工具中勾选了“禁用缓存”的强制重载,或者使用隐私窗口,都会抑制它,课程将会显示两次 preflight 而不是一次。面板会告诉你何时发生了这种情况。 - **各浏览器在边缘情况上存在差异。** 当观察到的结果与场景声明的预期不符时,UI 会明确指出,而不是隐藏它。网络跟踪始终是服务器发送内容的真实来源;测试在每次运行时都会对其进行断言。 - **它不诊断你的应用程序。** 它教授的是模型。没有将其指向真实 API、重播捕获的请求或扫描主机的功能。请使用 curl 进行重现,使用网络面板进行诊断。 - **不是 CORS 库或参考实现。** 服务器的 header 逻辑是为了可读性和能够故意犯错而编写的。不要将其照搬到生产环境中。 - **一次只能有一个配置。** API origin 在内存中保存单个全局配置。同时运行场景的两个浏览器标签页将为此发生冲突。 - **没有持久化。** 配置和日志保存在内存中,退出时即消失。日志上限为 400 条记录。 ## 测试 测试套件通过临时端口上的真实套接字驱动真实的 API 服务器,并针对每个场景断言响应 header 与场景的教学文本所承诺的完全一致 —— 状态码、preflight 处理、通配符/凭据交互、`Vary`、`Max-Age` 和 `Expose-Headers` 放置以及重复 header 的情况。场景在 `simulator/scenarios.py` 中统一定义一次,并由 UI 和测试共同使用,因此课程内容不会偏离服务器实际执行的操作。 ``` $ python3 -m unittest discover -s tests -v ... Ran 40 tests in 3.056s OK ``` 仅使用标准库。没有 CI 配置,因为不需要安装任何东西。 ## 延伸阅读 关于此处演示的机制的背景知识,来自 cross-origin.com,这是一个关于 CORS、preflight 机制和浏览器安全边界的技术参考: - [同源策略基础](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/) - [浏览器如何评估同源策略](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/origin-matching-rules-validation/how-browsers-evaluate-same-origin-policy/) - [简单请求与 preflight 请求](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/simple-vs-preflight-requests/) - [为什么 preflight 请求使用 OPTIONS 方法](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/simple-vs-preflight-requests/why-preflight-requests-use-options-method/) - [为什么 localhost 和 127.0.0.1 是不同的 origin](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/origin-matching-rules-validation/why-localhost-and-127-0-0-1-are-different-origins/) - [理解 Access-Control-Allow-Credentials](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/credential-sharing-security-boundaries/understanding-access-control-allow-credentials/) - [调试缺失的 Access-Control-Allow-Origin header](https://www.cross-origin.com/core-cors-mechanics-same-origin-policy-fundamentals/cors-error-code-breakdown/debugging-missing-access-control-allow-origin-header/) - [在 DevTools 中阅读 preflight OPTIONS 请求](https://www.cross-origin.com/cross-origin-debugging-error-diagnosis/devtools-network-preflight-inspection/reading-preflight-options-request-in-devtools/) - [使用 curl OPTIONS 模拟 preflight](https://www.cross-origin.com/cross-origin-debugging-error-diagnosis/curl-cors-reproduction/simulating-preflight-with-curl-options/) - [修复重复的 Access-Control-Allow-Origin header](https://www.cross-origin.com/cross-origin-debugging-error-diagnosis/proxy-layer-cors-troubleshooting/fixing-duplicate-access-control-allow-origin-header/) - [如何有效地设置 Access-Control-Max-Age](https://www.cross-origin.com/preflight-request-optimization-caching-strategies/cache-duration-tuning-max-age/how-to-set-access-control-max-age-effectively/) - [preflight 与简单请求的性能成本](https://www.cross-origin.com/preflight-request-optimization-caching-strategies/preflight-performance-analysis/preflight-vs-simple-request-performance-cost/) - [正确处理 Vary: Origin header](https://www.cross-origin.com/server-side-cors-configuration-header-management/access-control-header-directives/handling-vary-origin-header-correctly/) - [通配符与动态 origin 反射:何时使用哪种](https://www.cross-origin.com/server-side-cors-configuration-header-management/cors-security-auditing/wildcard-vs-dynamic-origin-reflection-when-to-use-each/) - [在开发环境中安全地允许 localhost origin](https://www.cross-origin.com/server-side-cors-configuration-header-management/wildcard-risks-mitigation/safely-allowing-localhost-origins-in-development/) - [CORS 安全审计检查表](https://www.cross-origin.com/server-side-cors-configuration-header-management/cors-security-auditing/cors-security-audit-checklist/) ## 贡献指南 参见 [CONTRIBUTING.md](CONTRIBUTING.md)。新的场景是最有用的贡献,每个场景都需要一个测试来确保其所承诺的 header 正确无误。 ## 许可证 MIT。参见 [LICENSE](LICENSE)。
标签:CORS, SOC Prime, Syscall, Web开发, 多模态安全, 开发工具, 教学演示, 数据可视化, 网络请求