Portfolio
← 返回博客列表
关于跨域和同域

2026-08-17 · 阅读 1

关于跨域和同域

跨域,同域

跨域与同域

一句话版本:协议、域名、端口,三者只要有一个不一样,就是跨域;三者完全一样,就是同域。


一、地基知识:什么是同域,什么是跨域

判断标准就一个:协议 + 域名 + 端口,三者完全相同 = 同域。

对比结果原因
http://www.a.com/page vs http://www.a.com/api同域 ✅路径不同不算,只看协议域名端口
http://a.com vs https://a.com跨域 ❌协议不同(http / https)
http://a.com vs http://www.a.com跨域 ❌域名不同(带不带 www 是两个域名)
http://a.com:80 vs http://a.com:8080跨域 ❌端口不同

大白话理解

  • 域名 = 小区名字
  • 协议 = 进小区的方式(走正门 / 走地库)
  • 端口 = 门牌号

三者全对上,才算"同一个地方的人",即同域;有一个对不上,就是"外来人口",即跨域。

一个重要认知(很多人不知道)

跨域限制只存在于浏览器里。服务器之间互相请求(比如后端调第三方 API)、用 Postman、用 curl,统统没有跨域问题。 因为这个限制是浏览器定给自己的一条安全规矩,别的地方不归它管。


二、为什么浏览器要搞这个限制?—— 它在防贼

想象一个场景:

你登录了 www.bank.com,浏览器帮你存好了登录 Cookie
然后你手贱点开了一个邪恶网站 www.evil.com

如果没有跨域限制,evil.com 的网页里可以偷偷写一行代码:

// 趁你不注意,用你的身份去查银行余额
fetch('https://www.bank.com/api/my-balance')

关键在于:浏览器发请求时会自动带上 bank.com 的 Cookie。银行一看,Cookie 是对的,以为是本人,就把余额乖乖返回了 → evil.com 拿到了你的钱。

所以浏览器立了一条规矩,叫同源策略

从 A 网站加载的 JS,不许去读写 B 网站的响应。

大白话:你家的保姆(A 站的 JS)不能拿着你家钥匙(Cookie)偷偷去邻居家(B 站)搬东西。

这也顺便解释了为什么 Postman 没有跨域问题——Postman 里没有你浏览器里的 Cookie,贼没钥匙,防不防都无所谓。


三、跨域到底是怎么被拦的?—— 一场"对暗号"

很多人以为跨域报错 = 请求没发出去。错。

真相是:请求发出去了,后端也正常返回数据了,但浏览器把数据扣下了,不给你的 JS 用。整个过程像门卫对暗号:

你的页面运行在 http://localhost:5173(前端开发服务器)

① 你的代码:fetch('http://localhost:3000/api/user')

② 浏览器发请求时,自动贴一张"身份证"(你控制不了,它自动加):
      Origin: http://localhost:5173        ← 意思是"我来自 5173"

③ 后端返回数据时,可以贴一张"通行证":
      Access-Control-Allow-Origin: http://localhost:5173
      ← 意思是"我允许 5173 的人来访问我"

④ 浏览器开始对暗号:
      身份证 = 通行证 ✅ → 数据交给你的 JS,一切正常
      通行证没贴 / 对不上 ❌ → 数据到了也扔掉,
                              控制台报红字(你熟悉的那坨 CORS 报错)

大白话理解

  • Origin 请求头 = 你递给门卫的身份证
  • Access-Control-Allow-Origin 响应头 = 保安亭贴的白名单

你平时看到的跨域报错,本质就是后端没贴白名单,或者白名单上写的不是你。所以解决跨域,说穿了就是想办法让"身份证"和"白名单"对上。


四、OPTIONS 预检请求 —— 先敲门,再进屋

上面那套流程适用于"老实巴交"的 GET 请求。但如果你的请求比较危险——比如 DELETE(删数据)、或者带上了登录 token 的自定义请求头——浏览器会多一道手续:先发一封问路信

① 你的代码:
fetch(url, { method: 'DELETE', headers: { Authorization: 'token' } })

② 浏览器先不执行,而是自动发一个 OPTIONS 请求(注意:你代码里根本没写它):
      "请问:我能用 DELETE 方法、带着 Authorization 头来访问你吗?"

③ 后端回信:
      允许的方法:GET, POST, DELETE
      允许的头:Authorization
      (并且这个许可有效期 1 天,1 天内不用重复问)

④ 浏览器:问了没问题 → 这才把真正的 DELETE 发出去

大白话理解

  • 普通请求 = 进便利店买瓶水,直接进
  • 危险请求 = 进别人家搬家具,得先敲门问一句"我能进来搬吗?",主人点头了才能动

实践提示:F12 打开网络面板,看到正式请求前多了一个 OPTIONS 请求,那不是 bug,是浏览器在替你敲门。


五、怎么解决跨域?(三个常用方案)

方案一:后端贴白名单(CORS 配置)—— 生产环境标准答案

让后端在响应里带上通行证:

// Node.js Express 例子
app.use(cors({ origin: 'http://localhost:5173' }));

大白话:保安亭把你的名字写进白名单,门卫一看对上了,放行。

方案二:开发环境用 Vite 代理 —— 找个中间人捎货

前面说了,跨域限制只在浏览器里。那我不让浏览器直接跨域,找个同域的中间人转发:

没有代理(跨域):
浏览器(5173) ───跨域❌──→ 后端(3000)

有代理(同域):
浏览器(5173) ──同域✅──→ Vite(5173) ──服务器之间无跨域✅──→ 后端(3000)

配置写在 vite.config.js

server: {
  proxy: {
    '/api': {
      target: 'http://localhost:3000',   // 真正的后端地址
      changeOrigin: true
    }
  }
}

之后代码里只写相对路径

fetch('/api/user')   // 请求发给 5173 自己(同域!),Vite 再转交给 3000

大白话理解:你不能出小区(浏览器不能跨域),但可以让小区物业(Vite)帮你跑腿,物业出门没有限制(服务器之间不检查暗号)。浏览器全程以为自己在访问 5173,根本没意识到跨域发生了。

这是日常开发最常用的方案

方案三:上线后前后端同域部署(Nginx)—— 干脆住一个小区

正式上线时,运维用 Nginx 把前后端挂在同一个域名下:

https://www.mysite.com/        → 返回前端页面
https://www.mysite.com/api/xx  → 转发给后端服务

同一个域,门卫根本不拦。

大白话理解:既然出小区要查证件,那前端后端干脆搬进同一个小区,从此不需要出门。

所以你会发现:跨域基本上是个"开发期问题",上线用同域部署就天然消失了。

方案四:本地同域、上线跨域 —— 反过来也行(常见架构)

和方案三正好相反的部署方式,很多公司在用:

【开发环境:同域】(浏览器以为没跨域)
浏览器(5173) ──同域✅──→ Vite ──proxy──→ localhost:3000 后端

【生产环境:跨域】(后端必须配 CORS)
浏览器 ──→ www.mysite.com      ← 前端静态资源(放 CDN)
       └─→ api.mysite.com     ← 后端 API(跨域 ❌,靠白名单放行)

注意 www.mysite.comapi.mysite.com 域名不同,是跨域的,所以生产环境必须由后端配 CORS 白名单放行前端域名。

为什么公司要这么部署?

  • 前端放 CDN,全球加速、抗流量
  • 一套 api.xxx.com 同时服务 Web、App、小程序(App 原生请求不受同源策略限制)
  • 前后端独立扩缩容、独立发版

代码怎么同时兼容两种环境:用环境变量切换请求地址——

# .env.development(开发)
VITE_API_BASE=/api

# .env.production(生产)
VITE_API_BASE=https://api.mysite.com
const baseURL = import.meta.env.VITE_API_BASE;
fetch(`${baseURL}/user`);

跨子域共享 Cookiewwwapi 虽跨域但同属主域 mysite.com,后端把 Cookie 设成 Domain=.mysite.com,两个子域的请求就都能带上这把钥匙,登录态打通。

方案四的坑:本地可以、上线报错?

这个架构确实有风险:本地走代理一路绿灯,上线后白名单写错(比如少了 https、带不带 www 写错),第一个请求就 CORS 报错。

但它依然是行业主流,因为:

  1. 跨域失败是"响亮失败":上线第一个请求就大红字,立刻发现,改一行配置就能修。不像内存泄漏那种静默 bug 藏三天。
  2. 正规团队有测试环境兜底:测试环境用和生产一样的域名结构(如 test.xxx.com + api-test.xxx.com),CORS 问题在测试环境就爆了,轮不到生产。
  3. 换来的架构收益(CDN、多端共用 API、独立扩容)是实打实的。

大白话理解:坑有两种——跳进去会喊疼的(CORS 配置错,秒发现)和底下埋刀的(静默 bug,深夜爆炸)。方案四属于前者,只要测试环境和生产"长得一样",坑在开发期就被踩掉了。真正挖坑的不是这个方案,而是"上了跨域架构却没有测试环境"。


默认情况下,跨域请求不携带 Cookie。你要带的话,必须前后端同时配合:

// ① 前端:明确声明要带 Cookie
fetch(url, { credentials: 'include' })
// ② 后端:白名单必须写具体地址,不能偷懒用 *
Access-Control-Allow-Origin: https://www.frontend.com   // ✅ 精确指定
Access-Control-Allow-Credentials: true
// ❌ 如果写 Access-Control-Allow-Origin: * 会直接报错,
//    浏览器认为"通配符 + 带凭证"太危险,不允许

大白话理解:默认出门不带钥匙(Cookie),怕丢。你要带钥匙,就得让目的地确认"只认你这一个人",写 *(谁来都行)这种敷衍白名单,门卫反而不敢放你带钥匙进。


七、全文速查表

概念一句话解释
同源策略浏览器防贼:A 站的 JS 不许读 B 站的响应
同域协议 + 域名 + 端口三者完全相同
跨域三者有任何一个不同
Origin 请求头浏览器自动贴的"身份证"(我来自哪)
Access-Control-Allow-Origin 响应头后端贴的"白名单"(我允许谁来)
OPTIONS 预检请求危险请求前,浏览器先替你问一句"我能来吗"
CORS后端贴白名单的那套标准机制
Vite 代理开发时找同域中间人转发,让浏览器以为没跨域
Nginx 同域部署上线后前后端一个域名,问题直接消失
credentials: 'include'跨域时想带 Cookie,前后端都得声明

八、面试总结(背这段就够了)

同域是协议、域名、端口三者完全相同;跨域限制是浏览器的同源策略,目的是防止恶意网站借用用户的 Cookie 偷取其他网站的数据,所以它只存在于浏览器,服务器之间和 Postman 都不受限。

拦截的机制是:浏览器自动带 Origin 头,后端通过 Access-Control-Allow-Origin 响应头声明白名单,对不上浏览器就扣下响应。对于 DELETE、PUT 或带自定义头的"危险请求",浏览器会先发 OPTIONS 预检,拿到许可后才发真正请求。

解决方案:生产环境由后端配置 CORS 白名单,或用 Nginx 把前后端部署在同一域名下;开发环境用 Vite 的 proxy 代理,让请求先发给同域的开发服务器再转发,从根本上绕开浏览器限制。


写在最后

回到最初那句话:

协议、域名、端口不一样就是跨域,相同就是同域。

这句话是地基,上面所有的内容其实只是在回答三个问题:

  1. 浏览器为什么拦? —— 防贼(防止恶意网站借你的 Cookie 偷数据)
  2. 怎么拦的? —— 对暗号(Origin 身份证 vs Allow-Origin 白名单)
  3. 怎么过? —— 让暗号对上(后端贴白名单),或者让浏览器根本不知道跨域了(代理 / 同域部署)