🚀 客户端 vs 服务器端 CORS:了解真实差异
Source: Dev.to

如果你曾经因为一个神秘的 CORS 错误对着显示器大喊……
恭喜你——你已经正式成为一名 web 开发者。 🎉
CORS 就像俱乐部门口那个严格的保镖,明明你发誓自己在名单上,却仍然不让你进去。
CORS 到底是什么?
CORS 是 跨域资源共享(Cross‑Origin Resource Sharing) 的缩写——听起来很高大上,但实际上就是:
“嘿,浏览器,这个网站能和那个网站对话吗?还是说这是陌生人危险的情况?”
浏览器极度多疑。
如果 域 A 调用 域 B,浏览器会说:
“等一下。谁把你送来的?把你的护照、Aadhar 卡、PAN 卡和两张 2×2 照片给我看看。”
客户端 CORS vs 服务端 CORS:剧情反转
人们常说 “客户端 CORS” 和 “服务端 CORS”,但事实是:
只有服务器决定 CORS。客户端只是一个困惑的青少年在请求许可。
两边都有角色——下面我们来解码它们。
客户端 CORS(又名:控制的幻觉)
开发者常写:
fetch("/api", { mode: "cors" })
并期待浏览器说:
“好的,先生!已启用 CORS!你真是天才!”
但浏览器实际上执行的是规则:
mode: "cors"→ “请让我进去!”mode: "no-cors"→ “好吧,我走了。反正我也不想看到响应。”credentials: "include"→ “这是我的 Cookie,请不要评判。”
⚠️ 客户端无法启用 CORS。 它只能请求,真正的钥匙仍在服务器手中。
服务端 CORS(真正的老板)
这才是真正的魔法所在。如果服务器不批准你的来源,浏览器会拦截请求。
典型的响应头:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
只有在收到正确的响应头后,浏览器才会放行请求。
示例(Express.js)
const cors = require('cors');
app.use(cors({
origin: "https://myapp.com",
credentials: true
}));
示例(Next.js API 路由)
export default function handler(req, res) {
res.setHeader("Access-Control-Allow-Origin", "https://myapp.com");
res.setHeader("Access-Control-Allow-Credentials", "true");
// ...handle request
}
只要少写了哪怕一个头部,你就会遇到 CORS 错误。
简单请求 vs 预检请求(又名:浏览器的戏码)
简单请求
这些请求“很 chill”。浏览器允许:
GET或POST,且使用简单头部(例如Accept,Content-Type限制为text/plain、multipart/form-data、application/x-www-form-urlencoded)。
预检请求
当你使用以下情况时会触发:
PUT、PATCH、DELETE等方法- 自定义头部
- JSON 请求体(即
Content-Type: application/json)
浏览器会先发送一个 OPTIONS 请求,向服务器请求许可。如果服务器没有正确响应,实际请求会被阻止。
为快速浏览的天才准备的总结表
| 项目 | 客户端 | 服务端 |
|---|---|---|
| 谁掌控控制权? | 哈哈。不是你。 | 真正的老板。 |
| 能否允许请求? | ❌ 不行 | ✅ 绝对可以 |
| 能否触发预检? | ✅ 可以 | ⚠️ 必须正确响应 |
| 这是真正的 CORS 吗? | ❌ 不是 | ✅ 是,100 % 正规 |
最后思考
CORS 错误可能让人感觉像是宇宙对你的个人攻击,但规则很简单:
- 客户端 发起请求。
- 服务器 决定是否允许(通过发送相应的头部)。
- 浏览器 强制执行这个决定。
当每个环节都做好自己的工作时,CORS 就毫无痛感。出现问题时……祝你享受接下来几个小时的调试之旅吧。 😅