工作扫盲之跨域问题
约 1745 字大约 6 分钟
2025-10-07
今天和后端联调遇到跨域问题,明明接口在 Postman 里能通,到浏览器就报错CORS。晚上无聊遂了解一下跨域问题并作记录。
OK Let's Dive in!🤿
核心概念
其实跨域不是 “bug”,是浏览器的 “安全洁癖” 导致的。先把两个基础概念掰明白,后面看解决方案就顺理成章了。
1. 什么是 “同源”?—— 跨域的判断标准
浏览器判断 “要不要拦截这个请求”,全看 “请求来源” 和 “请求目标” 是不是 “同源”。
“同源” 有三个硬性标准,必须完全一致:
协议:比如
http
和https
算不同(我之前试过用http
调后端的https
接口,直接跨域);域名:比如
www.abc.com
和api.abc.com
算不同(子域名不一样也不行),abc.com
和def.com
更不用说;端口:比如
localhost:8080
(前端本地)和localhost:3000
(后端本地)算不同(这是联调最常遇到的情况!)。
前端地址 | 后端接口地址 | 是否跨域 | 原因 |
---|---|---|---|
http://localhost:8080 | http://localhost:3000 | 是 | 端口不同 |
https://www.abc.com | http://www.abc.com | 是 | 协议不同 |
http://www.abc.com | http://api.abc.com | 是 | 子域名不同 |
http://www.abc.com:80 | http://www.abc.com | 否 | 80 端口是 http 默认值 |
2. 为什么会有 “跨域拦截”?—— 浏览器的同源策略
这里要澄清一个关键误区:跨域时,请求其实发出去了,后端也会返回响应,但浏览器会 “半路拦截” —— 因为浏览器有个 “同源策略” 的安全机制,怕你被钓鱼。
举个例子:你刚在银行网站(https://bank.com
)登录,Cookie 里存了你的登录信息;这时你不小心打开一个钓鱼网站(https://fake.com
),如果没有同源策略,钓鱼网站的 JS 就能偷偷调用银行的接口,用你的 Cookie 获取账户信息 —— 这就出事了!
所以同源策略的本质是:保护用户数据安全,防止恶意网站通过 JS 窃取其他网站的资源(比如 Cookie、LocalStorage) 。
而 Postman 能通,是因为 Postman 不是浏览器,没有同源策略的限制 —— 它不管 “来源”,只要接口能通就返回数据,这也是我们联调时 “明明接口好的,前端却报错” 的核心原因。
解决方案
搞懂原理后,解决方案就很清晰了:前端能做的是 “绕开拦截”,但只能临时用;后端做的是 “告诉浏览器允许访问”,能彻底解决问题。
1. 前端解决方案
这些方案大多是 “治标不治本”,生产环境基本没用,但开发时能帮我们快速联调。
原理很简单:前端不直接调后端接口,而是先把请求发给 “本地代理服务器”(比如 Vue/React 脚手架自带的代理),再由代理服务器转发给后端 —— 因为代理服务器是 “服务器之间的通信”,不受浏览器同源策略限制。
举个 Vue 项目的例子(React 类似):
在 vue.config.js
里加一段配置:
module.exports = {
devServer: {
proxy: {
// 匹配所有以 /api 开头的请求
'/api': {
target: 'http://localhost:3000', // 后端接口地址
changeOrigin: true, // 允许跨域(关键)
pathRewrite: {
'^/api': '' // 比如前端发 /api/user,实际转发到 http://localhost:3000/user
}
}
}
}
}
配置完重启项目,前端直接调 /api/user
就行,不用写完整的后端地址 —— 亲测有效,我今天联调就是这么临时解决的。
注意:这个方案只适用于开发环境!打包上线后,前端没有脚手架的代理了,还是会跨域,得靠后端。
(2)iframe + postMessage:跨域传递数据(特殊场景)
如果需要在两个跨域的页面之间传递数据(比如父页面和 iframe 子页面),可以用 postMessage
。
比如父页面(http://a.com
)给子页面(http://b.com
)发消息:
// 父页面:获取iframe元素,发送消息
const iframe = document.getElementById('myIframe');
iframe.onload = function() {
// 参数1:要发的数据,参数2:目标域名(\*表示所有域名,生产环境建议写具体域名)
iframe.contentWindow.postMessage('我是父页面的消息', 'http://b.com');
};
// 父页面接收子页面的消息
window.addEventListener('message', function(e) {
// 验证来源,防止恶意消息
if (e.origin === 'http://b.com') {
console.log('父页面收到子页面消息:', e.data);
}
});
子页面(http://b.com
)接收并回复:
// 子页面接收父页面消息
window.addEventListener('message', function(e) {
// 验证来源,防止恶意消息
if (e.origin === 'http://a.com') {
console.log('子页面收到父页面消息:', e.data);
// 子页面给父页面回复
e.source.postMessage('我收到消息啦!', 'http://a.com');
}
});
这个方案主要用于 “页面间通信”,不是调接口,属于特殊场景补充。
2. 后端解决方案
前端方案只是 “临时方案”,要让线上项目不跨域,必须靠后端配置 —— 核心是告诉浏览器:“这个前端域名是我允许的,你别拦截它的请求。”
(1)配置 CORS:最推荐,支持所有请求方式
CORS(跨域资源共享)是官方标准,原理是后端在响应头里加一些 “允许跨域” 的字段,浏览器看到这些字段就会放行。
重点配置的响应头:
Access-Control-Allow-Origin
:允许访问的前端域名(比如http://localhost:8080
,生产环境写线上域名,不能随便用*
);Access-Control-Allow-Methods
:允许的请求方式(GET、POST、PUT、DELETE 等);Access-Control-Allow-Credentials
:是否允许带 Cookie(如果前端要传 Cookie,这个必须设为true
);Access-Control-Allow-Headers
:允许的自定义请求头(比如前端传token
时,要加这个配置)。
(2)服务端代理:后端转发请求(特殊场景)
如果前端要调用 “不支持 CORS” 的第三方接口(比如某些老的公共 API),可以让后端帮忙转发:前端调自己的后端,自己的后端再调第三方接口,最后把数据返回给前端。
比如 Node.js(Express)转发示例:
const express = require('express');
const axios = require('axios');
const app = express();
// 前端调这个接口,后端转发到第三方API
app.get('/api/third-party', async (req, res) => {
try {
// 后端调第三方接口(不受同源策略限制)
const response = await axios.get('https://third-party-api.com/data');
// 把第三方的数据返回给前端
res.send(response.data);
} catch (error) {
res.status(500).send('转发失败');
}
});
app.listen(3000, () => {
console.log('后端服务启动在3000端口');
});
这个方案的好处是:前端不用管跨域,第三方接口的密钥也能存在后端(避免暴露在前端),安全性更高。
总结:一句话搞定跨域
开发环境:前端用 “代理”(Vue/React 脚手架配置)快速联调;
生产环境:后端配 “CORS”(核心是
Access-Control-Allow-Origin
设对域名);特殊场景:服务端代理(第三方接口)。
其实跨域不难,关键是搞懂 “浏览器为什么拦截”,再针对性解决 —— 下次再遇到,应该不会再卡半小时了😂。