工程上五种跨域解决方案
五种方案决策图
graph TD A["跨域问题"] --> B{"选择方案"} B -->|"标准方案(推荐)"| C["① CORS<br/>服务端加响应头"] B -->|"仅 GET + 老系统"| D["② JSONP<br/>script 标签不受同源限制"] B -->|"开发环境"| E["③ 代理 Proxy<br/>webpack devServer"] B -->|"生产环境"| F["④ Nginx 反向代理<br/>同源化"] B -->|"跨窗口通信"| G["⑤ postMessage<br/>iframe / 窗口间"]
1.1 方案一:CORS(配响应头)——最标准的做法
适用场景: 你控制了后端代码,可以加 CORS 响应头
优点: 标准方案,浏览器原生支持,安全可控
缺点: 需要改后端;多环境(dev/staging/prod)需要维护多个域名白名单
实现: 见上面 2.5 的代码。
一句话讲清:“生产环境用白名单校验 Origin——不能直接把请求里的 Origin 反射回去,那样等于允许所有来源。“
1.2 方案二:JSONP(仅 GET,考古级方案)
核心原理: <script> 标签不受同源策略限制。利用这个特性——把数据包在函数调用里返回,前端提前定义好这个函数。
前后端代码——看一次就知道为什么没人用了
前端:
<script>
function handleData(data) { // ← 提前定义好回调函数
console.log(data);
}
</script>
<script src="http://api.example.com/data?callback=handleData"></script>
<!-- 这个 script 标签加载的"JS 文件"实际上是后端返回的 JSON 包裹在函数调用里 -->
后端返回的不是 JSON,是一段”看起来像 JS 调用”的文本:
handleData({"name": "张三", "age": 22})
→ 浏览器把它当 JS 执行 → 调用了 handleData 函数 → 数据拿到了!
这就是 JSONP = JSON with Padding(用函数调用把 JSON “包”起来)。
优缺点:
-
✅ 兼容性好——IE6 都能用
-
❌ 只支持 GET——POST/PUT/DELETE 全做不了
-
❌ 不安全——后端完全信任前端传的函数名,容易被注入
-
❌ 错误处理困难——script 加载失败只能靠超时兜底
-
❌ 2025 年了,别用了。提一句”过时方案,仅作了解”就行。
1.3 方案三:Vite/Webpack 开发代理(本地开发首选)
适用场景: 本地开发时前端跑在 localhost:3000,后端跑在 localhost:8000
原理: 开发服务器在中间拦截请求→转发到后端→拿回响应→加上正确的 Origin 返回给浏览器。整个过程中,浏览器以为自己一直在和 localhost:3000 通信(同源!)
Vite 配置(vite.config.ts)
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8000', // 后端地址
changeOrigin: true, // 改 Origin 头为后端地址
rewrite: (path) => path.replace(/^\/api/, ''), // /api/users → /users
}
}
}
});
→ 前端代码写 axios.get('/api/users')(不写域名!因为同源)
→ Vite 开发服务器拦截 /api 开头的请求 → 转发到 http://localhost:8000
→ 浏览器看到的是同源请求 → 不存在跨域!
类比——代购:
你想买日本的东西 → 日本店铺不寄中国(跨域限制)
你找代购(开发代理)→ 代购在日本帮你买(服务器间通信没有跨域)
→ 代购寄给你 → 你收到包裹,发货地址是代购的国内地址(对你来说这是同源)
1.4 方案四:Nginx 反向代理(生产环境首选)
适用场景: 生产环境——前端 React/Vue build 产物 + 后端 API 部署在同一台机器
原理: Nginx 把前端静态文件和后端 API 都挂在同一个域名下。用户浏览器只看到一个域名 → 根本不存在跨域!
Nginx 配置
server {
listen 80;
server_name app.example.com; # 只有一个域名
# 前端静态文件
location / {
root /var/www/frontend/dist;
try_files $uri /index.html; # SPA 路由 fallback
}
# 后端 API——用 /api 前缀区分
location /api/ {
proxy_pass http://127.0.0.1:8000; # 反向代理到后端
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
→ 用户访问 http://app.example.com → Nginx 返回前端页面
→ 前端 axios.get('/api/users') → 同域名!不跨域!
→ Nginx 收到 /api/users → 转发给后端 → 后端返回 → Nginx 返回给浏览器
→ 整个链路浏览器只和 app.example.com 打交道
这比配 CORS 更好——CORS 是”允许跨域”,反向代理是”消除跨域”。从根源解决问题。
1.5 方案五:postMessage + iframe(特殊场景)
适用场景: 两个不同域的页面需要通信(如主页面和嵌入的第三方 iframe)
原理: HTML5 的 postMessage API 允许不同源的窗口之间安全传递消息
示例——父页面(a.com)和 iframe(b.com)互发消息
父页面(a.com):
const iframe = document.getElementById('child-frame');
iframe.contentWindow.postMessage('你好,我是父页面', 'https://b.com');
// ↑ 指定目标 Origin,防止消息泄露
window.addEventListener('message', (event) => {
if (event.origin !== 'https://b.com') return; // ⚠️ 必须验证来源!
console.log('iframe 回复:', event.data);
});
iframe 页面(b.com):
window.addEventListener('message', (event) => {
if (event.origin !== 'https://a.com') return; // ⚠️ 必须验证来源!
console.log('父页面说:', event.data);
event.source.postMessage('收到!', event.origin);
});
适用场景: 第三方支付页面嵌入、在线文档协作、微前端子应用通信
1.6 五种方案速查——直接甩表
| 方案 | 适用场景 | 一句话 | 需要改什么? |
|---|---|---|---|
| CORS | 你能改后端 | 服务器加响应头告诉浏览器”允许” | 改后端代码 |
| JSONP | 历史遗留(别用) | script 标签不受跨域限制 | 前后端都要改 |
| 开发代理 | 本地开发 | Vite/Webpack 中间拦截转发 | 改前端配置 |
| Nginx 反向代理 | 生产环境(推荐) | 前端+API 同域名,从根源消除 | 改 Nginx 配置 |
| postMessage | iframe 跨域通信 | HTML5 API,窗口间安全通信 | 改双方页面 |
✅ 开发环境:用 Vite/Webpack proxy——零配置,爽
✅ 生产环境:用 Nginx 反向代理——同域名,无跨域
✅ 对外 API:用 CORS 白名单——精确控制,安全
❌ JSONP:别用了——2025 年了
速记卡(面试闪卡)
Q1:一句话讲清「工程上五种跨域解决方案」到底是什么?
A:跨域源于浏览器同源策略,五种解法:CORS、JSONP、开发代理、Nginx 反向代理、postMessage。
Q2:1.1 方案一:CORS(配响应头)——最标准的做法 —— 怎么理解?
A:像”门卫看白名单”:服务端在响应头加 Access-Control-Allow-Origin 告诉浏览器”允许这个来源”。标准首选、浏览器原生支持;但多环境要维护域名白名单,且绝不能把请求里的 Origin 原样反射回去(等于放行所有来源)。
Q3:1.2 方案二:JSONP(仅 GET,考古级方案) —— 怎么理解?
A:像”把数据塞进函数电话”:利用 script 标签不受同源限制,后端把 JSON 包成 handleData({…}) 返回,前端提前定义函数接住。但它只支持 GET、易被注入、出错靠超时——2025 年了别用,提一句”过时”即可。
Q4:1.3 方案三:Vite/Webpack 开发代理(本地开发首选) —— 怎么理解?
A:像”代购”:本地前端跑 3000、后端 8000,devServer 代理拦截 /api 请求→转发后端→以同源身份还给浏览器。前端写 axios.get(‘/api/users’) 不打域名,以为一直跟自己通信,跨域根本不存在。开发环境零配置最爽。
Q5:1.4 方案四:Nginx 反向代理(生产环境首选) —— 怎么理解?
A:像”把两家店合并成一个门面”:生产把前端静态文件和后端 API 挂在同一域名(app.example.com),Nginx 用 location /api/ 反代到后端。浏览器只看到一个域名,跨域从根上消失——比配 CORS 更彻底(CORS 是”允许”,反代是”消除”)。
Q6:核心速记主线有哪些?
-
CORS:服务端加响应头,标准首选但别反射 Origin
-
JSONP:script 标签跨域,仅 GET、已过时
-
开发代理:Vite/Webpack 代购模型,本地开发零配置
-
Nginx 反向代理:同域名消除跨域,生产首选
口诀
A:跨域五把钥匙:CORS 标准最先提;
JSONP 只 GET 已过时,代理开发代购比。
生产 Nginx 同源化,从源头消跨域;
跨窗口用 postMessage,五把钥匙分场景。
相关链接
-
📋 目录:00-计算机网络
-
📚 学习清单:八股文学习路线图
-
🔗 同源策略 — 跨域的前提
-
🔗 CORS跨域资源共享 — CORS 详解
-
🔗 跨域与CORS — 跨域补充笔记