CORS 跨域详解:报错看不懂就先看这篇

控制台出现 Access to fetch ... has been blocked by CORS policy 时,很多人第一反应是「前端怎么绕过它」。但 CORS 是浏览器的安全机制,问题要在服务端解决

一、同源策略

「同源」要求三者完全一致:

说明
协议http / https
域名example.com
端口80 / 443 / 3000
bash
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 就是「服务端声明允许谁」

服务端在响应里加一个头,浏览器就放行:

bash
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;
  • 请求头只有安全的那几个(AcceptAccept-LanguageContent-LanguageContent-Type);
  • Content-Typetext/plainmultipart/form-dataapplication/x-www-form-urlencoded

预检请求(先发 OPTIONS)

不满足上面条件的,浏览器会先发一个 OPTIONS 请求问「我可以吗」:

bash
# 浏览器先发
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 配置

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;
}

多个源的处理(用变量):

nginx
set $cors "";
if ($http_origin ~* "^https://(baize\.net|www\.baize\.net)$") {
    set $cors $http_origin;
}
add_header Access-Control-Allow-Origin $cors always;

add_headeralways 参数很重要:不加的话,204 和错误响应不会带这些头,预检就会失败。

五、带 Cookie 的情况

前端要显式声明:

js
fetch("https://api.bzii.cn/data", {
  credentials: "include"
});

// axios
axios.defaults.withCredentials = true;

服务端必须:

bash
Access-Control-Allow-Origin: https://www.bzii.cn   # 不能是 *
Access-Control-Allow-Credentials: true

带 Cookie 时 Allow-Origin 不能用 *,必须指定具体源。这是一条硬性限制。

另外,现代浏览器要求跨站 Cookie 带 SameSite=None; Secure

bash
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 分支

七、排查步骤

bash
# 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

期望看到的响应头:

bash
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

八、开发环境的临时方案

前端代理(推荐,不改后端):

js
// vite.config.js
export default {
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true
      }
    }
  }
};

同源了,就没有 CORS 问题。

浏览器禁用安全策略(仅临时调试,不要用来开发):

bash
chrome --disable-web-security --user-data-dir=/tmp/chrome-dev

这只会掩盖问题,部署时照样报错。

九、几个易混淆的点

  1. CORS 不是安全功能,是「放宽」机制。它保护的是用户数据不被第三方站点读取,不是保护你的服务器;
  2. curl 不会遇到 CORS 问题——它是浏览器机制,服务端之间调用不受限;
  3. JSONP 是历史方案,只能 GET,别再用;
  4. 图片、CSS、JS 的加载不受 CORS 限制<img> <script src> 可以直接跨源),只有 fetch/XHR 读取受限制。

记住一句:CORS 报错要在服务端修。 前端唯一能做的是别触发非简单请求,或者用代理绕开。