CORS 跨域详解:报错看不懂就先看这篇
控制台出现 Access to fetch ... has been blocked by CORS policy 时,很多人第一反应是「前端怎么绕过它」。但 CORS 是浏览器的安全机制,问题要在服务端解决。
一、同源策略
「同源」要求三者完全一致:
| 项 | 说明 |
|---|---|
| 协议 | http / https |
| 域名 | example.com |
| 端口 | 80 / 443 / 3000 |
https://www.bzii.cn vs https://www.bzii.cn/post/1 → 同源
https://www.bzii.cn vs http://www.bzii.cn → 不同(协议)
https://www.bzii.cn vs https://api.bzii.cn → 不同(域名)
https://www.bzii.cn vs https://www.bzii.cn:8443 → 不同(端口)浏览器默认阻止跨源的读取响应(注意:请求其实发出去了,是响应被浏览器拦下)。
二、CORS 就是「服务端声明允许谁」
服务端在响应里加一个头,浏览器就放行:
Access-Control-Allow-Origin: https://www.bzii.cn常用响应头:
| 头 | 作用 |
|---|---|
Access-Control-Allow-Origin | 允许的源,或 * |
Access-Control-Allow-Methods | 允许的方法 |
Access-Control-Allow-Headers | 允许的请求头 |
Access-Control-Allow-Credentials | 是否允许带 Cookie |
Access-Control-Max-Age | 预检结果缓存多久(秒) |
Access-Control-Expose-Headers | 允许前端读取的响应头 |
三、简单请求 vs 预检请求
简单请求(不发预检)
同时满足:
- 方法是 GET / HEAD / POST;
- 请求头只有安全的那几个(
Accept、Accept-Language、Content-Language、Content-Type); Content-Type是text/plain、multipart/form-data或application/x-www-form-urlencoded。
预检请求(先发 OPTIONS)
不满足上面条件的,浏览器会先发一个 OPTIONS 请求问「我可以吗」:
# 浏览器先发
OPTIONS /api/data HTTP/1.1
Origin: https://www.bzii.cn
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type,authorization
# 服务端必须回答
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.bzii.cn
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Content-Type: application/json会触发预检——这是最常见的情况,因为 JSON 不在「简单」列表里。
四、Nginx 配置
location /api/ {
# 允许的源(生产环境别用 *,除非是公开 API)
add_header Access-Control-Allow-Origin "https://www.bzii.cn" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 86400 always;
# 处理预检请求:直接返回 204,不要转发给后端
if ($request_method = OPTIONS) {
add_header Content-Length 0;
add_header Content-Type "text/plain";
return 204;
}
proxy_pass http://127.0.0.1:3000;
}多个源的处理(用变量):
set $cors "";
if ($http_origin ~* "^https://(baize\.net|www\.baize\.net)$") {
set $cors $http_origin;
}
add_header Access-Control-Allow-Origin $cors always;
add_header的always参数很重要:不加的话,204 和错误响应不会带这些头,预检就会失败。
五、带 Cookie 的情况
前端要显式声明:
fetch("https://api.bzii.cn/data", {
credentials: "include"
});
// axios
axios.defaults.withCredentials = true;服务端必须:
Access-Control-Allow-Origin: https://www.bzii.cn # 不能是 *
Access-Control-Allow-Credentials: true带 Cookie 时
Allow-Origin不能用*,必须指定具体源。这是一条硬性限制。
另外,现代浏览器要求跨站 Cookie 带 SameSite=None; Secure:
Set-Cookie: session=abc; SameSite=None; Secure; HttpOnly六、常见报错对照
| 报错 | 原因 | 解法 |
|---|---|---|
| No 'Access-Control-Allow-Origin' header | 服务端没返回该头 | 加上 |
| The value is not equal to the supplied origin | 源不匹配 | 检查 Origin 值 |
| Credentials flag is true, but Allow-Origin is * | 带 Cookie 却用了通配 | 改成具体源 |
| Method PUT is not allowed | 预检没返回该方法 | 补 Allow-Methods |
| Request header authorization is not allowed | 请求头未声明 | 补 Allow-Headers |
| 预检返回 404/405 | 服务端没处理 OPTIONS | 加 OPTIONS 分支 |
七、排查步骤
# 1. 看请求到底发了什么
# DevTools → Network → 找到那条请求 → 看 Request Headers 里的 Origin
# 2. 看响应带了什么头
curl -I -H "Origin: https://www.bzii.cn" https://api.bzii.cn/data
# 3. 模拟预检
curl -X OPTIONS -I \
-H "Origin: https://www.bzii.cn" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: content-type" \
https://api.bzii.cn/data期望看到的响应头:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.bzii.cn
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
Access-Control-Allow-Headers: content-type八、开发环境的临时方案
前端代理(推荐,不改后端):
// vite.config.js
export default {
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true
}
}
}
};同源了,就没有 CORS 问题。
浏览器禁用安全策略(仅临时调试,不要用来开发):
chrome --disable-web-security --user-data-dir=/tmp/chrome-dev这只会掩盖问题,部署时照样报错。
九、几个易混淆的点
- CORS 不是安全功能,是「放宽」机制。它保护的是用户数据不被第三方站点读取,不是保护你的服务器;
- curl 不会遇到 CORS 问题——它是浏览器机制,服务端之间调用不受限;
- JSONP 是历史方案,只能 GET,别再用;
- 图片、CSS、JS 的加载不受 CORS 限制(
<img><script src>可以直接跨源),只有fetch/XHR 读取受限制。
记住一句:CORS 报错要在服务端修。 前端唯一能做的是别触发非简单请求,或者用代理绕开。
