一、先理解核心概念:什么是跨域?
跨域是浏览器的同源策略导致的安全限制:当请求的协议(http/https)、域名、端口三者任意一个与当前页面不一致时,就会触发跨域拦截。比如:
http://localhost:3000访问http://localhost:8080(端口不同)http://www.a.com访问http://api.a.com(子域名不同)http://a.com访问https://a.com(协议不同)
二、跨域请求的主流解决方案
1. CORS(跨域资源共享)- 最推荐的方案
核心原理:后端在响应头中添加允许跨域的配置,明确告知浏览器哪些域名、请求方法、头信息可以访问资源,是 W3C 标准,也是现代浏览器最支持的方案。
适用场景:前后端分离项目(Vue/React+Node/Java/PHP 等),后端可修改的场景。
实现示例:
-
后端配置(以 Node.js/Express 为例) :
const express = require('express'); const app = express(); // 全局跨域中间件 app.use((req, res, next) => { // 允许指定域名跨域(* 表示允许所有,生产环境不推荐) res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000'); // 允许的请求方法 res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); // 允许的自定义请求头 res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许携带cookie(需前后端同时配置) res.setHeader('Access-Control-Allow-Credentials', 'true'); // 预检请求(OPTIONS)的缓存时间,避免重复发送 res.setHeader('Access-Control-Max-Age', '86400'); // 处理预检请求(OPTIONS) if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // 接口示例 app.get('/api/data', (req, res) => { res.json({ code: 200, data: '跨域成功' }); }); app.listen(8080, () => console.log('后端服务运行在8080端口')); -
前端请求(以 Axios 为例) :
import axios from 'axios'; axios({ url: 'http://localhost:8080/api/data', method: 'GET', withCredentials: true, // 如需携带cookie,必须开启 }).then(res => console.log(res.data));
2. 代理服务器 - 开发环境首选
核心原理:浏览器有跨域限制,但服务器之间没有。通过本地开发服务器(如 Webpack Dev Server、Vite)做代理,将前端请求转发到目标后端,规避跨域问题。
适用场景:本地开发阶段(生产环境需配置 Nginx 代理)。
实现示例:
-
Vite 配置(vite.config.js) :
export default { server: { proxy: { // 匹配以/api开头的请求 '/api': { target: 'http://localhost:8080', // 目标后端地址 changeOrigin: true, // 开启跨域代理 // rewrite: (path) => path.replace(/^/api/, ''), // 可选:去掉/api前缀 }, }, }, }; -
前端请求(无需写完整域名) :
axios.get('/api/data').then(res => console.log(res.data)); -
生产环境 Nginx 配置:
server { listen 80; server_name www.frontend.com; # 代理后端接口 location /api/ { proxy_pass http://localhost:8080/api/; # 转发到后端地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 前端静态资源 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 适配SPA路由 } }
3. JSONP - 兼容老旧浏览器
核心原理:利用<script>标签不受同源策略限制的特性,通过动态创建 script 标签请求后端接口,后端返回一段带回调函数的 JS 代码,前端执行回调获取数据。
局限:仅支持 GET 请求,安全性较低(可能存在 XSS 风险),仅推荐兼容老旧浏览器时使用。
实现示例:
-
后端(Node.js/Express) :
app.get('/api/jsonp', (req, res) => { const { callback } = req.query; // 获取前端传的回调函数名 const data = { code: 200, data: 'JSONP跨域成功' }; // 返回回调函数+数据(格式:callback(data)) res.send(`${callback}(${JSON.stringify(data)})`); }); -
前端:
function jsonpRequest(url, callbackName) { return new Promise((resolve) => { // 1. 创建script标签 const script = document.createElement('script'); script.src = `${url}?callback=${callbackName}`; // 2. 定义回调函数 window[callbackName] = (data) => { resolve(data); document.body.removeChild(script); // 执行完移除script delete window[callbackName]; // 清理全局函数 }; // 3. 插入到页面 document.body.appendChild(script); }); } // 调用 jsonpRequest('http://localhost:8080/api/jsonp', 'handleJsonp').then(res => { console.log(res); });
4. postMessage - 页面间跨域通信
核心原理:HTML5 新增的 API,允许不同域名的页面(如 iframe、多窗口)之间安全地传递数据,不是用于 AJAX 请求,而是页面间通信。
适用场景:iframe 嵌套跨域页面、多窗口通信。
实现示例:
-
父页面(a.com) :
<iframe id="iframe" src="http://b.com"></iframe> <script> const iframe = document.getElementById('iframe'); // 等子页面加载完成后发送消息 iframe.onload = () => { iframe.contentWindow.postMessage( { type: 'data', content: '来自a.com的消息' }, 'http://b.com' // 只允许发送给该域名,* 表示所有 ); }; // 接收子页面的回复 window.addEventListener('message', (e) => { if (e.origin === 'http://b.com') { // 验证来源,防止恶意消息 console.log('收到回复:', e.data); } }); </script> -
子页面(b.com) :
// 接收父页面消息 window.addEventListener('message', (e) => { if (e.origin === 'http://a.com') { console.log('收到消息:', e.data); // 回复父页面 e.source.postMessage({ type: 'reply', content: '已收到消息' }, e.origin); } });
5. 其他补充方案
- WebSocket:不受同源策略限制,适用于实时通信(如聊天、推送),协议是 ws/wss,而非 http。
- document.domain:仅适用于主域名相同、子域名不同的场景(如a.com和api.a.com),需双方页面设置
document.domain = 'a.com',但兼容性差,不推荐。 - CORS 的预检请求:PUT/DELETE/ 带自定义头的 POST 请求会先发送 OPTIONS 预检请求,后端需正确处理(如上面 CORS 示例中的 OPTIONS 逻辑)。
总结
- 核心推荐方案:开发环境用代理服务器,生产环境用CORS(后端配置)+ Nginx 代理,覆盖 99% 的场景;
- 特殊场景:老旧浏览器兼容用JSONP,页面间通信用postMessage,实时通信用WebSocket;
- 关键原则:跨域的本质是浏览器的限制,服务器之间无跨域,因此 “代理” 和 “后端配置 CORS” 是最根本的解决思路。
选择方案时优先看场景:能改后端就用 CORS,开发阶段用代理,仅 GET 请求且需兼容老浏览器才用 JSONP。