跨域:浏览器到底拦住了什么

跨域的根本问题

先假设一个危险的情况:网站 A 可以随意读取网站 B 的响应。

用户已经登录银行,浏览器可能带着银行的 Cookie。此时,恶意网页只需要调用银行的余额、转账记录接口,再把响应发给攻击者,用户的敏感数据就泄露了。

所以,浏览器的**同源策略(Same-Origin Policy)**存在的核心目的,是阻断这种跨网站读取敏感数据的行为。

而 CORS(Cross-Origin Resource Sharing),则是服务器向浏览器声明的、受规则约束的例外机制:服务器可以明确告诉浏览器,哪些其他来源的网页可以读取自己的响应。

什么叫"同源"?

一个源由三部分共同组成:

Origin = 协议 + 主机 + 端口

这三者只要有一个不同,就是不同源:

text 复制代码
http://localhost:5173
-> http://localhost:3000   不同端口,跨源

http://localhost:3000
-> https://localhost:3000  不同协议,跨源

http://localhost:3000
-> http://127.0.0.1:3000  不同主机,跨源

跨域不等于请求绝对发不出去

浏览器需要允许很多跨源资源加载,否则网页根本无法使用图片、样式表或跳转链接。

真正受保护的重点是:

所以,看到 CORS 报错时,更准确的理解是:

浏览器不允许当前网页的 JavaScript 读取这次跨源响应。

这句话很重要。CORS 不等于请求一定没有到达服务器。 对于一些请求,浏览器可能会发送请求,但把响应拦在网页 JavaScript 之外。

这也是为什么 CORS 不能当作后端的认证、授权或 CSRF 防护。服务端仍然必须自己验证用户身份、权限和请求是否合法。

CORS 如何表达"同意"?

当网页 A 请求服务器 B 时,浏览器会带上请求来源信息:

http 复制代码
Origin: https://a.example

如果服务器 B 允许 https://a.example 的网页读取响应,应在响应中返回:

http 复制代码
Access-Control-Allow-Origin: https://a.example

Access-Control-Allow-Origin 的含义是:

允许来自 https://a.example 的网页读取这次响应。

其因果链如下:

所以,浏览器不是在信任网页 A,而是在执行服务器 B 对网页 A 做出的授权声明。

但这里的"授权"只针对浏览器能否把响应交给网页 JavaScript,不替代业务层面的登录认证和权限判断。

预检请求

并不是所有跨源请求都需要服务器先允许。

像有些满足 CORS"简单请求"条件的请求,浏览器会直接发送;而有些请求不满足这些条件,浏览器会先发送一个 OPTIONS 请求询问目标服务器。这个"先问"的行为,就叫预检请求(Preflight Request)。

这里不是浏览器判断"这个业务风险高不高",而是浏览器根据方法、请求头和 Content-Type 是否属于 CORS safelist 来决定是否预检。

预检通常会询问三件事:

http 复制代码
Origin: https://a.example
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type

服务器必须分别回应:

  • 是否允许这个来源。
  • 是否允许这个 HTTP 方法。
  • 是否允许这些额外请求头。

例如:

http 复制代码
Access-Control-Allow-Origin: https://a.example
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type

像 PUT、DELETE、Authorization 请求头、application/json 等情况,通常会触发预检。

预检的目的在于:

让目标服务器先声明:浏览器是否可以从这个源,以这个方法、带着这些请求头,发起实际跨源请求并读取响应。

即使预检通过,服务器在真正收到实际请求后,仍然必须完成认证、授权和业务校验。

一个完整的跨域请求

1. 预检请求

浏览器先发送 OPTIONS。如果服务器返回的 CORS 响应头满足要求,浏览器才继续发送实际请求。

2. 实际请求

实际响应也需要包含相应的 CORS 响应头,浏览器才会把响应内容交给网页 JavaScript。

跨域请求想携带 Cookie,还需要什么?

跨源请求是否携带 Cookie,不是只配置 Access-Control-Allow-Origin 就够了。

前端需要显式声明:

js 复制代码
fetch('https://api.example.com/orders', {
  credentials: 'include'
})

服务端则需要返回:

http 复制代码
Access-Control-Allow-Origin: https://a.example
Access-Control-Allow-Credentials: true

命令行或后端服务不受 CORS 影响

如果攻击者不是从浏览器调用,而是使用命令行程序或后端服务,则不会遇到 CORS 报错。

因为 CORS 从一开始就是浏览器的同源策略机制。curl、后端服务不受浏览器规则约束;它们能否访问接口,仍应该由服务端的身份认证、权限授权和网络安全策略决定。

总结

同源策略默认不让网页 JavaScript 读取跨源响应;CORS 是服务器授予特定来源读取权限的声明;它不是服务端安全边界,更不能替代认证、授权和 CSRF 防护。

相关推荐
rannn_1118 小时前
【Java面试题】高频面试题1|Java 后端、MySQL、Redis 与 RAG 面试整理
java·jvm·数据库·后端·面试
拖孩8 小时前
一个全程 AI 写的小程序「厨菜记」,上线 20 天跑通流量主,收入几块钱,开心得不行
前端·后端·微信小程序
颜进强8 小时前
21 · NestJs AsyncProviders 异步提供者:useFactory 返回 Promise 之后,容器发生了什么
前端·后端·ai编程
Mikko78 小时前
JVM 线上排查实战(七):jps 看不到进程、jstack 报不允许的操作怎么办?attach 失败的六种情况实测
java·运维·jvm·后端
小宋10218 小时前
Agent 工具升级如何不破坏线上:Tool Schema 版本兼容与契约测试
java·人工智能·后端·spring
geovindu8 小时前
rust: Flyweight Pattern
开发语言·后端·设计模式·rust·享元模式·结构型模式
我的xiaodoujiao8 小时前
Django 基础知识详细图文教程 13-Django 模型定义与使用 3
数据库·后端·python·测试工具·oracle·django
SimonKing8 小时前
QClaw关停之后:一个工具的退场,一段关系的告别
java·后端·程序员
不一样的少年_9 小时前
别用前端思维写后端:一张 5MB 图片,为什么能撑爆内存?
前端·后端·图片资源
夜之眷属9 小时前
记一次战斗服务器 CPU 打满 100% 且“无法恢复“的排查
java·linux·运维·服务器·后端·性能优化