我把最常踩的8个CORS跨域报错整理了一遍——第8个去年还不存在

CORS 报错大概是前端最难读的报错之一:一段英文、三层引号嵌套,第一眼根本分不清谁拦了谁。我把这几年项目里踩过的 CORS 报错整理了一遍,一共 8 个,每个都给出现场报错原文、原因和解法,文末有一张可以直接收藏的速查表------下次再遇到,10 秒对号入座。

提前说一个:第 8 个报错去年还不存在。它是 Chrome 正在灰度推进的 Private Network Access 限制,最近社区里"本地环境跑了一年突然报跨域"的求助帖,大半是它干的。

先用30秒把CORS机制过一遍

浏览器有个同源策略:不同源(协议、域名、端口任一不同)的页面之间,默认不允许读取对方的响应。CORS 就是给这个限制开的口子------服务端在响应头里声明"允许哪个 origin 读我",浏览器检查通过才把数据交给 JS。

所以记住第一个结论:CORS 报错几乎都是服务端响应头的问题,不是你前端代码的问题。你的请求大概率发出去了,服务端也正常返回了,只是响应头里没有那句"允许"。

请求还分两种:

  • 简单请求:GET/HEAD/POST + 简单头(没有自定义头、Content-Type 只限表单那几种),浏览器直接发,回来检查响应头。
  • 预检请求:带自定义头、用了 PUT/DELETE、Content-Type 是 application/json 之类的,浏览器会先发一个 OPTIONS 请求"问路"------服务端在预检响应里答"我允许这些方法和头",通过了才发真实请求。

报错也就分成两类:简单请求查响应头,预检请求查 OPTIONS 响应。下面 8 个报错,对号入座。

报错1:没有Access-Control-Allow-Origin头(最常见)

报错原文:

csharp 复制代码
Access to fetch at 'https://api.example.com/data' from origin
'https://app.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

含义很直白:响应头里压根没有 Access-Control-Allow-Origin。解法分三档:

1)能改服务端,直接加头。 Express 示例:

js 复制代码
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://app.example.com');
  next();
});

2)本地开发改不了后端,用构建工具代理。 Vite 示例:

js 复制代码
// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': { target: 'https://api.example.com', changeOrigin: true },
    },
  },
});

配好之后前端直接请求 /api,dev server 帮你转发到后端。浏览器看到的是同源请求,跨域问题从根上消失------这也是本地开发最推荐的方式。

3)线上环境、服务端又不归你管,只能让你自己的服务端加一层转发,别让浏览器直接碰对方接口。

报错2:带cookie时不能用通配符*

报错原文:

ruby 复制代码
The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is 'include'.

前端开了 credentials: 'include'(要带 cookie)之后,服务端就不能偷懒用 Access-Control-Allow-Origin: * 了,必须回显具体的 origin,而且还要带上 Access-Control-Allow-Credentials: true。正确姿势是服务端动态判断白名单:

js 复制代码
const allowList = new Set(['https://app.example.com']);
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowList.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
  }
  next();
});

注意:这个场景下 Access-Control-Allow-HeadersAccess-Control-Allow-Methods 同样不接受 *,都得写具体值。

报错3:Allow-Origin出现了多次

报错原文:

sql 复制代码
The 'Access-Control-Allow-Origin' header contains multiple values.

这个头在响应里只能出现一次、只能有一个值。典型成因是两层都配了:nginx 里 add_header 加了一次,应用代码里又 setHeader 了一次,浏览器收到两个值直接拒绝。

解法:全链路排查 nginx、网关、应用框架三层,只保留一处。排查时可以打开 DevTools 的 Network 面板看响应头原文,重复的会明晃晃地出现两行。

报错4:预检不允许该请求方法

报错原文:

csharp 复制代码
Method DELETE is not allowed by Access-Control-Allow-Methods in preflight response.

前面说过,DELETE 属于复杂请求,浏览器会先发 OPTIONS 问路。服务端的预检响应里 Access-Control-Allow-Methods 没列 DELETE,请求在问路阶段就被毙了,真实请求根本没发出去。

解法是把用到的方法补全。Express 示例:

js 复制代码
if (req.method === 'OPTIONS') {
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,X-Token');
  res.setHeader('Access-Control-Max-Age', '86400');
  return res.status(204).end();
}

报错5:预检不允许自定义请求头

报错原文:

vbscript 复制代码
Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response.

你给请求加的每一个非简单头------Authorization、自定义的 X-Token------都要在服务端的 Access-Control-Allow-Headers 里逐个列出来,一个都不能少。

一个容易忽略的连带问题:很多 HTTP 库会默认把 Content-Type 设成 application/json,这本身就会把请求升级成复杂请求、触发预检。如果你的接口其实只收表单数据,把 Content-Type 改回 application/x-www-form-urlencoded 可以省掉整轮预检。

报错6:配置明明改了,浏览器还报老错

这个没有新的报错文本,症状是"报错4、报错5都修好了,浏览器还在报一模一样的错"。

原因:预检结果是可以缓存的,Access-Control-Max-Age 就是缓存时长。之前配置宽松时设过 86400(一天),那你改完配置后的一天内,浏览器都会拿旧的预检结果直接用,根本不再问路。

解法:调试期间把 Access-Control-Max-Age 设成 0,或者在 DevTools 里勾上 Disable cache,再测。

报错7:重定向的目标没有CORS头

报错原文(注意里面出现了两个 URL):

csharp 复制代码
Access to fetch at 'https://b.example.com/data' (redirected from
'https://a.example.com/api') from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header
is present on the requested resource.

CORS 检查的是最终响应。如果接口 302 跳到了另一个域------常见的有跳登录中心、跳 CDN、跳网关------那么最终那个域的响应也必须带 CORS 头。前端对这种错无解,只能让服务端处理:要么去掉重定向,要么让重定向目标也配上头。

报错8:Chrome Private Network Access(去年还不存在的新坑)

报错原文和传统跨域长得很像,但会多出一句关键信息:

swift 复制代码
The request client is not a secure context and the resource is in
more-private address space `local`

或者预检阶段的:

csharp 复制代码
No 'Access-Control-Allow-Private-Network' header is present on the
requested resource.

背景:Chrome 正在分阶段推进 Private Network Access(私有网络访问)限制------公网页面想访问 localhost、内网 IP 这种"更私有"的网络资源,必须先通过预检,而且服务端要额外返回一个头

vbnet 复制代码
Access-Control-Allow-Private-Network: true

同时页面本身得处于安全上下文(https 或 localhost)。这就是最近"本地环境跑了一年都没事、突然开始报跨域"的原因:不是你的代码变了,是浏览器的规则在收紧。

应对分场景:

  • 本地开发:把页面也放在 localhost 下跑(同源同为本地,直接绕开),是最省事的。
  • 必须公网访问本地服务(比如真机调试、内网部署的调试工具):给本地服务加上面那个响应头,并确保页面是 https。
  • 临时应急:在 chrome://flags 里搜索 Private Network Access,把相关拦截开关临时关掉------只适合自己调试,别写进文档让用户做。

这个限制还在灰度推进中,建议现在就把它加进排查清单,不然哪天团队里总有人会来问你"为什么突然全挂了"。

CORS报错速查表

报错关键词 原因 解法
No 'Access-Control-Allow-Origin' header 服务端没返回该头 服务端加头 / 构建工具代理 / 服务端转发
must not be the wildcard '*' 带 cookie 却用了通配符 动态回显白名单 origin + Allow-Credentials
contains multiple values 同一个头被配置了两层 排查 nginx/网关/应用,只留一处
not allowed by Access-Control-Allow-Methods 预检没放行该方法 Allow-Methods 补全方法
not allowed by Access-Control-Allow-Headers 自定义头没被列出 Allow-Headers 逐个补上
配置改了还报旧错 预检结果被 max-age 缓存 max-age 设 0 或禁用缓存再测
redirected from ... 重定向目标没 CORS 头 服务端去掉重定向或给目标加头
more-private address space Chrome PNA 私有网络限制 加 Allow-Private-Network: true + 安全上下文

三个常见误区

误区1:mode: 'no-cors' 能解决跨域。 不能。它只是让请求不报错,返回的是一个 opaque response------状态码读不到、响应体读不到,等于发了个寂寞。它唯一的正当用途是埋点上报这种"不需要读响应"的场景。

误区2:装个 Allow CORS 浏览器插件就行。 插件只是给你本机浏览器的响应注入头,线上用户的浏览器里没有任何插件。它只适合本地调试救急,把它当解决方案,上线必翻车。

误区3:用 JSONP 绕过去。 JSONP 只支持 GET、要后端专门配合、还存在注入风险,现在几乎没有新项目在用。都这个年代了,老老实实走标准 CORS。

写在最后

CORS 报错看着吓人,本质上只有三类:服务端没给头、头的值不对、浏览器加了新规则(PNA 就是第三类)。下次看到报错,先复制报错文本到上面的速查表里对号入座,别上来就乱加头碰运气------乱加头只会把报错1变成报错3。

你踩过哪个 CORS 报错?或者最近也遇到了"突然跨域"的灵异事件?评论区聊聊。速查表有用的话帮我点个收藏,后面遇到新报错我会继续往里补。

我是把报错原文整理成速查表的前端,关注我,下次报错不慌。

相关推荐
Csvn2 小时前
🕰️ 闭包 + setTimeout 的 5 个经典陷阱:为什么定时器看到的永远不是最新的值?
前端
lilian2332 小时前
Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
前端·数据库·华为·harmonyos
CarIise3 小时前
CSS选择器与样式关联
前端·css·tensorflow
里欧跑得慢4 小时前
CSS 模块化架构的演进:BEM、CSS Modules 到 CSS-in-JS 的反思
前端·css·flutter·web·css-in-js
IT_陈寒4 小时前
Vue的computed属性把我坑惨了,原来我一直用错姿势
前端·人工智能·后端
小灰灰搞电子5 小时前
Rust+Slint 实现温度计源码分享
前端·rust·slint
计算机魔术师5 小时前
面壁智能 OpenBMB 推出 MathForm,面向 Lean 4 数学自动形式化的开源框架、数据集与模型
前端
NeilCarmack5 小时前
Deepseek-harness增加桌面版端序列:第 2 讲 · spawn Electron:当前进程如何“交棒“
前端·javascript·electron
上海魁鲸科技有限公司6 小时前
APS高级排产系统到底有什么用?一文讲清功能、选型与落地建议
前端·microsoft·excel