摘要 :跨域是前后端分离开发中最高频、最容易一知半解 的问题。绝大多数开发者只会复制粘贴跨域配置,却不知道跨域为什么产生、预检请求是什么、开发代理为什么上线失效、带 Token/Cookie 为什么跨域报错。本文从零带你吃透跨域底层原理,搭配可直接复制运行的完整实战示例、报错复现案例、场景对照表、流程图、多语言后端配置、线上线下解决方案,覆盖开发、联调、上线全流程避坑,适合日常开发调试、面试深度复盘。
一、什么是跨域?根源:浏览器同源策略(超详细解析)
1.1 跨域的本质:不是 Bug,是浏览器安全机制
很多新手误以为跨域是代码报错、接口故障,完全错误 。跨域是 浏览器同源策略(Same-Origin Policy) 带来的安全限制,是浏览器为了保护用户数据、防止恶意网站窃取隐私的核心机制。
真实完整流程:
- 前端 JS 发起跨域接口请求;
- 请求 正常穿透浏览器、正常发送到后端服务器;
- 后端 正常接收请求、正常执行业务逻辑、正常返回数据;
- 数据回到浏览器后,浏览器检测到跨域,直接拦截 JS 读取响应;
- 前端控制台报 CORS 跨域错误,拿不到返回数据。
简单一句话:请求成功、响应成功,只是浏览器不让你用结果。
1.2 同源判定三大核心规则(缺一即跨域)
浏览器判定两个地址是否同源,必须 协议、域名、端口 三者完全一致,任意一个不同,直接判定跨域:
- 协议:http / https 互相不同源
- 域名:主域名、子域名不一致都不同源
- 端口:任意端口差异,直接跨域(80、443 默认端口也不例外)
1.3 全网最全同源/跨域实战对照示例(可直接对照测试)
| 当前页面地址 | 请求接口地址 | 是否跨域 | 详细原因(重点解析) |
|---|---|---|---|
| http://localhost:5173 | http://localhost:5173/api/user | ❌ 不跨域 | 协议、域名、端口三者完全一致,严格同源,无任何限制 |
| http://localhost:5173 | http://localhost:8080/api/user | ✅ 跨域 | 前端开发最常见场景:前端运行5173,后端运行8080,端口不一致触发跨域 |
| a.com | a.com | ✅ 跨域 | 协议不同(http 明文 / https 加密),哪怕域名端口全一致,依旧跨域 |
| www.a.com | api.a.com | ✅ 跨域 | 生产环境高频场景:主站www子域名、接口api子域名,子域名不同源 |
| a.com | a.com:8081 | ✅ 跨域 | 域名、协议一致,端口不同,严格跨域 |
| http://127.0.0.1:5173 | http://localhost:5173 | ✅ 跨域 | 超级隐蔽坑:浏览器将 localhost 和 127.0.0.1 判定为两个不同域名,本地极易踩坑 |
1.4 重要补充:哪些场景没有跨域?
同源策略 只限制浏览器前端 JS,以下场景完全不存在跨域:
- 后端服务器 <-> 后端服务器 互相调用接口(Java/Node/Python 后台请求无限制)
- 安卓、iOS、桌面客户端请求接口(无浏览器同源限制)
- Postman、Apifox、curl 测试接口(工具不受浏览器策略限制)
这也是为什么:后端自测接口通、前端调用就跨域的根本原因。
二、CORS 跨域资源共享(W3C官方标准,超详细拆解)
CORS(Cross-Origin Resource Sharing)是浏览器官方推出的跨域解决方案,核心逻辑:后端通过响应头告知浏览器"允许哪些前端跨域访问" 。
CORS 是 生产环境首选方案,安全、标准、无副作用,适配所有前后端分离项目。
2.1 六大核心跨域响应头(逐字段详解+禁忌)
| 响应头字段 | 详细作用 | 正确示例 | 禁止写法/坑点 |
|---|---|---|---|
Access-Control-Allow-Origin |
允许跨域的前端域名,核心字段 | http://localhost:5173 | 开启Cookie后禁止写 *,会直接报错 |
Access-Control-Allow-Methods |
允许的请求方式,必须包含OPTIONS | GET,POST,PUT,DELETE,OPTIONS | 只写业务方法,漏写OPTIONS,预检405 |
Access-Control-Allow-Headers |
允许前端自定义请求头 | Content-Type,Token,Authorization | 带自定义头不配置,直接跨域失败 |
Access-Control-Allow-Credentials |
是否允许携带Cookie、Session、凭证 | true | 开启后Origin必须写具体域名,不能* |
Access-Control-Max-Age |
预检请求缓存时长(秒) | 86400(1天) | 不配置会频繁发送OPTIONS预检,损耗性能 |
2.2 简单请求 vs 复杂预检请求(彻底讲透90%人的盲区)
浏览器将跨域请求分为两类,判定规则严格固定,直接决定是否发送 OPTIONS 预检请求。
✅ 简单请求(无预检、直接放行)
必须同时满足全部条件,缺一不可:
- 请求方法:仅 GET / POST / HEAD
- 无自定义请求头(不能带 Token、Authorization)
- Content-Type 仅限三种:
application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求实战可运行示例
javascript
// 普通表单POST,属于简单请求,无OPTIONS预检
fetch('http://localhost:8080/api/test', {
method: 'POST',
body: new URLSearchParams({ name: '测试简单请求' }),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(res => res.json()).then(data => console.log(data))
⚠️ 复杂请求(必触发OPTIONS预检,项目最常用)
满足任意一条即为复杂请求,浏览器会先发送 OPTIONS 预检请求校验权限,通过后才发真实业务请求:
- 请求方法:PUT / DELETE / PATCH
- 请求体为 JSON 格式:
Content-Type: application/json(Axios 默认格式) - 携带自定义请求头:Token、Authorization、timestamp 等
复杂请求报错复现示例(高频报错场景)
php
// Axios默认JSON请求+自定义Token,必触发预检
axios.post('http://localhost:8080/api/login', {
username: 'admin',
password: '123456'
}, {
headers: {
Token: '123456-abcdef'
}
})
// 报错现象:Network出现OPTIONS请求,状态码404/405,业务请求直接失败
核心坑点 :绝大多数后端只配置了业务接口,没有兼容处理 OPTIONS 预检请求,导致预检请求404,整体跨域失败。
三、跨域完整执行流程图
直观区分简单请求、复杂预检请求的完整链路:

四、主流后端完整可运行 CORS 配置(生产可用、解决预检问题)
4.1 Node.js Express 全局CORS配置(终极完整版)
特点:处理预检请求、支持Token、支持Cookie、生产安全,可直接上线使用
javascript
const express = require('express');
const app = express();
// 必须放在所有路由、中间件最前面
app.use((req, res, next) => {
// 允许指定前端域名(生产禁止*)
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
// 允许所有业务方法+预检必带OPTIONS
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
// 放行自定义请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Token,Authorization');
// 开启凭证跨域(Cookie/Session)
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 预检缓存1天,减少请求压力
res.setHeader('Access-Control-Max-Age', 86400);
// 预检请求直接返回200,不进入业务路由
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
})
// 测试跨域接口
app.use(express.json());
app.post('/api/login', (req, res) => {
res.json({ code: 200, msg: '跨域请求成功', data: req.body });
})
app.listen(8080, () => {
console.log('后端服务启动:http://localhost:8080');
})
4.2 SpringBoot 全局CORS配置(企业级生产配置)
解决OPTIONS预检405、Token跨域、Cookie跨域等所有问题
kotlin
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 拦截所有api接口
.allowedOrigins("http://localhost:5173") // 放行前端地址
.allowedMethods("GET","POST","PUT","DELETE","OPTIONS") // 必加OPTIONS
.allowedHeaders("Content-Type","Token","Authorization") // 放行自定义请求头
.allowCredentials(true) // 允许携带Cookie凭证
.maxAge(86400); // 预检缓存时长
}
}
五、前端代理解决方案(仅开发环境,超详细讲解)
重中之重核心知识点 :Vite / Webpack 本地代理 仅 npm run dev 开发环境生效 ,执行 npm run build 打包后,代理配置完全失效,上线必须用 CORS 或 Nginx 代理!
5.1 代理原理通俗讲解
浏览器禁止前端跨域请求后端,但是服务器和服务器之间无跨域限制。
本地开发流程:前端请求本地 Node 开发服务 → Node 服务转发请求到后端 → 后端返回数据给 Node → 前端获取数据,完美规避浏览器跨域限制。
5.2 Vite 完整代理配置(可直接复制使用)
javascript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
// 匹配所有/api开头的请求
'/api': {
target: 'http://localhost:8080', // 后端真实接口地址
changeOrigin: true, // 开启跨域模拟
rewrite: (path) => path.replace(/^/api/, '') // 去除接口前缀
}
}
}
})
使用方式 :前端直接请求 /api/login,本地自动代理转发到 http://localhost:8080/login
六、生产环境终极方案:Nginx反向代理(零跨域、无需后端配置)
生产环境最优方案之一,无需后端改任何代码、无需配置CORS,通过Nginx统一同源。
6.1 原理
前端静态资源、后端接口全部由 Nginx 统一托管,浏览器访问 Nginx 同源地址,彻底消灭跨域。
6.2 Nginx 完整可运行配置
perl
server {
listen 80;
server_name fe.test.com; // 你的线上域名
# 托管前端打包后的静态文件
location / {
root /home/front/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# 代理后端接口
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
七、四大跨域方案优缺点+适用场景对照表
| 解决方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| CORS 后端配置 | 官方标准、安全稳定、支持所有请求、适配线上 | 需要后端配合开发 | 所有正式前后端分离项目(首选) |
| Vite/Webpack本地代理 | 前端独立配置、无需改后端、本地调试快 | 仅开发环境生效,上线完全失效 | 本地开发联调、测试接口 |
| Nginx反向代理 | 零代码侵入、性能好、彻底解决跨域 | 需要运维配置Nginx | 生产环境线上部署 |
| JSONP | 兼容老旧浏览器 | 仅支持GET、存在XSS风险、不安全 | 老旧遗留项目,新项目彻底禁用 |
八、全网最全跨域踩坑合集(99%开发者遇到的问题)
坑1:本地开发正常,打包上线直接跨域
原因:依赖 vite/webpack 本地代理,打包后代理消失,前端直接请求跨域后端地址。
解决:生产环境必须使用 CORS 或 Nginx 代理。
坑2:配置CORS依旧跨域,看不到响应头
原因:CORS中间件/配置 写在 拦截器、鉴权、过滤器之后,OPTIONS请求被提前拦截。
解决 :CORS配置必须是全局第一个执行的中间件。
坑3:开启Cookie/凭证跨域报错
原因 :allowCredentials:true 时,Origin 不能为 *,必须写具体域名;前端axios需开启 withCredentials:true。
解决:前后端统一配置具体域名+凭证开启。
坑4:OPTIONS请求404/405
原因:后端未兼容OPTIONS预检请求,OPTIONS请求打到业务接口不存在。
解决:后端拦截OPTIONS请求,直接返回200状态码。
坑5:Nginx+后端双重配置CORS,跨域报错
原因:响应头重复,浏览器校验失败。
解决:Nginx、后端只保留一处CORS配置。
九、跨域标准排查流程(快速定位问题)
- 打开浏览器 Network,查看是否存在 OPTIONS 预检请求;
- 预检失败 → 优先修复后端OPTIONS兼容、CORS头配置;
- 预检成功、业务请求失败 → 检查自定义请求头、凭证配置;
- 本地正常线上报错 → 排查是否依赖本地代理、线上接口域名配置;
- 带Cookie报错 → 检查Origin是否为*、前端是否开启withCredentials。
十、全文总结
- 跨域是浏览器同源策略安全限制,服务端、客户端工具无跨域限制;
- 跨域请求请求和响应都是成功的,仅浏览器拦截前端读取数据;
- JSON请求、自定义Header、PUT/DELETE方法必触发OPTIONS预检,后端必须兼容;
- 本地代理只适合开发调试,绝对不能用于生产环境;
- 企业项目生产环境优先:CORS后端配置 / Nginx反向代理。