跨域是 浏览器的安全限制机制 ,核心是:前端页面(A地址)通过 AJAX/fetch 等方式请求另一个不同"源"的接口(B地址)时,浏览器会拦截该请求,导致请求失败。这是开发中非常常见的问题,尤其前后端分离项目中几乎必然遇到。
跨域的核心限制是:阻止"前端通过脚本(AJAX/fetch/axios等)发起的跨源接口请求" ------ 简单说:
- 你可以直接在浏览器输入URL、通过
<a>标签跳转、iframe嵌入等方式,访问任意域名的页面(比如从http://a.com跳转到http://b.com的页面,完全没问题); - 但如果
http://a.com的页面里,通过JavaScript脚本请求http://b.com的接口(比如获取数据、提交表单),浏览器会触发跨域限制,拦截这个请求的响应。
一、先搞懂:什么是"同源"?什么是"跨域"?
"源"由 协议、域名、端口 三个部分组成,三者必须完全一致才叫"同源",只要有一个不同,就是"跨域"。
| 访问类型 | 是否受跨域限制? | 示例场景 |
|---|---|---|
| 页面访问(非脚本) | 不受限制 | 1. 直接输http://b.com/page打开页面; 2. a.com的页面用<a href="http://b.com/page">跳转; 3. a.com用<iframe src="http://b.com/page">嵌入页面 |
| 脚本发起的接口请求 | 受限制(跨域拦截) | 1. a.com的页面用axios.get("http://b.com/api/data"); 2. a.com的页面用fetch("http://b.com/api/submit")提交数据 |
举个例子(当前页面地址:http://localhost:8080):
| 目标接口地址 | 是否同源? | 跨域原因 |
|---|---|---|
http://localhost:8080/api |
是 | 协议、域名、端口完全一致 |
https://localhost:8080/api |
否 | 协议不同(http vs https) |
http://www.test.com/api |
否 | 域名不同(localhost vs test.com) |
http://localhost:8081/api |
否 | 端口不同(8080 vs 8081) |
http://test.localhost:8080/api |
否 | 子域名不同(无 vs test) |
举个直观例子
假设:
- 前端页面地址:
http://localhost:8080(A源); - 后端服务地址:
http://localhost:3000(B源,端口不同,跨域)。
场景1:直接访问B源的页面
- 浏览器输入
http://localhost:3000/page(B源的页面),能正常打开------不受跨域限制。
场景2:A源页面嵌入B源页面
http://localhost:8080的页面里写<iframe src="http://localhost:3000/page">,iframe能正常显示B源的页面------不受跨域限制。
场景3:A源页面脚本请求B源接口
http://localhost:8080的页面里写axios.get("http://localhost:3000/api/data"):- 没配置CORS:浏览器控制台报错"跨域",前端拿不到数据;
- 配置了CORS:浏览器放行响应,前端正常拿到数据。
二、为什么浏览器要限制"跨域"?(同源策略的目的)
同源策略是浏览器的核心安全机制,目的是 防止恶意网站窃取用户数据。
比如:你登录了银行网站(https://bank.com),浏览器会保存银行的登录 Cookie;此时你又打开了一个恶意网站(https://hacker.com),如果没有跨域限制,这个恶意网站就能通过 AJAX 请求银行的接口,利用你已登录的 Cookie 操作你的账户------这会导致严重的安全问题。
跨域限制本质是:阻止不同源的页面,随意访问另一个源的敏感数据(Cookie、LocalStorage、接口数据等) 。防止恶意网站通过脚本窃取用户数据,而"页面访问"和"脚本请求"的风险完全不同:
- 页面访问(非脚本) :只是单纯展示对方的页面,不会主动窃取你的数据(比如你跳转到
b.com,只是看b.com的内容,a.com的脚本无法通过这种跳转获取b.com的Cookie、接口数据); - 脚本请求(跨源) :如果没有限制,
a.com的恶意脚本可以:- 趁你登录
b.com(浏览器保存了b.com的登录Cookie),偷偷请求b.com的接口(比如查询你的余额、修改你的信息); - 直接获取
b.com接口返回的敏感数据(比如用户信息、订单),导致数据泄露。
- 趁你登录
所以浏览器的逻辑是:只拦截"脚本发起的跨源数据请求",不干预"单纯的页面访问" ------ 既保证安全,又不影响正常的页面跳转、嵌入等场景。
三、常见的跨域场景
- 前后端分离项目:前端(
http://localhost:8080)请求后端接口(http://localhost:3000)(端口不同); - 前端部署在静态服务器(
https://xxx-cdn.com),请求后端 API(https://xxx-api.com)(域名不同); - 本地开发(
http://127.0.0.1:8080)请求线上接口(https://api.xxx.com)(协议+域名+端口都不同)。
四、跨域解决方案(按常用度排序,从简单到复杂)
核心思路:跨域限制是浏览器的行为,不是服务器的限制------服务器之间互相请求是没有跨域问题的,所以解决方案要么让浏览器"放行",要么绕开浏览器的限制。
1. 最常用:CORS(跨域资源共享)------ 后端配置即可
CORS 是官方推荐 的跨域解决方案,本质是 后端在响应头中声明"允许哪些源访问",浏览器收到响应后,会验证该源是否在允许列表中,若在则放行请求。
实现方式(后端配置,前端无需修改):
以 Node.js(Express)为例,核心是设置响应头:
javascript
const express = require('express');
const app = express();
// 允许所有源跨域(开发环境可用,生产环境不推荐,建议指定具体域名)
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); // 允许的请求方法
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
next();
});
// 接口示例
app.get('/api/data', (req, res) => {
res.send({ code: 200, data: '跨域请求成功' });
});
app.listen(3000);
关键响应头说明:
Access-Control-Allow-Origin:指定允许跨域的源(*表示所有源,生产环境建议写具体域名,如https://xxx.com);Access-Control-Allow-Methods:允许的 HTTP 请求方法(GET/POST/PUT/DELETE 等);Access-Control-Allow-Headers:允许的请求头(如 Content-Type、Token 等);- 若需要传递 Cookie(如登录态):后端需额外设置
Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin不能为*(必须指定具体域名),前端请求时需设置withCredentials: true(Axios 中配置)。
优点:简单高效,支持所有 HTTP 方法,生产环境推荐;缺点:需要后端配合配置。
2. 开发环境专用:代理转发(前端配置)
开发时,前端本地启动的服务(如 http://localhost:8080)请求跨域接口,可通过 前端代理服务器转发请求------因为代理服务器是"服务器之间的请求",没有跨域限制。
实现方式(以 Vue/React 项目为例):
-
Vue 项目(vue.config.js):
javascriptmodule.exports = { devServer: { proxy: { // 匹配所有以 /api 开头的请求 '/api': { target: 'http://localhost:3000', // 后端接口的真实地址 changeOrigin: true, // 开启代理,模拟后端接口的源 pathRewrite: { '^/api': '' } // 去掉请求路径中的 /api(可选,根据后端接口路径调整) } } } }; -
React 项目(package.json):
json"proxy": "http://localhost:3000" // 直接指定后端接口地址(简单场景) // 复杂场景需用 http-proxy-middleware 配置
原理:
前端请求 http://localhost:8080/api/data → 代理服务器(前端本地服务)转发到 http://localhost:3000/data → 后端返回数据 → 代理服务器再把数据返回给前端。
浏览器看到的是"同源请求"(都是 localhost:8080),所以不拦截。
优点:前端独立配置,无需后端参与,开发环境必备;缺点:仅适用于开发环境,生产环境需另寻方案。
3. 兼容旧浏览器:JSONP
JSONP 是早期的跨域方案,利用 <script> 标签不受同源策略限制 的特性实现(<script> 可以加载任意域名的脚本)。
实现方式(前后端配合):
- 前端:创建
<script>标签,请求后端接口并传入回调函数名; - 后端:返回回调函数调用的脚本,将数据作为参数传入。
示例:
javascript
// 前端代码
function handleData(data) {
console.log('跨域数据:', data); // 接收后端返回的数据
}
// 创建 script 标签,发起请求
const script = document.createElement('script');
script.src = 'http://localhost:3000/api/jsonp?callback=handleData'; // 传入回调函数名
document.body.appendChild(script);
// 后端代码(Node.js)
app.get('/api/jsonp', (req, res) => {
const callback = req.query.callback; // 获取前端传入的回调函数名
const data = JSON.stringify({ code: 200, data: 'JSONP 跨域成功' });
res.send(`${callback}(${data})`); // 返回:handleData({"code":200,"data":"JSONP 跨域成功"})
});
优点:兼容 IE 等旧浏览器;缺点:仅支持 GET 请求,安全性较低(可能遭受 XSS 攻击),现在很少用。
4. 其他方案(适用于特殊场景):
- document.domain + iframe :仅适用于"主域名相同、子域名不同"的场景(如
a.xxx.com和b.xxx.com),通过设置document.domain = 'xxx.com'实现同源; - postMessage :适用于两个不同源的页面之间通信(如 iframe 嵌套场景),通过
window.postMessage()发送数据,window.addEventListener('message')接收数据; - Nginx 反向代理:生产环境常用,通过 Nginx 服务器转发前端请求到后端接口,本质和"前端代理"原理一致,但部署在服务器端(适合生产环境)。
示例(Nginx 配置):
nginx
server {
listen 80;
server_name localhost;
# 前端页面请求 /api 时,转发到后端接口
location /api {
proxy_pass http://localhost:3000; # 后端接口地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 静态资源(前端页面)
location / {
root /usr/share/nginx/html;
index index.html;
}
}
五、跨域避坑要点
- 生产环境不要用
Access-Control-Allow-Origin: *+ 传递 Cookie,会导致跨域失败(浏览器不允许); - 跨域请求可能会触发"预检请求(OPTIONS)":当请求方法不是 GET/POST,或请求头包含自定义字段(如 Token)时,浏览器会先发送 OPTIONS 请求验证后端是否允许跨域,后端需正确响应 OPTIONS 请求;
- JSONP 仅支持 GET 请求,且要注意 XSS 防护(后端需过滤回调函数名和返回数据);
- 代理转发仅适用于开发环境,生产环境建议用 CORS 或 Nginx 反向代理。
六、关键误区澄清:"跨域拦截"不是"服务器拒绝访问"
很多人会误以为"跨域就是服务器不让访问",但实际情况是:
- 脚本发起的跨源请求 已经发送到服务器了(服务器会处理并返回响应);
- 是 浏览器在接收响应后,发现不符合CORS规则,才主动拦截了这个响应,不让前端脚本获取数据(前端会看到"Access-Control-Allow-Origin"相关的错误)。
比如之前你用Express/Spring Boot配置的跨域服务:
- 没配置CORS时,前端请求
http://localhost:3000/api/data,服务器已经返回了{code:200, data:"跨域请求成功"},但浏览器拦截了这个响应; - 配置CORS后,浏览器验证响应头的
Access-Control-Allow-Origin符合规则,才放行响应,让前端脚本拿到数据。
总结
- 开发环境:优先用 前端代理转发(Vue/React 自带配置),无需后端参与;
- 生产环境:优先用 CORS (后端配置,简单高效),或 Nginx 反向代理(适合需要统一部署的场景);
- 兼容旧浏览器:可用 JSONP(仅支持 GET,不推荐优先使用)。
跨域限制的核心是:阻止"前端脚本跨源获取数据",不阻止"单纯的跨源页面访问"。
- 能做的:直接访问、跳转、嵌入任意域名的页面;
- 不能做的(无CORS配置时):在A源的页面里,用JavaScript脚本请求B源的接口并获取数据。
这也是为什么前后端分离项目必须配置CORS(或代理)------ 因为前端需要通过脚本请求后端接口,而直接访问页面、跳转等场景完全不需要处理跨域。