跨域问题的由来与几种通用解法
跨域是浏览器同源策略下的安全机制,不是服务器在拦。本文讲清 CORS 与预检请求的原理,并给出 cros 中间件白名单、Nginx 反向代理、开发期代理和 JSONP 等多种通用解法,帮你分清开发与上线的不同处理方式,避免把 Access-Control-Allow-Origin: * 一刀切。
问题背景
前端页面在 http://localhost:8080 ,后端接口在 http://localhost:3000 ,浏览器里一调接口就报 "blocked by CORS policy"。很多人第一反应是"后端把跨域关掉",可那其实不是挂开关的事。跨域是浏览器的一个安全机制,得先弄明白它为什么存在,才知道怎么解。
原理说明
跨域(CORS,Cross-Origin Resource Sharing)是 浏览器 实现的同源策略——协议、域名、端口三者任一不同都算跨域。它的初衷是安全:防止 A 网站发起的请求去读 B 网站的敏感数据(比如你在 A 的恶意页面偷偷读取你的银行信息)。所以严格说, 不是服务器在阻拦,是浏览器在阻拦 。
判断逻辑:浏览器在请求发出前先发一个 Preflight(预检)请求 ,用 OPTIONS 方法带上 Origin ,问服务器"你允许哪些源来访问?"。服务器通过响应头回答,比如 Access-Control-Allow-Origin 。只有遇到过简单请求(GET/POST/HEAD,无自定义头)才可能跳过预检。
通用解法主要几种:
- 后端加 CORS 响应头(最正统)
- 后端配置 Nginx 反向代理,把前端请求转发给后端,浏览器认为同源
- 开发阶段用代理(前端 dev server 开启 proxy)
- JSONP(仅 GET,且已较少用)
示例代码
方案一:后端用 cors 中间件放开白名单:
// app.js
const express = require('express');
const cors = require('cors');
const app = express();
// 严格限制允许的源,别用 '*' 一刀切
const allowedOrigins = ['http://localhost:8080', 'https://www.example.com'];
app.use(cors({
origin(origin, callback) {
// 无 Origin(同源或非浏览器请求)直接放行
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // 允许跨域携带 Cookie
}));
app.get('/api/user', (req, res) => res.json({ name: 'whoami' }));方案二:Nginx 反代让前后端同源(推荐用于上线):
server {
listen 80;
server_name example.com;
# 前端静态资源
location / {
proxy_pass http://frontend-app/;
}
# 接口转发到后端
location /api/ {
proxy_pass http://backend-app:3000/;
}
}方案三:开发期用 Vite 代理,前端代码完全不用写绝对地址:
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
};常见坑与解决方案
- 坑一:
credentials: true时又写了Access-Control-Allow-Origin: *,浏览器会直接报错,因为带凭证不允许通配符源。解法:必须明确列出来源,不能用*。 - 坑二:预检请求
OPTIONS也走业务中间件,导致鉴权失败挡住预检。解法:让OPTIONS请求直接放行(app.options('*', cors()))。 - 坑三:本地开了代理以为跨域解决了,一上线直连后端又报跨域。解法:生产环境统一走反向代理把前后端放同源,否则就正确配置 CORS 头。
- 坑四:自定义请求头(如把 token 放进
Authorization)触发了预检,服务器不认Authorization直接 404。解法:allowedHeaders里显式允许所需请求头。
小结
跨域本质是浏览器的同源策略,解法围绕"让服务端对指定源放行"或"让前后端同源"两个方向。开发用代理、上线用反代,既干净又安全,别图省事把 * 拉满。接下来把异常返回统一格式,让接口报错也能友好。
FAQ 常见问题
Q:为什么 credentials: true 时不能配 Access-Control-Allow-Origin: * ?
A:因为带凭证(Cookie)的跨域请求不允许使用通配符源。必须显式列出允许的 Origin,否则浏览器会直接拦截报错。
Q:为什么预检 OPTIONS 请求会被鉴权挡住?
A:预检请求也会走业务中间件,若鉴权逻辑处理 OPTIONS 就会把预检拦下。解法是让 OPTIONS 请求直接放行( app.options('*', cors()) )。
Q:本地开了代理没问题,一上线直连后端又报跨域,为什么?
A:代理只在本地开发环境生效。上线后若前后端仍分属不同源,就得正确配置 CORS 响应头,或统一走反向代理让它们同源。






