前言
前后端分离与浏览器安全限制使得跨域成为前端开发绕不过的问题。目前跨域对开发最大的影响是本地开发请求服务器资源,生产环境多数已经通过CORS配置好。跨域是前后端对接数据的第一道坎,也是比较能考验web开发合作经验的一个问题。
什么是跨域(Cross-Origin)?
跨域(Cross-Origin)是指浏览器出于安全考虑,限制了来自不同 源(Origin) 的 JavaScript 代码访问资源的权限。
1. 什么是“同源”?
浏览器的 同源策略(Same-Origin Policy, SOP) 规定,只有当两个 URL 的 协议(Protocol)、域名(Domain)、端口(Port) 完全一致时,才属于同源,否则就是跨域。
URL A | URL B | 是否同源 | 原因 |
---|---|---|---|
https://example.com | https://example.com | ✅ 同源 | 协议、域名、端口相同 |
https://example.com | http://example.com | ❌ 跨域 | 协议不同(HTTPS vs HTTP) |
https://example.com | https://api.example.com | ❌ 跨域 | 子域名不同 |
https://example.com:80 | https://example.com:443 | ❌ 跨域 | 端口不同 |
2. 跨域的限制范围
跨域限制主要影响以下操作:
- AJAX / Fetch 请求(XMLHttpRequest、axios、fetch API)
- Web 字体(@font-face)
- Canvas 绘制跨域图片
- Web Storage / IndexedDB(部分浏览器限制)
但以下情况 不受跨域限制:
<img>
、<script>
、<link>
、<iframe>
等标签的src
或href
属性(但 JavaScript 无法直接读取返回内容)。- WebSocket(不受 SOP 限制,但可能受 CORS 影响)!!!!!!。
3. 为什么要有跨域限制?
- 安全考虑:防止恶意网站窃取用户数据(如 Cookie、LocalStorage)。
- 防止 CSRF(跨站请求伪造):避免攻击者诱导用户发送恶意请求。
4. 常见的跨域场景
- 前端运行在
http://localhost:3000
,但请求后端 APIhttps://api.example.com
。 - 主站
https://www.example.com
请求子域https://api.example.com
。 - 使用 CDN 资源时,不同域名导致跨域(如
https://cdn.example.com
)。
前端解决跨域解决方案
1. CORS(跨域资源共享)最常用
- 原理:服务器设置响应头(如
Access-Control-Allow-Origin
)允许特定域访问资源。 - 适用场景:前后端分离项目,后端可控。
- 示例:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
2. JSONP(JSON with Padding)
- 原理:利用
<script>
标签不受同源策略限制的特性,通过动态创建脚本获取数据。 - 缺点:仅支持 GET 请求,安全性较低。
- 示例:
function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
3. 代理服务器(Proxy)
- 原理:前端请求同域代理服务器,由代理服务器转发请求到目标服务器。
- 实现方式:
- 开发环境:使用 webpack-dev-server、Vite 或 http-proxy-middleware。
// webpack.config.js
devServer: {
// 代理所有以 /api 开头的请求
'/api': {
target: 'http://your-backend-server.com', // 目标服务器地址
changeOrigin: true, // 改变请求头中的host为目标URL
pathRewrite: {
'^/api': '' // 重写路径,去掉/api前缀
},
secure: false, // 如果是https接口,需要配置这个参数
// 其他可选配置
// headers: {
// 'X-Custom-Header': 'foobar'
// }
}
}
// vite.config.js
server: {
proxy: {
// 字符串简写写法
'/foo': 'http://localhost:4567',
// 完整写法
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
ws: true, // 是否代理 WebSocket(默认为 true)
rewrite: (path) => path.replace(/^/api/, ''),
// 更多配置
// configure: (proxy, options) => {
// // proxy 是 'http-proxy' 的实例
// }
},
// 正则表达式写法
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^/fallback/, '')
}
}
}
生产环境:通过 Nginx 反向代理。
location /api {
proxy_pass https://api.example.com;
}
4. WebSocket
- 原理:WebSocket 协议不受同源策略限制,适用于实时通信。
- 示例:
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = (event) => {
console.log(event.data);
};
5. postMessage
- 原理:通过
window.postMessage
实现不同窗口(如 iframe、弹窗)间的跨域通信。 - 示例:
// 发送方
window.parent.postMessage('Hello', 'https://target.com');
// 接收方
window.addEventListener('message', (event) => {
if (event.origin === 'https://source.com') {
console.log(event.data);
}
});
6. 修改 document.domain
- 原理:将子域和父域的
document.domain
设置为相同值(仅适用于主域相同的情况)。 - 示例:
// a.example.com 和 b.example.com
document.domain = 'example.com';
7. 跨域资源共享的其他头部
- 服务器可设置更多 CORS 头部以细化控制:
Access-Control-Allow-Headers
: 允许的自定义头。Access-Control-Allow-Credentials
: 是否允许携带 Cookie。
8. 浏览器扩展或插件
- 临时解决方案:如 Chrome 的跨域插件(
Allow CORS
),或启动浏览器时禁用安全策略(仅开发用):
chrome.exe --disable-web-security --user-data-dir=/tmp
9. 服务端转发(BFF模式)
- 后端提供一个统一接口聚合第三方服务,前端只与同域后端交互。
注意事项:
- 安全性:确保 CORS 或代理配置不会开放敏感资源。
- 生产环境:优先使用 CORS 或代理,避免 JSONP 等不安全方案。
根据项目需求(如开发环境调试、生产部署、实时通信等)选择合适的方案。