Hi,我是前端人类学 !
跨域是浏览器同源策略导致的拦截行为------协议、域名、端口任一不同,AJAX 请求即被阻断。解决方案没有万能药,需根据场景权衡。本文将对比五种主流方案,并给出选型建议。
文章目录
-
- [一、 现代标准答案:CORS(跨域资源共享)](#一、 现代标准答案:CORS(跨域资源共享))
-
- [1.1 核心原理](#1.1 核心原理)
- [1.2 两类请求](#1.2 两类请求)
- [1.3 关键配置(含凭证)](#1.3 关键配置(含凭证))
- [二、后端代理模式:Nginx 反向代理](#二、后端代理模式:Nginx 反向代理)
-
- [2.1 工作原理](#2.1 工作原理)
- [2.2 配置示例](#2.2 配置示例)
- [三、开发环境专用:Webpack / Vite 代理](#三、开发环境专用:Webpack / Vite 代理)
- [四、古典方案与特殊场景:JSONP 与 postMessage](#四、古典方案与特殊场景:JSONP 与 postMessage)
-
- [4.1 JSONP:仅限 GET 的"取巧"方案](#4.1 JSONP:仅限 GET 的“取巧”方案)
- [4.2 postMessage:页面间通信的桥梁](#4.2 postMessage:页面间通信的桥梁)
- 五、总结:如何选择?
一、 现代标准答案:CORS(跨域资源共享)
CORS 是 W3C 制定的现代跨域标准,也是目前最推荐 的生产环境解决方案。它由服务端主导,通过在响应头中设置特定字段,明确告诉浏览器"允许哪些源访问我的资源"。
1.1 核心原理
浏览器在发起跨域请求时,会自动携带 Origin 头。服务端若允许,需在响应头中携带 Access-Control-Allow-Origin 字段。
1.2 两类请求
CORS 将请求分为简单请求 和非简单请求,处理机制不同。
- 简单请求 (如 GET/POST,Content-Type 为
text/plain等):浏览器直接发送请求,服务器返回Access-Control-Allow-Origin即可。 - 非简单请求 (如 PUT 方法,或 Content-Type 为
application/json):浏览器会先发送一次 OPTIONS 预检请求(Preflight),询问服务器是否允许。通过后,才会发送真正的请求。
1.3 关键配置(含凭证)
如果请求需要携带 Cookie,前后端都需要特殊设置:
- 前端 :
xhr.withCredentials = true(原生 XHR)或fetch(url, { credentials: 'include' })。 - 服务端 :
Access-Control-Allow-Credentials: true。注意 :此时Access-Control-Allow-Origin不能 设为*,必须指定明确的域名。
适用场景 :绝大多数前后端分离项目(尤其是需要 POST、PUT、DELETE 等复杂请求时)。优点 :支持所有 HTTP 方法,安全性高,功能完善。缺点:需要后端配合配置。
二、后端代理模式:Nginx 反向代理
如果无法或不想修改后端代码,Nginx 反向代理是最稳健的生产环境方案。其核心思路是:利用"服务端请求不受同源策略限制"这一特性,让 Nginx 充当"中间人"。
2.1 工作原理
浏览器不再直接请求目标服务器,而是请求同源的 Nginx 服务器。Nginx 将请求"转发"给目标服务器,拿到响应后再返回给浏览器。从浏览器视角看,请求和响应都在同一个源下,自然不存在跨域问题。
2.2 配置示例
nginx
server {
listen 80;
server_name your-domain.com;
# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
}
# 代理 API 请求
location /api/ {
proxy_pass http://backend-server:8080/api/; # 转发到后端真实地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
适用场景 :生产环境,特别是同一域名下需要代理多个后端服务时。优点 :对前端代码无侵入,配置灵活,可同时处理负载均衡、缓存等。缺点:需要额外部署和维护 Nginx 服务器,增加运维成本。
三、开发环境专用:Webpack / Vite 代理
在本地开发时,我们常通过 webpack-dev-server 或 Vite 的 proxy 功能解决跨域。它本质上是轻量级的本地反向代理,原理与 Nginx 一致,但仅限于开发环境。
在 vue.config.js 或 vite.config.js 中配置:
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://backend-server:8080', // 后端地址
changeOrigin: true, // 修改请求头中的 Origin
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
适用场景 :本地开发调试。优点 :配置简单,前端独立完成,无需后端参与。缺点:仅限开发环境,代码部署到生产仍需其他方案。
四、古典方案与特殊场景:JSONP 与 postMessage
4.1 JSONP:仅限 GET 的"取巧"方案
JSONP 利用 <script> 标签不受同源策略限制的特性,通过 src 发送请求,后端返回一个函数调用的 JavaScript 代码,浏览器收到后立即执行,从而拿到数据。
缺点 明显:仅支持 GET 请求,且存在 XSS 安全风险。如今除了一些老旧接口,已不推荐使用。
示例:
javascript
// 前端
function handleData(data) { console.log(data); }
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleData';
document.body.appendChild(script);
// 后端返回:handleData({ name: 'test' })
4.2 postMessage:页面间通信的桥梁
postMessage 是 HTML5 提供的 API,专门用于不同窗口或 iframe 之间的安全跨域通信。
适用场景 :页面嵌入了跨域的 iframe,或需要与通过 window.open 打开的跨域窗口通信。优点 :功能强大,双向通信,安全性可控(可校验 origin)。
五、总结:如何选择?
| 方案 | 主导方 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| CORS | 服务端 | 生产环境首选,通用跨域请求 | 标准、安全、支持所有请求类型 | 需后端配置 |
| Nginx代理 | 运维/后端 | 生产环境,不便修改后端代码时 | 前端无感知,配置灵活,功能强大 | 需部署维护Nginx |
| Dev Proxy | 前端 | 本地开发环境 | 配置简单,前端独立解决 | 仅限开发环境 |
| JSONP | 前端/后端 | 仅兼容老旧浏览器或只能GET的场景 | 兼容性极好 | 仅支持GET,有安全隐患 |
| postMessage | 前端 | iframe / 新窗口 跨域通信 | 专用于页面间通信,安全可控 | 不适用于AJAX请求 |
在实际开发中,最经典的组合是:开发环境使用 Webpack/Vite 代理,生产环境使用 CORS(配合 Nginx 代理作为兜底)。理解每一种方案的底层原理,能帮助我们在遇到跨域报错时,快速定位问题根源,而不是盲目地复制粘贴配置代码。