1. 引言:一个 PUT 请求引发的"连环惨案"
故事始于一个 React Router 的渲染错误。我的应用在跳转页面时崩了,控制台抛出了一个令人困惑的对象:{to: '/login...', options: {...}}。
经过排查,我们发现这只是表象 。真正的元凶潜伏在网络层:
Access to XMLHttpRequest ... has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
故障链条还原:
-
前端发起
PUT请求。 -
浏览器检测到跨域,先发
OPTIONS预检请求。 -
后端返回的允许列表中没有包含
PUT。 -
浏览器拦截请求,报 CORS 错误。
-
前端 API 调用失败,错误处理逻辑试图重定向,但因操作不当导致 React 组件渲染崩溃。
2. 本质解析:什么是 CORS?
CORS(跨域资源共享) 不是为了方便开发者,而是浏览器为了保护用户安全设立的**"安检机制"**。
我们可以用**"银行门禁"**来做比喻:
-
同源(Same Origin): 你(前端)就在银行柜台(后端)内部工作。
- 结果: 随意存取,不需要安检。
-
跨域(Cross Origin): 你是外面的客户,想去柜台存钱。
-
结果: 门口的保安(浏览器)会把你拦住,先去问柜台主管(后端):"这个人能存钱(PUT)吗?"
-
CORS 报错: 主管说:"我们只办取钱(GET),不办存钱。"保安因此把你拒之门外。
-
只要 协议 + 域名 + 端口 有任何不同,就是跨域。
3. 架构抉择:开启 CORS 还是 追求同源?
在解决上面这个问题时,我们面临两个方向的选择:
方案 A:开启 CORS (Cross-Origin Resource Sharing)
做法: 后端配置 Access-Control-Allow-Origin 等响应头,允许前端跨域访问。
-
优点: 架构解耦,前端(如 Vercel)和后端(如 AWS EC2)可以物理分离。
-
缺点:
-
性能损耗: 每次非简单请求(如 PUT/DELETE/带 JSON 的 POST)都会多发一次
OPTIONS请求。 -
安全隐患: 为了支持跨域 Cookie,Cookie 必须设为
SameSite=None,降低了防御 CSRF 的能力。
-
方案 B:反向代理 (Reverse Proxy) / 同源架构 【推荐】
做法: 前端不直接连后端,而是通过一个"中间人"(Nginx 或 Vite)转发请求。浏览器认为自始至终都在访问同一个域名。
-
优点:
-
极致安全: Cookie 可设为
SameSite=Strict,彻底锁死在当前域名下。 -
性能更优: 没有
OPTIONS预检请求,少一次 RTT(往返时延)。 -
架构清晰: 符合企业级内网/后台系统的标准设计。
-
4. 终极解决方案:如何在本地完美模拟线上架构
为了坚持"生产环境禁用 CORS,保持同源"的高标准,我们在本地开发环境(Local)复刻了线上的架构。
步骤一:欺骗浏览器 (/etc/hosts)
让浏览器以为 local.mcp.tester.com 就是你的本机。
# 文件路径:/etc/hosts (Mac/Linux) 或 C:\Windows\System32\drivers\etc\hosts
127.0.0.1 local.mcp.tester.com
步骤二:建立中间人 (Vite Proxy)
利用 Vite 自带的开发服务器作为"反向代理",替代 Nginx 的角色。
vite.config.ts 配置核心:
server: {
// 1. 允许自定义域名访问 (Vite 安全策略)
allowedHosts: ['local.mcp.tester.com'],
host: '0.0.0.0',
port: 5173,
// 2. 代理转发 (核心魔法)
proxy: {
'/api': {
target: 'https://mcp-backend.tester.com', // 真实的后端地址
changeOrigin: true, // 欺骗后端,修改 Host 头
secure: false, // 忽略 HTTPS 证书错误
},
},
}
步骤三:前端代码适配
绝对禁止 在代码里写死 http://localhost:3001。必须使用相对路径,让请求走代理通道。
// ✅ 正确写法
axios.put('/api/v1/zone/497/status', ...);
效果
-
浏览器访问:
http://local.mcp.tester.com:5173 -
请求发出:
http://local.mcp.tester.com:5173/api/...(同源!) -
Vite 转发 -> 后端。
-
结果:CORS 彻底消失,Cookie 完美传递。
5. 后端视角:NestJS 配置迷思
既然使用了代理,后端还需要配置 CORS 吗?
如果你能保证所有请求都经过 Nginx/代理转发,后端的 CORS 配置确实可以关闭。但在开发阶段,为了调试方便,或者作为一种兜底策略,我们通常会看到这种配置:
// NestJS 配置
cors: {
origin: true, // "反射型允许":不管谁来,我都说允许,并把你的域名写回响应头
methods: ['GET', 'POST', 'PUT', ...], // 明确允许的方法列表
credentials: true, // 允许携带 Cookie
}
-
origin: truevsorigin: '*': 前者允许带 Cookie,后者不允许。 -
注意:
origin: true在开发环境很方便,但在生产环境如果直接暴露在公网,可能过于宽松。配合反向代理使用时,它是安全的,因为外部流量接触不到后端,只接触代理。
6. 总结
在现代 Web 开发中,面对 CORS 报错,初级工程师会问"后端怎么配才能通?",而高级架构师会思考"我们真的需要跨域吗?"。
最佳实践路径:
-
首选同源: 使用 Nginx (生产) 和 Vite Proxy (本地) 进行反向代理,彻底规避 CORS。
-
统一环境: 通过
/etc/hosts和自定义域名,让本地开发环境(Local)和线上(Prod)保持一致,避免"在我本地是好的,上线就挂了"的尴尬。 -
安全至上: 同源架构允许你使用最严格的 Cookie 策略 (
SameSite=Strict),为系统安全构筑坚实的防线。