前端跨域请求实现方案:原理、代码与场景选型
跨域的核心原因是 浏览器同源策略(协议、域名、端口三者必须一致,否则限制资源访问)。前端实现跨域的核心思路分两类:
- 客户端方案:前端直接配置(无需后端修改,如 JSONP、CORS 前端适配);
- 代理方案:通过中间层转发请求(避开浏览器同源限制,如开发环境代理、Nginx 代理)。
以下是 6 种主流跨域方案,从原理、代码实现到适用场景逐一解析,覆盖开发 / 生产全场景。
一、核心概念铺垫(先懂同源策略)
1. 同源判定标准
两个 URL 需同时满足以下 3 点才是 “同源”,否则为跨域:
- 协议相同(如
httpvshttps不同); - 域名相同(如
a.comvsb.a.com不同); - 端口相同(如
8080vs3000不同)。
2. 跨域限制的行为
- 无法通过
XMLHttpRequest/Fetch发起跨域 HTTP 请求; - 无法访问跨域 DOM(如 iframe 内容);
- 无法读取跨域 Cookie/LocalStorage。
本文重点解决 跨域 HTTP 请求 问题(前端最常用场景)。
二、方案 1:CORS(跨域资源共享)—— 推荐生产环境
CORS(Cross-Origin Resource Sharing)是 W3C 标准,也是最主流的跨域方案。核心是后端在响应头中添加允许跨域的配置,前端无需额外修改(或仅需简单适配),支持所有 HTTP 方法和复杂请求(如带 Cookie、自定义头)。
1. 原理
- 简单请求(GET/POST/HEAD,无自定义头、Content-Type 为
application/x-www-form-urlencoded/multipart/form-data/text/plain):直接发起请求,后端响应头返回Access-Control-Allow-Origin: 允许的域名即可; - 复杂请求(PUT/PATCH/DELETE、带自定义头、Content-Type 为
application/json):先发起 OPTIONS 预检请求,验证后端是否允许跨域,通过后再发起真实请求。
2. 前端实现(无需额外代码,仅需后端配合)
(1)基础跨域请求(无 Cookie)
// Fetch 示例(Axios 用法类似)
fetch('https://api.other-domain.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 触发 OPTIONS 预检
},
body: JSON.stringify({ name: '张三' }),
})
.then(res => res.json())
.then(data => console.log('跨域响应:', data))
.catch(err => console.log('跨域错误:', err));
(2)带 Cookie 的跨域请求(需前后端配合)
前端需设置 credentials: true,后端需返回 Access-Control-Allow-Credentials: true 且 Access-Control-Allow-Origin 不能为 *(需指定具体域名):
// Fetch 带 Cookie
fetch('https://api.other-domain.com/data', {
method: 'GET',
credentials: 'include', // 允许跨域携带 Cookie(Axios 中是 withCredentials: true)
})
.then(res => res.json())
.then(data => console.log(data));
// Axios 带 Cookie 全局配置
axios.defaults.withCredentials = true;
axios.get('https://api.other-domain.com/data')
.then(res => console.log(res.data));
3. 后端核心配置(以 Node.js/Express 为例)
const express = require('express');
const app = express();
// 全局跨域中间件(核心配置)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://your-frontend-domain.com'); // 允许的前端域名(不可为 * 若带 Cookie)
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); // 允许的方法
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
res.header('Access-Control-Allow-Credentials', 'true'); // 允许携带 Cookie
if (req.method === 'OPTIONS') {
res.sendStatus(204); // 预检请求直接返回成功
} else {
next();
}
});
4. 优点与适用场景
- 优点:标准方案、支持所有 HTTP 方法、支持复杂请求、安全性高;
- 适用场景:生产环境(前后端可控,如公司内部前后端分离项目、第三方接口开放 CORS)。
三、方案 2:JSONP —— 兼容旧浏览器(仅支持 GET)
JSONP 是 古老但兼容的跨域方案,核心利用 script 标签不受同源策略限制的特性,仅支持 GET 方法(因为 script 标签只能发起 GET 请求)。
1. 原理
- 前端创建
script标签,src指向跨域接口,同时传入回调函数名(如callback=handleData); - 后端接收回调函数名,返回
handleData(响应数据)格式的 JS 代码; - 前端
script标签加载后,执行回调函数,获取跨域数据。
2. 前端实现(原生 / 封装版)
(1)原生 JSONP 实现
// 1. 定义回调函数(接收跨域数据)
function handleJsonpData(data) {
console.log('JSONP 跨域数据:', data);
// 移除创建的 script 标签(避免冗余)
document.body.removeChild(scriptTag);
}
// 2. 创建 script 标签,发起跨域请求
const scriptTag = document.createElement('script');
// 跨域接口 + 回调函数参数(后端需解析 callback 参数)
scriptTag.src = 'https://api.other-domain.com/data?callback=handleJsonpData';
document.body.appendChild(scriptTag);
(2)封装 JSONP 工具函数(复用)
/**
* JSONP 跨域请求工具
* @param {string} url - 跨域接口地址
* @param {object} params - 请求参数(含 callback 可选)
* @returns {Promise} - 返回 Promise 便于异步处理
*/
function jsonp(url, params = {}) {
return new Promise((resolve, reject) => {
// 1. 生成唯一回调函数名(避免冲突)
const callbackName = `jsonpCallback_${Date.now()}`;
// 2. 拼接参数(添加回调函数名)
const paramsStr = Object.entries({ ...params, callback: callbackName })
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
// 3. 创建 script 标签
const scriptTag = document.createElement('script');
scriptTag.src = `${url}?${paramsStr}`;
scriptTag.onerror = (err) => {
reject(err);
document.body.removeChild(scriptTag);
};
// 4. 挂载回调函数到 window
window[callbackName] = (data) => {
resolve(data);
delete window[callbackName]; // 移除回调,避免内存泄漏
document.body.removeChild(scriptTag);
};
// 5. 插入页面发起请求
document.body.appendChild(scriptTag);
});
}
// 使用示例
jsonp('https://api.other-domain.com/data', { name: '张三' })
.then(data => console.log(data))
.catch(err => console.log(err));
3. 后端实现(Node.js/Express 为例)
app.get('/data', (req, res) => {
const callback = req.query.callback; // 获取前端传入的回调函数名
const data = { code: 200, message: 'JSONP 跨域成功', data: { name: '李四' } };
// 返回 JS 代码:回调函数包裹数据
res.send(`${callback}(${JSON.stringify(data)})`);
});
4. 优点与适用场景
- 优点:兼容旧浏览器(IE6+)、实现简单、无需后端复杂配置;
- 缺点:仅支持 GET 方法、不安全(可能遭受 XSS 攻击)、无法捕获 404/500 错误;
- 适用场景:兼容旧浏览器、第三方接口仅支持 JSONP(如部分老接口)。
四、方案 3:开发环境代理(Webpack/Vite/Vue CLI)—— 本地开发首选
前端开发时(如 Vue/React 项目),可通过构建工具的 代理服务器 转发跨域请求 —— 核心是 “前端请求本地代理服务器,代理服务器转发请求到目标跨域接口”,因代理服务器是后端服务,不受浏览器同源策略限制。
1. 原理
- 前端配置代理规则(如
/api开头的请求转发到https://api.other-domain.com); - 前端发起请求
http://localhost:8080/api/data(同源,无跨域限制); - 代理服务器接收请求,转发到
https://api.other-domain.com/data; - 代理服务器接收目标接口响应,返回给前端。
2. 不同构建工具的配置示例
(1)Vue CLI 配置(vue.config.js)
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
devServer: {
proxy: {
// 匹配所有以 /api 开头的请求
'/api': {
target: 'https://api.other-domain.com', // 目标跨域接口域名
changeOrigin: true, // 开启跨域(修改请求头的 Host 为目标域名)
pathRewrite: {
'^/api': '' // 路径重写(如 /api/data → 目标接口 /data)
},
// 若目标接口是 https,需配置(可选)
secure: false,
// 支持 WebSocket(可选)
ws: true
}
}
}
});
(2)Vite 配置(vite.config.js)
export default {
server: {
proxy: {
'/api': {
target: 'https://api.other-domain.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, '') // 路径重写
}
}
}
};
(3)Webpack 配置(webpack.config.js)
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.other-domain.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
3. 前端请求代码(无跨域感知)
// 无需考虑跨域,直接请求本地代理路径
axios.get('/api/data')
.then(res => console.log('代理跨域响应:', res.data));
// 等价于请求 https://api.other-domain.com/data
4. 优点与适用场景
- 优点:配置简单、前端无感知(无需修改请求代码)、支持所有 HTTP 方法、开发环境专属(不影响生产);
- 缺点:仅适用于开发环境(生产环境需用 Nginx 代理);
- 适用场景:本地开发(前后端分离项目,目标接口未开放 CORS)。
五、方案 4:Nginx 反向代理 —— 生产环境部署首选
生产环境中,前端项目通常通过 Nginx 部署,此时可利用 Nginx 作为 反向代理服务器 转发跨域请求,原理与开发环境代理一致,但用于生产环境(稳定、高性能)。
1. 原理
- 前端项目部署在 Nginx 服务器(
https://frontend-domain.com); - 前端请求
https://frontend-domain.com/api/data(同源,无跨域); - Nginx 接收请求,转发到目标跨域接口
https://api.other-domain.com/data; - Nginx 将目标接口响应返回给前端。
2. Nginx 配置示例(nginx.conf)
server {
listen 80;
server_name frontend-domain.com; # 前端项目域名
# 静态资源部署(前端打包后的 dist 目录)
location / {
root /usr/share/nginx/html; # 前端文件路径
index index.html;
try_files $uri $uri/ /index.html; # 解决 SPA 路由刷新 404
}
# 跨域代理配置(匹配 /api 开头的请求)
location /api {
proxy_pass https://api.other-domain.com; # 目标跨域接口域名
proxy_set_header Host $host; # 传递 Host 头
proxy_set_header X-Real-IP $remote_addr; # 传递真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # 传递协议(http/https)
# 路径重写(可选,若前端 /api 对应目标接口根路径)
rewrite ^/api/(.*)$ /$1 break;
}
}
3. 前端请求代码(与开发环境一致)
// 直接请求 Nginx 代理路径(同源)
axios.get('/api/data')
.then(res => console.log('Nginx 代理响应:', res.data));
4. 优点与适用场景
- 优点:高性能、稳定可靠、支持所有 HTTP 方法、生产环境推荐、无需前端修改代码;
- 缺点:需配置 Nginx 服务器(需运维支持);
- 适用场景:生产环境部署(前端项目通过 Nginx 发布,目标接口未开放 CORS)。
六、方案 5:PostMessage —— 跨域页面通信(非 HTTP 请求)
postMessage 是 HTML5 新增 API,用于 两个跨域页面之间的通信(如 iframe 父子页面、不同域名的窗口),并非直接发起 HTTP 请求,但可间接实现跨域数据交互(如 iframe 内页面请求数据后,通过 postMessage 传给父页面)。
1. 原理
- 发送方页面调用
window.postMessage(data, targetOrigin)发送数据,指定接收方域名; - 接收方页面监听
message事件,验证发送方域名(避免恶意攻击),接收数据。
2. 代码示例(父页面 + 跨域 iframe 页面)
(1)父页面(https://parent-domain.com)
<!-- 嵌入跨域 iframe -->
<iframe id="crossIframe" src="https://child-domain.com/iframe.html" width="500" height="300"></iframe>
<script>
// 1. 向 iframe 发送消息(如请求数据)
const iframe = document.getElementById('crossIframe');
iframe.onload = () => {
// 第二个参数指定接收方域名(* 表示允许所有域名,不安全,建议指定具体域名)
iframe.contentWindow.postMessage({ type: 'GET_DATA', params: { name: '张三' } }, 'https://child-domain.com');
};
// 2. 监听 iframe 返回的消息(跨域数据)
window.addEventListener('message', (e) => {
// 验证发送方域名(安全校验,必须加!)
if (e.origin !== 'https://child-domain.com') return;
console.log('父页面接收跨域数据:', e.data);
}, false);
</script>
(2)iframe 页面(https://child-domain.com/iframe.html)
// 1. 监听父页面消息
window.addEventListener('message', (e) => {
// 验证发送方域名
if (e.origin !== 'https://parent-domain.com') return;
// 接收父页面请求参数
const { type, params } = e.data;
if (type === 'GET_DATA') {
// 2. 发起 HTTP 请求(iframe 与目标接口同源,无跨域)
fetch('https://child-domain.com/api/data', {
method: 'POST',
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' }
})
.then(res => res.json())
.then(data => {
// 3. 将数据返回给父页面
e.source.postMessage({ code: 200, data }, e.origin);
});
}
}, false);
3. 优点与适用场景
- 优点:支持跨域页面通信、可传递复杂数据;
- 缺点:需两个页面配合、仅适用于页面间交互(非直接 HTTP 请求);
- 适用场景:跨域 iframe 通信、不同域名窗口间数据交互(如 OAuth 授权回调)。
七、方案 6:WebSocket —— 跨域实时通信
WebSocket 是 全双工实时通信协议,不受同源策略限制(握手阶段基于 HTTP,但连接建立后是独立协议),适用于实时通信场景(如聊天、实时数据推送)。
1. 原理
- 前端通过
new WebSocket('wss://api.other-domain.com/ws')发起连接(握手阶段会携带Origin头); - 后端验证
Origin允许跨域后,建立 WebSocket 连接; - 连接建立后,前后端可双向实时发送数据,无跨域限制。
2. 前端实现代码
// 建立 WebSocket 连接(wss 是加密协议,ws 是明文协议)
const ws = new WebSocket('wss://api.other-domain.com/ws');
// 连接成功回调
ws.onopen = () => {
console.log('WebSocket 连接成功');
// 发送数据给后端
ws.send(JSON.stringify({ type: 'JOIN', userId: 123 }));
};
// 接收后端数据
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log('WebSocket 接收跨域数据:', data);
};
// 连接关闭回调
ws.onclose = (e) => {
console.log('WebSocket 连接关闭:', e.code, e.reason);
};
// 错误回调
ws.onerror = (err) => {
console.log('WebSocket 错误:', err);
};
3. 后端配置(Node.js + ws 库为例)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
// 验证 Origin(允许跨域域名)
const origin = req.headers.origin;
const allowedOrigins = ['https://frontend-domain.com'];
if (!allowedOrigins.includes(origin)) {
ws.close(403, 'Origin not allowed');
return;
}
// 接收前端数据
ws.on('message', (message) => {
console.log('收到前端消息:', message.toString());
// 发送数据给前端
ws.send(JSON.stringify({ type: 'MESSAGE', content: '实时推送数据' }));
});
});
4. 优点与适用场景
- 优点:实时性高、全双工通信、不受同源策略限制;
- 缺点:需后端支持 WebSocket 协议、不适用于普通 HTTP 请求;
- 适用场景:实时聊天、实时数据推送(如股票行情、监控数据)、协作工具。
八、6 种跨域方案对比与选型建议
| 方案 | 支持方法 | 浏览器兼容 | 安全性 | 适用场景 | 核心优势 |
|---|---|---|---|---|---|
| CORS | 所有 | 现代浏览器 | 高 | 生产环境(前后端可控) | 标准方案、支持复杂请求 |
| JSONP | 仅 GET | 所有(含 IE) | 低 | 兼容旧浏览器、第三方老接口 | 实现简单、无后端复杂配置 |
| 开发环境代理 | 所有 | 开发环境 | 中 | 本地开发(Vue/React/Vite 项目) | 前端无感知、配置简单 |
| Nginx 代理 | 所有 | 生产环境 | 高 | 生产环境部署(Nginx 托管前端) | 高性能、稳定可靠 |
| PostMessage | - | 现代浏览器 | 中 | 跨域页面 /iframe 通信 | 页面间数据交互、无需 HTTP 请求 |
| WebSocket | - | 现代浏览器 | 高 | 实时通信(聊天、实时推送) | 全双工、低延迟 |
选型优先级(按场景)
- 本地开发:开发环境代理(Webpack/Vite/Vue CLI);
- 生产环境(前后端可控) :CORS(首选)或 Nginx 代理;
- 生产环境(第三方接口无 CORS) :Nginx 代理;
- 兼容旧浏览器:JSONP;
- 跨域页面交互:PostMessage;
- 实时通信:WebSocket。
九、常见误区(避坑指南)
1. CORS 配置 Access-Control-Allow-Origin: * 仍跨域
- 原因:带 Cookie 的跨域请求,
Access-Control-Allow-Origin不能为*,需指定具体域名; - 解决:后端改为
Access-Control-Allow-Origin: 前端域名+Access-Control-Allow-Credentials: true。
2. 开发环境代理生效,生产环境跨域
- 原因:生产环境未配置 Nginx 代理或 CORS,仅开发环境有代理;
- 解决:生产环境部署 Nginx 代理,或让后端开启 CORS。
3. JSONP 无法发起 POST 请求
- 原因:JSONP 基于
script标签,仅支持 GET 方法; - 解决:改用 CORS 或代理方案。
4. PostMessage 未验证 origin 导致安全风险
- 原因:未验证发送方域名,可能接收恶意网站的消息;
- 解决:在
message事件中添加if (e.origin !== '允许的域名') return。
总结
前端跨域的核心是 “避开浏览器同源策略限制”,选择方案时需优先考虑 场景适配性:
- 开发环境:优先用构建工具代理(简单高效);
- 生产环境:优先用 CORS(标准)或 Nginx 代理(高性能);
- 特殊场景:旧浏览器用 JSONP、页面通信用 PostMessage、实时通信用 WebSocket。
大多数前后端分离项目的最终选型是:开发环境代理 + 生产环境 CORS/Nginx 代理,兼顾开发效率和生产稳定性。