解决跨域问题有哪些常见方案?(CORS、JSONP、Nginx反代等)
跨域问题源于浏览器的同源策略(Same-Origin Policy),即协议、域名、端口必须完全一致。
以下是目前最常见、最实用的跨域解决方案,按推荐程度和使用场景分类:
一、 主流推荐方案
1. CORS (Cross-Origin Resource Sharing) —— 最标准
这是 W3C 的标准方案,也是目前后端最常用的方式。它允许浏览器向跨源服务器发出 XMLHttpRequest 请求。
- 原理:后端在响应头中设置
Access-Control-Allow-Origin等字段,告诉浏览器“我允许这个域名访问我”。 - 实现:
后端(以 Node.js/Express 为例)设置响应头:javascriptres.header("Access-Control-Allow-Origin", "http://your-frontend-domain.com"); // 或 * (不推荐带Cookie时用*) res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); - 优点:支持所有 HTTP 请求类型(GET, POST, PUT 等);安全性高。
- 缺点:需要后端支持;IE9 以下不支持(现在基本可忽略)。
- 注意:对于复杂请求(如带自定义 Header 或非简单 Content-Type),浏览器会先发送一个
OPTIONS预检请求。
2. Nginx 反向代理 —— 生产环境最常用
不需要后端改代码,运维在服务器网关层解决。
- 原理:同源策略是浏览器的限制,服务器与服务器之间通信没有跨域限制。
浏览器访问 Nginx(同源),Nginx 转发请求给后端服务器,再把结果返回给浏览器。 - 实现:
修改nginx.conf:plaintextserver { listen 80; server_name www.client.com; location /api/ { # 浏览器访问 http://www.client.com/api/user # Nginx 转发给 http://www.server.com/api/user proxy_pass http://www.server.com/; } } - 优点:后端无需改动;配置简单;安全性好(隐藏了真实后端地址)。
- 缺点:需要有 Nginx 服务器配置权限。
3. Node.js 中间件代理 (Webpack/Vite Proxy) —— 开发环境最常用
在 Vue、React 本地开发时,我们经常遇到跨域,这时候不需要后端配合,也不需要装 Nginx。
- 原理:本地启动了一个 Node.js 服务器(Dev Server),利用
http-proxy-middleware插件将请求转发给后端。原理同 Nginx。 - 实现:
- Vue (vue.config.js):javascript
module.exports = { devServer: { proxy: { '/api': { target: 'http://www.server.com', changeOrigin: true, pathRewrite: { '^/api': '' } } } } } - Vite (vite.config.ts): 类似配置。
- Vue (vue.config.js):
- 注意:这只在本地开发环境生效。打包上线后,通常需要配合 Nginx 反向代理使用。
二、 历史/特定场景方案
4. JSONP (JSON with Padding) —— 经典但过时
- 原理:利用
<script>标签没有跨域限制的漏洞。前端定义一个回调函数,通过 script 标签请求后端,后端返回一个函数调用,并将数据作为参数传入。 - 实现:
- 前端:
<script src="http://api.com?callback=handleData"></script> - 后端:返回字符串
handleData({ "name": "test" })
- 前端:
- 优点:兼容性极好(支持老版本 IE)。
- 缺点:只支持 GET 请求;不安全(容易遭受 XSS 攻击);调试困难。
- 现状:除了老旧系统维护,现代开发基本不再使用。
5. WebSocket
- 原理:WebSocket 是一种持久化协议。虽然它建立在 TCP 之上,但它本身不受同源策略限制。
- 实现:使用
Socket.io或原生WebSocket。 - 注意:虽然浏览器不限制,但后端通常会验证
Origin头部来防止 CSRF 攻击。
三、 窗口/Iframe 通信方案 (非接口请求)
如果你的跨域需求不是“请求接口”,而是“页面 A 和 iframe 中的页面 B 通信”,则使用以下方案:
6. postMessage
HTML5 引入的 API,专门用于不同窗口(Window)、Iframe 之间的跨域通信。
- 发送方:
otherWindow.postMessage(message, targetOrigin) - 接收方:监听
message事件。javascriptwindow.addEventListener("message", function(event) { if (event.origin !== "http://trusted.com") return; // 安全检查 console.log(event.data); });
7. document.domain (仅限主域相同)
- 场景:
a.test.com和b.test.com。 - 方法:两个页面都设置
document.domain = 'test.com'。 - 现状:已废弃。现代浏览器(如 Chrome 115+)默认禁用了修改
document.domain,不推荐使用。
总结:我该用哪个?
- 日常开发接口请求:
- 本地开发:使用 Webpack/Vite Proxy。
- 线上部署:使用 Nginx 反向代理。
- 后端愿意配合:直接用 CORS,这是最标准的方法。
- 需要实时通讯:使用 WebSocket。
- Iframe 通信:使用 postMessage。
- 必须兼容 IE9 以下且只有 GET 请求:无奈使用 JSONP。