跨域
想象一下,你曾经或者正登录着银行网站(bank.com),同时在另一个标签页打开了一个恶意网站(evil.com)。
如果没有跨域限制,evil.com 里的 JS 脚本可以轻而易举地:
- 读取你银行网站的 Cookie。
- 冒充你向银行发送「转账」请求。
- 读取你的私人账单数据。
这就是著名的 CSRF?(跨站请求伪造) 和 数据泄露 风险。为了防止这种「大乱斗」,浏览器强制执行了 同源策略?(Same-Origin Policy)。
虽然跨域有限制,但现代互联网需要资源共享(比如你的博客引用自己的云音乐平 台的歌曲,通过脚本读取音频渲染出跳动的音符)。于是有了 CORS?(Cross-Origin Resource Sharing,跨源资源共享)。
它本质上是服务器与浏览器之间的一次对话:
- 浏览器问: 「嘿,服务器,我这有个来自
abc.com的网页,想拿你的 MP3,准吗?」 - 服务器答(响应头): 「准了!我带上
Access-Control-Allow-Origin: https://abc.com。」
如果你是一个善良的免费平台,你也可以用
*表示所有人都可以访问:Access-Control-Allow-Origin: *
- 浏览器看: 「嗯,名单上有它,放行!」
CORS 是白名单制度。服务器必须明确说出允许谁访问。
如果服务器没配 Header,或者缓存了没头的响应,浏览器会一直拒绝!
浏览器双标行为
在 Web 早期,像 <img>、<video>、<audio>、<script> 标签都被设计为可以引用任何域名的资源。浏览器认为,既然你只是想「看」或者「听」,并不会把像素数据或音频二进制流通过 JS 拿走,那就没风险。这被称为无损读取。因此<audio src="xxx">(无属性):浏览器以「匿名」身份去拿。服务器返回什么它都播,它不在乎有没有 Access-Control-Allow-Origin。
如果你想在网页上实现音频可视化(跳动的频谱)、音效滤镜或者点击下载:浏览器要求你必须通过 AudioContext 提取音频数据。此时必须用 fetch("xxx") 才能操作二进制数据。fetch("xxx"):浏览器以「跨域」身份去拿。如果服务器没给「通行证」,即使数据下载完了,浏览器也会在最后关头把数据拦下并报错。
如果你的代码之前通过 audio 标签在浏览器里缓存了一个音频(不带 Access-Control-Allow-Origin),之后又改为用 fetch 获取这个资源,浏览器会从缓存里取出无 CORS 头的旧响应提供给 fetch,并在控制台提示该资源被拦截。
建议: 给 audio 标签加上 crossOrigin="anonymous"。这样 audio 和 fetch 都会要求服务器返回 CORS 头。
为了避免相同资源被频繁读取,不只是浏览器,整个网络都充满了各种各样的缓存。
缓存链路
| 缓存层级 | 存储位置 | 常见缓存时长 (TTL) | 对 CORS 的影响 | 缓存清除方式 |
|---|---|---|---|---|
| 浏览器强缓存 | 用户本地磁盘/内存 | 分钟级到年级(由 Cache-Control 决定) | 最致命。一旦存入不带 CORS 头的响应,后续请求不再询问服务器。 | 强制刷新(Ctrl+F5)、手动清除浏览器缓存、或修改资源 URL(加版本号/hash)。 |
| 浏览器协商缓存 | 用户本地 | 即时校验(ETag / Last-Modified) | 相对安全。每次都会向服务器确认文件是否变更,服务器在响应(包括 304)中有机会补发正确的 CORS 头。 | 无需特殊清除,每次请求均会与服务器协商。 |
| Nginx 反向代理缓存 | 服务器内存/磁盘 | 通常不主动缓存(除非配置了 proxy_cache) | 若开启缓存且未配置 Vary: Origin,会将缓存的错误响应(无 CORS 头)发送给所有用户。 | 清空 proxy_cache 目录、重载 Nginx,或配置 proxy_cache_bypass。 |
| CDN(如云存储/边缘节点) | 全球边缘节点 | 小时级到天级(通常 24h) | 扩散性强。一旦边缘节点缓存了缺少 CORS 头的响应,该地区所有用户均会触发 CORS 报错。 | 在 CDN 运营商后台手动刷新/预热缓存,或修改资源 URL 强制回源。 |
| ISP / 运营商网关 | 运营商机房 | 不透明(几小时到几天) | 极少数情况下(通常仅限 HTTP 明文流量),运营商会劫持并缓存静态资源,HTTPS 下基本不受影响。 | 基本无法主动清除,只能等待自然过期,或改用 HTTPS 规避。 |
| 后端框架内存缓存 | 应用服务器内存 | 秒级到小时级(由代码逻辑决定) | 若缓存了不含 CORS 头的响应对象并直接返回,后续请求将持续缺少 CORS 头,且对所有用户生效。 | 重启应用服务、调用缓存失效接口(如 Redis DEL、Spring @CacheEvict),或等待 TTL 自然过期。 |
浏览器缓存
缓存条目的 Response Headers 在开发者工具里不会展示 Access-Control-Allow-Origin,给排查带来一定困难。
浏览器的逻辑顺序
- 读取: JS 发起 fetch,浏览器发现磁盘缓存命中。
- 校验: 浏览器取出 from disk cache 的原始响应头,内部检查是否包含
Access-Control-Allow-Origin。 - 通过: 检查通过,数据交给 JS。出于安全考虑,浏览器在 Network 面板中会省略展示部分安全相关头(如 CORS 头)。
- 展示: 当你在开发者工具(Network 选项卡)点开该请求时,看到的是浏览器认为对当前调试有意义的头信息。
chrome://net-export/ 是 Chrome 最强大的网络日志工具,可将所有网络行为(包括读取缓存的细节)记录到一个 JSON 文件中。
操作步骤:
- 在地址栏输入
chrome://net-export/。 - 点击 "Start Logging to Disk"