你可能好奇:“浏览器是怎么发现跨域请求并拦截的?它又凭什么说‘这个请求不能通过’?”
其实,浏览器的拦截机制就像小区门口的保安,会严格检查每一个“进出请求”,一旦发现不符合规则(跨域且没通行证),就会立刻拦下。下面我们分步骤拆解这个过程,用生活化的比喻帮你彻底理解。
一、浏览器的“保安岗哨”:同源策略(Same-Origin Policy)
浏览器的核心安全机制叫同源策略,它规定:
只有协议、域名、端口完全相同的网站(即“同源”),才能直接互相访问数据。
如果违反了这一规则(比如你的网页是https://奶茶店.com
,却想直接读取https://咖啡工厂.com
的数据),浏览器就会触发拦截。
举个栗子🌰:
- 同源:
https://奶茶店.com/page1
和https://奶茶店.com/page2
(协议、域名、端口都一样)。 - 跨域:
https://奶茶店.com
想访问https://咖啡工厂.com/api/data
(域名不同),或者http://奶茶店.com
想访问https://奶茶店.com/api/data
(协议不同)。
二、拦截的“分步流程”:从请求发出到被拦下
当你的前端代码(比如JavaScript)发起一个跨域请求时,浏览器会像保安一样层层检查,最终决定是否放行。具体流程如下:
1️⃣ 第一步:浏览器发现请求是跨域的
当你执行类似 fetch('https://咖啡工厂.com/api/data')
的代码时,浏览器会先解析目标URL(https://咖啡工厂.com/api/data
),然后对比当前网页的地址(比如 https://奶茶店.com
):
- 如果协议、域名、端口有一个不同 → 判定为跨域请求。
- 如果是同源请求 → 直接放行,不会触发CORS检查(因为同源策略允许)。
此时浏览器心里想:
“哎?这个请求的目标地址和当前网页不是一个地方的!得先看看对方(服务器)有没有给我开通行证(CORS头部)。”
2️⃣ 第二步:浏览器先发一个“试探请求”(如果是非简单请求)
根据请求的类型(简单请求 or 非简单请求),浏览器的行为会有所不同:
情况A:简单请求(GET/POST/HEAD + 简单头部)
- 浏览器直接发送真实请求到服务器(比如
GET https://咖啡工厂.com/api/data
),但会在请求头里偷偷塞一个Origin: https://奶茶店.com
(告诉服务器:“我是从奶茶店来的”)。 - 服务器收到请求后,如果支持CORS,会在响应头里返回
Access-Control-Allow-Origin: https://奶茶店.com
(盖章:“允许奶茶店访问”)。 - 如果服务器没返回CORS头部 → 浏览器直接拦截响应,哪怕服务器已经返回了数据!
情况B:非简单请求(PUT/DELETE/带自定义头部等)
- 浏览器不会直接发真实请求,而是先发一个
OPTIONS
请求(叫“预检请求”),问服务器:“我想发一个
PUT /api/data
请求,还带了X-Custom-Header: xxx
,你允许吗?” - 服务器需要回复:
- 是否允许这个方法(
Access-Control-Allow-Methods: PUT
); - 是否允许这些头部(
Access-Control-Allow-Headers: X-Custom-Header
); - 是否允许这个来源(
Access-Control-Allow-Origin: https://奶茶店.com
)。
- 是否允许这个方法(
- 如果服务器没通过预检 → 浏览器直接拦截,不会发真实请求!
- 如果服务器通过了预检 → 浏览器才会发真正的
PUT
请求。
关键点:
无论是简单请求还是非简单请求,只要服务器没返回正确的CORS头部,浏览器就会拦截响应或请求!
3️⃣ 第三步:浏览器检查响应头是否有“通行证”
如果服务器返回了数据,浏览器会检查响应头里有没有以下关键字段:
Access-Control-Allow-Origin: https://奶茶店.com
(或*
表示允许所有域名)。- 其他可能的CORS头部(如
Access-Control-Allow-Methods
、Access-Control-Allow-Headers
)。
如果缺少这些头部 → 浏览器直接拦截数据,前端代码拿不到任何结果!
如果头部正确 → 浏览器放行数据,前端代码可以正常使用。
三、拦截的表现:前端代码“悄无声息”地失败
当浏览器拦截跨域请求时,前端代码不会收到任何数据,但也不会直接报“网络错误”(因为请求可能已经发到服务器了,只是浏览器不让用结果)。你会在浏览器的开发者工具(F12→Console)里看到类似这样的报错:
Access to fetch at 'https://咖啡工厂.com/api/data' from origin 'https://奶茶店.com'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
翻译成人话:
“浏览器发现你跨域访问了
https://咖啡工厂.com
的数据,但对方没给你开通行证(响应头里没有Access-Control-Allow-Origin
),所以直接没收了数据,你啥也拿不到!”
四、对比:为什么Postman/curl能拿到数据,但浏览器不行?
很多人会疑惑:
“我用Postman或者命令行工具(如curl)访问同一个API,明明能拿到数据,为什么浏览器就不行?”
原因很简单:
- Postman/curl不受同源策略限制:它们是“直接访问”服务器的工具,没有浏览器的安全限制,所以不管服务器有没有CORS头部,都能拿到数据。
- 浏览器有“保安”(同源策略):浏览器必须严格遵守CORS规则,如果没有通行证,即使服务器返回了数据,也会被强行拦截。
类比:
- Postman/curl就像快递员直接进工厂拿货,工厂不管是谁来拿,只要货存在就给。
- 浏览器就像顾客去超市买东西,必须出示会员卡(同源)或者临时通行证(CORS头部),否则保安(浏览器)会拦住你。
五、总结:浏览器拦截跨域请求的“核心逻辑”
- 先判断是否跨域:对比协议、域名、端口,不同就是跨域。
- 如果是非简单请求,先发
OPTIONS
预检请求,问服务器是否允许。 - 服务器响应后,检查响应头有没有
Access-Control-Allow-Origin
:- 有且匹配当前域名 → 放行数据。
- 没有或匹配失败 → 拦截数据。
- 前端代码只能拿到被放行的数据,被拦截的请求对开发者透明(只能看到报错)。