浏览器为什么要如此严格地限制跨域请求?

我来搭一个demo,把那句「严格限制」到底限制了什么,讲到能去 Network 面板玩玩的程度。

Demo 在线地址:numfeel.996.ninja/pages/cors-...

添加图片注释,不超过 140 字(可选)


先纠正一个误解

几乎所有人都把 CORS 理解成「请求被拦在门外」。打开 DevTools 的 Network 面板发一个被 CORS 拦下的 GET 请求看看------状态码 200,响应体其实已经返回给浏览器。 浏览器干的只有一件事:把响应体扣下,不交给你的 JS。

这一条就把「严格限制」的本质讲清楚了:限制的落点在「读跨域响应」,请求该发还是发、服务器该处理还是处理。

🔍 在 demo 里亲眼看这一步:打开页面,策略默认是 deny,直接点「fetch('/cors-lab/me')」。控制台抛 CORS 错。 可你切到 Network 看那条 /cors-lab/me,状态码 200。浏览器拦的是「把响应交给 JS」,拦不了「请求发出」。

添加图片注释,不超过 140 字(可选)

为什么是浏览器管

HTTP 无状态。服务器收到一个请求,只能靠 Origin / Referer 两个头去猜这请求是谁指使的。这俩头在浏览器里由浏览器自己写、脚本改不了;离开浏览器(curl、Python),想填什么填什么。后端永远没法可靠判断一个请求是不是某个网页里的脚本偷偷发的。

能可靠知道的,只有浏览器。脚本跑在它肚子里,它知道这段代码从哪个源加载来,这个信息脚本自己篡改不了。同源策略只能由浏览器执行------它管得了,别人管不了。


很多人想:把 cookie 拦住,相当于隐身发请求,不就不泄密了?W3C 确实给了这个选项(Allow-Origin: * + 不带 Allow-Credentials)。这只覆盖了「用 cookie 鉴权」一种场景。现实里信任方式远不止 cookie:

  • 内网信任:路由器、打印机、IoT 设备、内网管理台对「本网段的请求」几乎无条件信任,根本不要 cookie。一个恶意网页就能以你的网络位置去戳 192.168.x.x 的路由器后台,改 DNS、刷固件。一个 cookie 没传,泄露照样发生。
  • Token 鉴权:很多接口把 token 放 Authorization 头或 URL 里,跟 cookie 无关。浏览器不拦「读响应」,恶意页就读走你的私密数据。
  • 身份关联:哪怕没鉴权,一个跨域响应体本身可能就含你的身份(某个内网页面 HTML 里写着你的工号)。能「读」本身就是伤害。

所以限制得落在「读跨域响应」这一层。这正是 W3C 把它拆成 Allow-Origin(管能不能读响应)和 Allow-Credentials(管带不带 cookie)两个独立开关的原因。

🔍 在 demo 里操作同一件事:策略切 allow(Access-Control-Allow-Origin: *),勾上「携带凭据 (credentials: include)」再点 fetch------立刻被拦。规范禁止 * 与凭据同时使用。再切 allow-credentials(回显具体 Origin + Access-Control-Allow-Credentials: true),又能读了。两个开关各管各的,一清二楚。

策略切 allow(Access-Control-Allow-Origin: *),勾上「携带凭据 (credentials: include)」再点 fetch

allow-credentials

预检:OPTIONS 先问一句

带自定义头、用 PUT/DELETE、或者 Content-Type: application/json 的请求,都不算「简单请求」。浏览器会先发一个 OPTIONS 问问服务器「我能不能这么请求」,得到允许才发真正的请求。这一步叫预检。

deny 下预检被 403 顶回来,真正的请求根本不会发出;allow 下预检通过(204),才有后续的 GET/POST。这套机制挡的是「有副作用的请求」------服务器在预检阶段就有机会拒绝,避免请求真的发出去造成影响。

🔍 在 demo 里:实验二给请求加一个自定义头 X-Demo。下方会提示「需要预检(携带了自定义头 X-Demo)」。 点按钮后去 Network 看,先出现一条 OPTIONS /cors-lab/me。 策略是 deny 时它返回 403,后面没有真正的 GET;切到 allow 再点,OPTIONS 返回 204,紧跟着才是一条 200 的 GET。

实验 2|预检:OPTIONS 先问一句

添加图片注释,不超过 140 字(可选)

CORS 拦不住 CSRF

既然「简单请求照发」,CSRF 自然绕过 CORS------攻击者压根不在乎读不到响应,他要的是「请求被服务器执行」。所以 CSRF 的防线是 SameSite cookie、CSRF token,跟 CORS 无关。CORS 管「读」,SameSite cookie 管「带身份的写」,两条线各管各的,别混成一回事。

demo 里用一笔转账把这件事给你演示一遍。转账接口只收 application/x-www-form-urlencoded------这是个简单请求,浏览器不会预检,请求会直接送达服务器并真正扣款。

🔍 在 demo 里亲手验证:按实验三的四步走。 1. 把策略切 allow,点「读取余额」记下当前 ¥99999.00; 2. 切 deny,点「发起转账」------JS 抛 CORS 错; 3. 切回 allow,再点「读取余额」。余额变成了 ¥99899.00。 第 2 步那个「失败」的转账,其实已经扣款了。再点「查转账流水」,那条转账赫然在列。 这正好证明:CORS 拦的是「读」,拦不住「发」。

添加图片注释,不超过 140 字(可选)


严格,因为恶意脚本用的是你的脸

关键的不对称:恶意脚本运行时拥有你的全部浏览器上下文------cookie、内网位置、登录态、IP。你不是旁观者,你是被劫持的肉鸡。如果没有同源策略,你只要打开一个投毒网页,里面的脚本就能以你的身份读邮箱网银、扫描内网、改路由器 DNS、读你其它标签页里的内网系统。浏览器是这一切的唯一出入口,也是唯一能设卡的地方。

CORS 是一套协作机制:默认拒绝(浏览器单方面执行)→ 服务器用 Access-Control-Allow-Origin 显式放行某些源(浏览器代为检查)→ 对有副作用的请求先发预检。本质是 default-deny 加显式 opt-in。浏览器替那些没能力自我保护的服务器(也就是绝大多数)把了关。

最后试试看

再贴一下链接:numfeel.996.ninja/pages/cors-...

打开前先按 F12 把 Network 标签调出来,这一页所有的关键证据都在那。默认是 deny,直接点「fetch('/cors-lab/me')」就能看到「请求 200、JS 读不到」的名场面。想看预检就去实验二点带 X-Demo 头的那个;想看「拦读不拦发」就去实验三走一遍转账流程。

添加图片注释,不超过 140 字(可选)

引用:

相关推荐
Cobyte1 小时前
17.响应式系统比对:链表如何实现响应式系统的高效更新
前端·javascript·vue.js
2601_963771371 小时前
Speeding Up Creative Agency Portfolios: A Performance Guide
前端·数据库·php
禅思院1 小时前
AI对话前端从入门到崩溃:一个长对话引发的五层优化战争【二】
前端·前端框架·ai编程
IT_陈寒2 小时前
React useEffect里面写异步,我的状态怎么老丢?
前端·人工智能·后端
AI视觉网奇2 小时前
BambuStudio 自定义加载页面
服务器·前端·javascript
橙子家2 小时前
Extension storage、Interest groups 和 Shared storage 简介-浏览器缓存之【隐私沙盒与扩展专用存储】
前端
漂流瓶jz3 小时前
理解Webpack插件机制:从插件使用、各类编译对象、Tapable到自定义插件与钩子开发
前端·javascript·webpack
梦帮科技13 小时前
GRAVIS v4.0:基于Web的极速套利架构设计与实时数据流实现
前端·人工智能·rust·自动化·区块链·智能合约·数字货币
爱勇宝14 小时前
办公资料反复修改、补传、交接混乱,我做了个桌面工具来解决这件事
前端·后端·程序员