跳至正文
老丹的足迹 —— 代码写给机器,游记写给自己,感悟写给时间
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面
老丹的足迹 老丹的足迹
老丹的足迹 老丹的足迹
  • 首页
  • 示例页面
  • 首页
  • 示例页面

跨域:一个让无数前端开发者头疼的概念,彻底讲透它

如果你曾经在浏览器控制台看到过红色的 Access-Control-Allow-Origin 报错,那么你已经被“跨域”折磨过了。这篇文章,我们从零开始,把跨域的前因后果、底层原理、解决方案,一次性梳理清楚。

一、从一个场景说起

想象一下这个场景:

你正在浏览器里访问 www.example.com 这个网站,网站里有一段 JavaScript 代码,想要去请求 test.example.com 上的数据。你觉得这段代码能成功拿到数据吗?

答案是:不能。浏览器会在控制台给你报一个红色的错误,大意是“跨域请求被拦截了”。

可问题是:

  • 你在地址栏直接输入 test.example.com,能正常打开。
  • 你用 curl 命令在终端里请求 test.example.com,也能拿到数据。
  • 为什么偏偏 JavaScript 代码去请求就不行?

这就是跨域最让人困惑的地方——同样的请求,换个方式就能成功,用 JS 发就被拦截。

要理解这个问题,我们必须先搞懂一个概念:源。

二、什么是“源”?

“源”(Origin)是浏览器给每个网站贴的一个“门牌号”,由三个部分拼接而成:

组成部分含义举例
协议(Protocol)通信方式http:// 或 https://
域名(Domain)网站地址www.example.com
端口(Port)入口编号:80 或 :443

当你在浏览器里打开一个网页时,浏览器会根据这个网页的 URL,计算出它的“源”。

https://www.example.com:443
─┬─  ───┬─── ─┬─
 协议        域名       端口

只有当两个 URL 的协议、域名、端口完全一致时,它们才属于同一个“源”。

看看下面这些例子:

页面地址请求地址是否同源原因
http://example.com/pagehttp://example.com/api✅ 是协议、域名、端口全相同
http://example.com/pagehttps://example.com/api❌ 否协议不同(http vs https)
http://example.com/pagehttp://test.example.com/api❌ 否域名不同(子域名不同)
http://example.com:80/pagehttp://example.com:8080/api❌ 否端口不同(80 vs 8080)

其中第二、三、四种情况,就是所谓的跨域——从一个“源”跨越到另一个“源”去请求资源。

三、为什么要有跨域限制?

这个问题可以从一个真实的安全漏洞说起。

假如没有跨域限制……

你登录了网上银行,地址是 https://bank.com。登录后,浏览器里保存了你的登录凭证(Cookie),所以后续你对 bank.com 发起的所有请求,都会自动带上这个凭证,银行服务器就知道“这是本人操作”。

好,一切正常。然后你打开一个新标签页,访问了一个恶意网站 https://evil.com。这个网站里有一段 JS 代码:

fetch('https://bank.com/transfer?to=hacker&amount=10000')

如果没有跨域限制,这段代码会带着你的 Cookie 发出请求,银行服务器以为是你在操作,一万块钱瞬间就转走了。

这就是跨站请求伪造(CSRF)。

正是因为存在这种风险,浏览器才制定了同源策略(Same-Origin Policy):只允许当前源的 JS 代码访问同源的数据,禁止访问非同源的数据。

同源策略是浏览器最核心的安全防线之一,它保护的是用户的数据安全。

四、跨域到底“拦截”了什么?

很多人以为跨域是“请求根本没发出去”,这是最大的误解。

真相是:请求发出去了,后端也处理了,甚至返回了正确的数据,但浏览器中途把数据截胡了,没交给你的 JS 代码。

整个流程是这样的:

1. 你的 JS 代码发起请求  →  ✅ 请求正常发出
2. 请求到达目标服务器    →  ✅ 服务器正常处理
3. 服务器返回响应数据    →  ✅ 响应正常返回
4. 浏览器收到响应        →  ❌ 检查源,发现跨域 → 数据被拦截
5. 你的 JS 代码          →  ❌ 收到报错,拿不到数据

所以,跨域失败的本质是“响应被浏览器扣下了”,而不是“请求没发出去”。这就是为什么后端日志里能看到请求成功(状态码 200),但前端控制台却在报跨域错误——浏览器把成功返回的数据扔掉了,只给你看了一个错误提示。

五、有哪些请求会受到跨域限制?

同源策略并不是禁止所有跨域请求,它针对的主要是由 JavaScript 发起的网络请求。

❌ 受跨域限制的请求

  • XMLHttpRequest 对象发起的请求
  • Fetch API 发起的请求
  • WebSocket 连接(受同源策略影响,但有单独的握手机制)
  • EventSource(SSE,Server-Sent Events)

这些请求的共同特征是:由 JS 代码主动发起,且可以读取响应内容。

✅ 不受跨域限制的请求

  • 在浏览器地址栏直接输入 URL 访问
  • <img src="跨域地址"> 加载图片
  • <script src="跨域地址"> 加载脚本
  • <link rel="stylesheet" href="跨域地址"> 加载样式
  • <video>、<audio>、<iframe> 等标签加载资源
  • 表单提交(<form action="跨域地址">,但无法读取响应)

这些请求的共同特征是:要么是用户主动触发的(地址栏访问),要么是浏览器标签自动加载的(图片、脚本),且 JS 代码无法直接读取其响应内容。

注意:<script> 标签加载跨域 JS 文件虽然不受同源策略限制,但浏览器仍会阻止跨域脚本读取你的 DOM 或 Cookie。另外,现代浏览器对 <script> 标签加载的跨域资源还增加了 SRI(子资源完整性)等安全校验机制。

对于 <script> 标签加载 JSONP 接口的场景,虽然请求本身不受同源策略限制,但服务器返回的是一段可执行的 JS 代码,而不是纯 JSON 数据——这和正常的 API 请求有本质区别,后面会在 CORS 章节详细说明。

正是因为这些标签不受跨域限制,JSONP、CDN 加载等方案才得以实现。同时也说明了一个关键点:跨域限制不是网络层面的拦截,而是浏览器层面的安全策略。

六、如何解决跨域问题?

理解了跨域的来龙去脉,解决它就有思路了。核心方向无非两个:要么让浏览器放行,要么让浏览器根本意识不到在跨域。

方案一:CORS(跨域资源共享)

CORS 是 W3C 制定的官方跨域解决方案。它的核心思想是:后端主动告诉浏览器“这个来源可以访问我”。

后端在响应头里加上这个字段:

Access-Control-Allow-Origin: https://www.example.com

浏览器收到响应后,检查这个头:如果当前页面的源在允许列表里,就放行数据;否则拦截。

如果后端的 API 需要开放给所有来源,可以设置为:

Access-Control-Allow-Origin: *

但生产环境不建议这么做,会有安全风险。

CORS 的两种请求

CORS 把请求分为两类:

1. 简单请求

满足以下条件的就是简单请求:

  • 方法为 GET、POST、HEAD 之一
  • 请求头只有 Accept、Accept-Language、Content-Language、Content-Type 等常规字段
  • Content-Type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain 之一

简单请求的流程很简单:浏览器直接发出请求,后端返回 Access-Control-Allow-Origin 头,浏览器据此决定是否放行。

2. 非简单请求

不符合上面条件的就是非简单请求,比如:

  • 方法为 PUT、DELETE、PATCH
  • Content-Type 是 application/json
  • 带了自定义请求头(如 X-Token)

对于非简单请求,浏览器会先发一个 OPTIONS 预检请求,询问后端:“我打算发一个跨域请求,你允许吗?”后端返回允许的规则后,浏览器才发出真正的请求。

整个过程是这样的:

浏览器:OPTIONS /api(预检)
后端:  响应 200,带上允许的方法和头
浏览器:GET /api(正式请求)
后端:  响应数据,带上 Access-Control-Allow-Origin
浏览器:✅ 放行,交给 JS

如果你在浏览器开发者工具的 Network 面板里看到两个相同的请求,其中一个方法是 OPTIONS,那就是预检请求。

对于预检请求,后端需要明确返回允许的 HTTP 方法(Access-Control-Allow-Methods)和允许的请求头(Access-Control-Allow-Headers),否则正式请求会被浏览器拦截。

方案二:JSONP

JSONP 是 CORS 出现之前,业界普遍使用的“曲线救国”方案。

它的原理很简单:既然 <script> 标签不受跨域限制,那我就用动态创建 <script> 标签的方式来请求数据。

function handleData(data) {
    console.log(data);
}

const script = document.createElement('script');
script.src = 'https://test.example.com/api?callback=handleData';
document.body.appendChild(script);

后端返回的响应像这样:

handleData({ name: '张三', age: 18 })

浏览器加载这个脚本后,自动执行 handleData 函数,数据就传进来了。

但 JSONP 的局限性很明显:

  • 只支持 GET 请求
  • 需要后端配合改造
  • 存在安全风险(XSS 攻击面增加)

所以现在基本被 CORS 取代,仅在某些老旧系统中还能见到。

方案三:Nginx 反向代理

这是最优雅、最彻底的跨域解决方案,也是你之前配置的方案。

原理很简单:让浏览器压根意识不到自己在跨域。

你在 Nginx 里配置一条路由:

location /test/ {
    proxy_pass http://test.example.com/;
}

然后前端请求的是:

https://www.example.com/test/api

浏览器一看:www.example.com 和当前页面同源,放行。至于 Nginx 内部怎么把请求转发给 test.example.com,那是服务器之间的事,浏览器看不到也管不着。

核心优势:

  • 前端代码不需要任何改动
  • 后端不需要配置 CORS
  • 安全,因为跨域请求根本没有从浏览器发出
  • 还可以顺带做负载均衡、缓存、SSL 终结等

方案四:WebSocket

WebSocket 不存在跨域限制,因为它不是基于 HTTP 的请求-响应模型,而是通过独立的握手协议建立长连接。如果你用 WebSocket 通信,天然就没有跨域问题。

方案五:postMessage + iframe

在特定场景下(比如嵌入第三方页面),可以用 window.postMessage() 配合 iframe 实现跨域通信。这种方式主要用于不同源的页面之间传递消息,而不是用于 API 请求,应用场景相对有限。

七、各方案对比与适用场景

为了帮你快速做决策,我把五种方案的核心差异整理成一张表:

方案原理需要改前端?需要改后端?适用场景
CORS后端告诉浏览器放行❌ 否✅ 是你控制后端 API,或需要开放 API 给第三方
JSONP<script> 标签绕过限制✅ 是✅ 是老旧系统、仅需 GET 请求,几乎已淘汰
Nginx 反向代理同源请求,服务器端转发❌ 否❌ 否首选方案,尤其是前后端在同一域名下时
WebSocket协议本身无跨域限制✅ 是✅ 是实时通信、双向交互场景
postMessage + iframe页面间消息传递✅ 是❌ 否嵌入第三方页面、跨域页面通信

做选择的建议:

  • 如果前后端都能部署在同一个域名下 → Nginx 反向代理,最简单干净
  • 如果后端是开放 API,需要给多个前端应用使用 → CORS,让后端配一次就行
  • 如果需要实时双向通信 → WebSocket
  • JSONP 和 postMessage 方案在 2026 年的今天已经用得很少了,了解一下原理即可,实际项目中优先考虑前三种

八、总结:一张图看懂跨域

┌─────────────────────────────────┐
│                        跨域问题全貌                            │
├─────────────────────────────────┤
│                                                                │
│  为什么会有跨域?                                               │
│  → 浏览器的同源策略,为了保护用户数据安全                       │
│                                                                 │
│  什么是跨域?                                                   │
│  → 协议/域名/端口不同,就是跨域                                │
│                                                                 │
│  跨域拦截了什么?                                               │
│  → JS 发起的网络请求(Ajax、Fetch)                            │
│  → 不拦截:地址栏访问、标签加载、表单提交、服务器间请求         │
│                                                                 │
│  跨域是怎么拦截的?                                             │
│  → 请求发出去了,数据也返回了,浏览器在中途把数据扣下了         │
│                                                                 │
│  怎么解决跨域?                                                 │
│  ├── CORS:后端告诉浏览器“放行”                             │
│  ├── JSONP:用 script 标签绕过(已淘汰)                      │
│  ├── Nginx 反向代理:让浏览器不觉得是在跨域 ★推荐            │
│  ├── WebSocket:协议本身无跨域限制                            │
│  └── postMessage:页面间跨域通信                              │
│                                                                 │
└──────────────────────────────────┘

最后的话

跨域之所以让人困惑,是因为它把“网络请求”和“浏览器策略”这两件事混在了一起。从网络层面看,请求畅通无阻;从浏览器层面看,数据被无情拦截。

理解了这两层的区别,跨域就不再神秘了。

回到你的场景:用 Nginx 把 www.example.com/test 代理到 test.example.com,这是最干净利落的方案——不需要改代码,不需要配 CORS,浏览器全程没离开过同源。不管后端是 HTTP 还是 HTTPS,不管有多少个子域名要转发,一套 Nginx 配置全部搞定。

如果你在实际部署中遇到具体的报错,欢迎随时来问我。

作者

老丹

关注我
其他文章
上一个

NAT回环:网络地址转换中的“发夹弯”与内外网访问一致性问题

下一个

ZLMediaKit:国产高性能流媒体服务器框架深度解析

关于博主

    老丹是一名C/C++后台开发工程师,信奉“无抽象不设计,无性能不生产”。

  • 技术栈:Modern C++、Linux环境编程、多线程/并发、网络编程等。
  • 信条:能用constexpr解决的问题绝不拖到运行时,能靠RAII避免的泄漏绝不写析构。
  • 正在填坑:从解封装到渲染的C++全链路实现,正在驯服FFmpeg与H.264/H.265。
  • 输出原则:这里的每一段代码都经过-Wall -Wextra -Werror -O2的洗礼。

近期文章

  • Linux系统的安全基石:深入理解可插拔认证模块(PAM) 2026年7月27日
  • vsftpd 完全指南:从核心原理到Docker容器化部署 2026年7月27日
  • 互联网的”导航”安全卫士:深入解读DNSSEC 2026年7月27日
  • Ubuntu DNS 配置完全指南 2026年7月27日
  • 在 Ubuntu 中使用 Certbot 的操作指南 2026年7月27日

文章分类

  • C/C++开发 (13)
  • Docker容器 (3)
  • Linux工具包 (10)
  • Linux服务配置 (33)
  • Linux系统 (10)
  • OpenWrt路由 (2)
  • Shell脚本 (3)
  • 安防技术 (4)
  • 数据安全 (30)
  • 网络协议 (17)
  • 计算机理论 (22)
联系我们:📍 地址:中国·广东省深圳市   |   ✉️ 邮箱:support@tanglinux.com   |   💬 QQ:870866607
版权所有:老丹的足迹粤ICP备2026061170号-1       公安备案图标 粤公网安备44030002013274号