☰
# CORS 配置有问题?啥是跨域?啥是CORS?
2026/10/10 17:33:49 网站建设 项目流程

CORS 配置有问题?啥是跨域?啥是CORS?

一篇从「安全扫描报告说我 CORS 配置有问题」出发,把跨域这件事彻底讲明白的文章


一、开局两个场景,你大概至少中过一个

场景 A:你写好了前端,本地跑得飞起。一上测试环境,控制台红了一片:

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

你用 Postman 试了一下——完全正常,200,数据也对。后端同学看了日志,也是 200。于是双方开始互相怀疑:「是不是你前端写错了?」

场景 B(我这次遇到的):系统跑了半年,没有任何人报错,一切风平浪静。然后安全团队做渗透测试,报告里写着:

【高危】CORS 配置不当,存在跨域资源共享漏洞

你一脸茫然:这功能明明好着呢,怎么就成漏洞了?

这两个场景,其实是同一件事的两个极端。场景 A 是配得太紧,场景 B 是配得太松。而绝大多数 CORS 漏洞的成因,就是开发者被场景 A 折磨了两天,然后随手把配置改松了,报错消失,皆大欢喜。

这篇文章想讲清楚三件事:

  1. CORS 到底是个什么机制,它保护谁、防谁、由谁执行;
  2. 遇到什么症状应该想到「这是 CORS」,遇到什么症状不该想到;
  3. 配置到底该放在哪、怎么写、怎么自测。

二、地基:三个概念,先说人话

2.1 什么是「源」(Origin)

源 =协议 + 域名 + 端口,三者完全相同才叫同源。

以https://app.example.com/list?p=1为例,它的源是https://app.example.com。注意:

对比对象是否同源原因
https://app.example.com/other/path✅ 同源路径不参与判断
http://app.example.com❌协议不同
https://api.example.com❌子域不同
https://example.com❌主域也算不同
https://app.example.com:8443❌端口不同

记住一句:Origin 里没有路径、没有斜杠、默认端口会被省略。这句话后面会救你一次。

2.2 什么是同源策略(Same-Origin Policy)

这是浏览器最古老、最核心的安全机制。一句话:

A 网站的 JS 代码,默认不能读取 B 网站的数据。

打个比方。浏览器是一栋写字楼,每个源是一个独立办公室。同源策略就是门禁卡:你的卡只能开自己办公室的门,隔壁公司的文件柜你碰不到。

为什么必须这样?想象一下如果没有它:

你在网银登录着,同时打开了evil.com。evil.com的 JS 可以直接fetch('https://your-bank.com/api/balance')。因为浏览器会自动带上网银的 Cookie,请求在服务端看来完全合法,然后返回值被evil.com读走了。

同源策略正是为了阻止这个。它拦住的不是「请求」,而是**「跨源读取响应内容」**。

2.3 那 CORS 是什么

同源策略太严了。现实中前后端分离、微服务、开放 API,都需要合法的跨源读取。于是就有了 CORS(Cross-Origin Resource Sharing,跨源资源共享)。

CORS 是同源策略的「例外申报机制」。它本质上是资源持有方(服务端)在响应里贴一张便条:

「我允许https://app.example.com的页面读取我的这份响应。」

浏览器看到这张便条,核对一下确实写着当前页面的源,就放行;没看到便条,或者上面写的不是你,就把响应内容扣下来,在控制台报错。

2.4 ⚠️ 全文最重要的一句话

服务端只负责「声明」,真正的「执行」发生在浏览器里。

这句话你需要现在就刻进去,因为它能一次性解释后面所有的困惑:

  • 为什么Postman / curl 能通,浏览器不通?因为 curl 不看那张便条,它自愿不遵守。CORS 对它毫无约束力。
  • 为什么后端日志显示 200,前端却说跨域?因为请求确实发出去了、后端确实处理了、响应确实回来了,然后在最后一步被浏览器扣下。两边说的都是真话,别再争论请求到没到。
  • 为什么CORS 配置错了会变成漏洞?因为浏览器只是个执行者,它不判断你的白名单是否合理。你说允许,它就允许。

三、工作原理:浏览器和服务器到底在聊什么

CORS 请求分两类,搞混这两类是排查跨域时最大的时间浪费。

3.1 简单请求:一趟就完事

满足全部以下条件的,叫简单请求:

  • 方法是GET/HEAD/POST
  • 除浏览器自动加的头之外,只用了这几个头:Accept、Accept-Language、Content-Language、Content-Type(有限制)、Range(有限制)
  • 如果有Content-Type,值只能是text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一

流程很直白:

浏览器 ──────► 服务器 GET /api/list Origin: https://app.example.com 浏览器 ◄────── 服务器 200 OK Access-Control-Allow-Origin: https://app.example.com { "data": [...] } ↓ 浏览器核对便条 → 通过 → 交给 JS

注意:这里请求是真的发出去了。哪怕最后被拦,服务端的数据库写入也已经发生了。CORS 从来不阻止请求,只阻止 JS 读取响应。

3.2 预检请求:先派人打个招呼

一旦不满足简单请求条件——最常见的就是Content-Type: application/json,或者用了PUT/DELETE,或者加了Authorization头——浏览器就会先发一个OPTIONS请求去问路:

① 预检 浏览器 ──────► 服务器 OPTIONS /api/item Origin: https://app.example.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: content-type,authorization 浏览器 ◄────── 服务器 204 No Content Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Methods: GET,POST,PUT,DELETE Access-Control-Allow-Headers: Content-Type,Authorization Access-Control-Max-Age: 600 ↓ 预检通过,才发真实请求 ② 真实请求 浏览器 ──────► 服务器 PUT /api/item ...

预检有两个极容易踩坑的特性:

坑一:预检请求不携带 Cookie,也不携带Authorization头。

它是一个「匿名问路」。所以如果你的鉴权中间件挡在最前面,OPTIONS必然被判 401/403,预检失败,真实请求根本不会发出。

症状:GET 接口一切正常,POST/PUT 全部报跨域。

解法:把OPTIONS放行到鉴权链之前。

坑二:预检结果会被缓存,缓存期内不再预检。

Access-Control-Max-Age控制缓存秒数,但浏览器有上限(Chrome 最多 2 小时,Firefox 最多 24 小时)。

症状:你改完配置刷新页面还是报错,以为没生效。换个无痕窗口试试。

3.3 完整头字段速查

请求头(浏览器自动加,JS 改不了)

头说明
Origin当前页面的源。浏览器强制添加,无法伪造
Access-Control-Request-Method仅预检。真实请求打算用什么方法
Access-Control-Request-Headers仅预检。真实请求打算带哪些自定义头

响应头(服务端要配的就是这些)

头说明常见错误
Access-Control-Allow-Origin允许的源。只能是单个值或*,不能是列表想写多个域名用逗号分隔 → 无效
Access-Control-Allow-Credentialstrue表示允许带 Cookie与*同时出现 → 浏览器直接拒绝
Access-Control-Allow-Methods预检回答:允许的方法漏了PATCH
Access-Control-Allow-Headers预检回答:允许的自定义头写*但覆盖不了Authorization,必须显式列出
Access-Control-Expose-Headers允许 JS 读取的响应头见下方说明
Access-Control-Max-Age预检缓存秒数—
Vary: Origin告诉缓存层「响应随 Origin 变化」见下方说明

关于Expose-Headers,一个高频困惑:

跨源时,JS 默认只能读 7 个响应头:Cache-Control、Content-Language、Content-Length、Content-Type、Expires、Last-Modified、Pragma。

所以如果你的分页总数放在X-Total-Count里,或者下载文件名放在Content-Disposition里——接口 200,数据正常,但这两个头在 JS 里是null。这时候人往往想不到是 CORS,会去怀疑后端没返回。用 curl 一看明明有,更加困惑。

关于Vary: Origin,一个隐蔽的雷:

你的响应内容取决于请求的Origin,但 CDN / nginx 缓存默认不看Origin。结果是 CDN 缓存了给 A 站的响应,B 站命中同一份缓存,拿到了别人的Allow-Origin。

症状:时好时坏、只有部分用户不行、换个网络就好了。这是 CORS 里最难查的一类问题,加一行Vary: Origin就能避免。


四、澄清:CORS 到底在保护谁?

这一节是理解「为什么会被扫出漏洞」的关键。很多人对 CORS 的定位有根本性误解。

误解一:「CORS 能保护我的接口不被别人调用」

错。CORS 完全拦不住任何攻击者。攻击者写个脚本、用 curl、用 Pythonrequests,一行 CORS 头都不看,你的接口该怎么调还是怎么调。

CORS 不是访问控制机制。接口的访问控制靠的是鉴权(Token / Session / 签名),跟 CORS 是两条完全独立的线。

误解二:「CORS 是浏览器给我添麻烦」

反了。CORS 是放宽限制的机制,不是增加限制的机制。真正限制你的是同源策略;CORS 是同源策略给你开的那扇门。没有 CORS,跨源读取是彻底不可能的。

误解三:「配松一点没关系,反正没人攻击」

这是最危险的一条,需要展开讲。

CORS 真正保护的是:用户的身份凭证不被第三方站点盗用。攻击模型长这样:

用户已登录 your-app.com(浏览器里有 Cookie) ↓ 用户被诱导访问 evil.com(钓鱼链接、论坛帖子、广告) ↓ evil.com 的 JS 执行: fetch('https://api.your-app.com/user/profile', { credentials: 'include' }) ↓ 浏览器自动带上 your-app.com 的 Cookie,请求完全"合法" ↓ 服务端返回用户的完整个人信息 ↓ 浏览器检查 Access-Control-Allow-Origin: ├── 没有 / 不匹配 → 扣下,evil.com 读不到 ✅ └── 回显了 evil.com → 放行,数据被读走 🔴

注意这里的关键:攻击工具是受害者自己的浏览器。攻击者不需要拿到 Cookie,只需要借用受害者的浏览器去发请求、再把响应读走。

所以下面这段代码,是把大门直接拆了:

// ⚠️ 这不是配置,这是漏洞response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));response.setHeader("Access-Control-Allow-Credentials","true");

「谁来我就回显谁 + 允许带凭证」=任何网站的 JS 都能带着用户的 Cookie 读你的接口,并读到完整响应体。浏览器为什么不拦?因为你亲口说了允许。

危险写法清单

写法危险等级说明
ACAO: *+Credentials: true🟢规范禁止,浏览器直接拒绝。反而是安全的(但功能坏了)
ACAO: *,无 Credentials🟡取决于接口是否本就该公开。内网接口这样配 = 信息泄露
反射Origin+Credentials: true🔴真正的高危漏洞
SpringallowedOriginPatterns("*")+allowCredentials(true)🔴等价于上一行,但看起来无辜得多
正则未锚定:~app\.example\.com🔴app.example.com.evil.com直接通过
用startsWith/contains匹配🔴同上,https://app.example.com.evil.com或https://evil-app.example.com
白名单包含null🔴sandbox iframe、data:URL 的 Origin 就是null,可被伪造
白名单里的http://localhost:3000上了生产🔴攻击者在受害者本机起个服务即可

为什么这类漏洞能潜伏半年?看这张表

配得太紧(漏配)配得太松(反射)
浏览器表现❌ 控制台红字,立刻炸✅一切正常
上线后用户当天投诉零症状
谁能发现任何人只有主动安全扫描
修复动力极强几乎为零(「又没坏」)

CORS 配得太紧,你会在五分钟内知道;配得太松,你可能永远不会知道——直到有人扫你,或者数据已经出去了。

这也解释了为什么我这次会被扫出问题:这两种错误在因果上是连着的。开发者被跨域报错折磨够了,最后改成反射 Origin,报错消失,问题「解决」了。

所以真正的教训不是「别配错」,而是:

CORS 报错时,不要以「消除报错」为目标,要以「让正确的 origin 通过」为目标。

这两个目标看起来一样,但前者的最优解是反射 Origin,后者的最优解是显式白名单。


五、实用指南 A:什么症状该想到 CORS?

CORS 的报错经常伪装成别的问题,这是最容易浪费时间的地方。

5.1 一眼就是 CORS 的

症状说明
控制台出现blocked by CORS policy一定要读完后半句,它会明确告诉你缺哪个头
Network 面板里冒出一个你没写过的OPTIONS预检。它失败了,真实请求根本没发
fetch抛TypeError: Failed to fetch,信息极少规范要求不向 JS 泄露细节。必须去看 Console,而不是 catch 到的 error 对象

5.2 伪装成别的问题的(重点)

症状真实原因
Postman 通,浏览器不通执行者差异。好消息:说明后端逻辑全对,只缺响应头
后端日志全 200,前端说跨域正常现象,响应被浏览器最后一步扣下。别争论请求到没到,直接查响应头
登录接口 200,下一个请求就 401跨站 Cookie 没带上。四件套必须齐:后端Set-Cookie带SameSite=None; Secure、Allow-Credentials: true、ACAO是具体域名不能是*、前端credentials: 'include'
GET 好用,POST/PUT 报错预检问题。九成是鉴权中间件拦了OPTIONS
OPTIONS返回 401/403同上。预检不带凭证,进了鉴权链必死
响应 200 但 body 是空的fetch用了mode: 'no-cors',拿到的是 opaque 响应。这是「消除报错」的另一种错误姿势
分页总数读不到 / 下载文件名丢了缺Access-Control-Expose-Headers
本地好,上线炸白名单只写了 localhost;或本地那个vite proxy没跟着上生产(详见第六节)
时好时坏 / 只有部分用户不行缺Vary: Origin,CDN 缓存污染
改完配置不生效预检缓存(Max-Age);或多实例配置不一致;或 CDN 缓存了旧响应
重定向之后失败跨源重定向会重新走一遍 CORS 检查,且预检请求不允许跟随重定向

5.3 看起来像 CORS,其实不是

症状真实原因
HTTPS 页面调 HTTP 接口失败Mixed Content,在 CORS 之前就被拦了。而且localhost有「潜在可信」豁免,所以本地不报、上线才报
ERR_CONNECTION_REFUSED/ 502 / 504后端挂了、反代配错。跟 CORS 无关,但因为错误响应里没有 CORS 头,浏览器会顺带报一个 CORS 错误,极具误导性
只有某些用户 / 某些浏览器不行广告拦截插件、企业代理、隐私模式屏蔽第三方 Cookie

一条判据:Network 面板里如果连状态码都没有(显示(failed)或0),先怀疑网络层;如果有正常状态码但 JS 读不到,才轮到 CORS。


六、实用指南 B:配置到底放在哪?

回到最开始那个问题:「所以主要就是改 nginx 配置吧?」

答案是取决于架构,而且有一条铁律比「改哪里」更重要:

CORS 响应头必须只有一个地方负责输出。

不是「nginx 加一份、应用再加一份保险」。两份配置 = 两个Access-Control-Allow-Origin= 浏览器判定非法 =整个 CORS 直接失败。

诡异的是,这时候你 curl 一看,两个值都是对的,于是开始怀疑人生。所以第一步不是「怎么写」,而是「先决定谁负责」。

6.1 四种方案

方案头写在哪适用场景评价
A. 同源反代哪都不写前后端能收敛到同一域名⭐最优解
B. 应用层Spring / Express / Django 中间件后端单一服务直接对外灵活,能按接口区分
C. nginx / 网关nginxadd_header多服务、多语言,要统一策略统一,但语法有坑
D. CDN / API Gateway边缘配置已有 CDN 层注意Vary和缓存

6.2 方案 A:最好的 CORS 配置是不需要 CORS

server { server_name app.example.com; location / { root /var/www/frontend; # 前端静态资源 try_files $uri /index.html; } location /api/ { proxy_pass http://backend:8080; # 后端 proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }

在浏览器眼里,https://app.example.com/api/xxx和页面是同源的。于是:CORS 从未介入、预检不存在、Cookie 天然携带、SameSite不用改、一行ACAO都不用写——跨域漏洞在架构层面就不可能存在。

顺便解决一个高频困惑:如果你本地开发用的是vite proxy或webpack devServer.proxy,那你本地跑的就是方案 A。上线如果不继续用方案 A,等于换了一套架构——这就是「本地好、上线炸」最常见的原因。

6.3 方案 C:nginx 正确写法 + 三个坑

# ① 白名单用 map,必须放在 http 块(不能放 server / location) map $http_origin $cors_origin { default ""; # 不匹配 → 空值 "~^https://app\.example\.com$" $http_origin; # 注意 ^ 和 $ 锚定 "~^https://admin\.example\.com$" $http_origin; } server { location /api/ { # ② 先剥掉后端可能已经输出的 CORS 头,保证"只有一个地方负责" proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Credentials; # ③ 预检短路:不打扰后端,天然绕开鉴权链 if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Credentials true always; add_header Vary Origin always; add_header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type,Authorization" always; add_header Access-Control-Max-Age 600 always; add_header Content-Length 0; return 204; } add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Credentials true always; add_header Vary Origin always; add_header Access-Control-Expose-Headers "X-Total-Count,Content-Disposition" always; proxy_pass http://backend:8080; } }

坑 1:不加always,出错时头就消失了。

nginx 的add_header默认只对 2xx / 3xx 生效。不加always,后端返回 401 或 500 时 CORS 头不输出,浏览器于是报一个 CORS 错误,把真正的 401 完全掩盖掉。你会花两小时查跨域,实际问题是 token 过期。always不是可选项。

坑 2:if块里的add_header不继承外层。

nginx 的继承规则是:当前层只要写了任何一条add_header,就完全不继承上层的。所以 OPTIONS 分支里必须把ACAO、Credentials、Vary全部重写一遍。少写一个,预检就挂,而主请求看起来完全正常。

坑 3:白名单必须用map,别用if拼字符串。

map的妙处:不匹配时值为空字符串,而nginx 对空值的add_header会直接跳过不输出。这天然实现了「白名单外不给头」,不需要额外判断。

正则一定要^...$锚定。写成~app\.example\.com会被https://app.example.com.evil.com匹配上——这就是扫描器最常报的那类漏洞。

6.4 方案 B:应用层正确写法

// Spring:注意是 setAllowedOrigins,不是 setAllowedOriginPatternsCorsConfigurationconfig=newCorsConfiguration();config.setAllowedOrigins(List.of("https://app.example.com","https://admin.example.com"));config.setAllowedMethods(List.of("GET","POST","PUT","PATCH","DELETE"));config.setAllowedHeaders(List.of("Content-Type","Authorization"));config.setAllowCredentials(true);config.setMaxAge(600L);

白名单字符串本身也是坑源,以下写法都会静默失配(不报错,就是不匹配,超难查):

错误写法问题
https://app.example.com/末尾多了斜杠。Origin 永远无斜杠、无路径
https://app.example.com:443不该写默认端口,Origin 会省略:443/:80
http://app.example.com协议写错
https://APP.example.comhost 必须小写
https://example.com漏了子域

6.5 三条 curl,上线前必跑

# ① 简单请求:合法 origin 必须通过curl-s-D--o/dev/null https://api.example.com/api/list\-H"Origin: https://app.example.com"# 期望:ACAO 精确等于 https://app.example.com,且有 Vary: Origin# ② 预检:最容易漏的一环curl-s-D--o/dev/null-XOPTIONS https://api.example.com/api/item\-H"Origin: https://app.example.com"\-H"Access-Control-Request-Method: PUT"\-H"Access-Control-Request-Headers: content-type,authorization"# 期望:2xx/204 + Allow-Methods 含 PUT + Allow-Headers 含两者# 若返回 401/403 → 鉴权链拦了预检# ③ 反向验证:恶意 origin 必须不被回显curl-s-D--o/dev/null https://api.example.com/api/list\-H"Origin: https://evil-random-9f8a7b.com"# 期望:响应里没有任何 Access-Control-Allow-* 头

再跑一组绕过变体,这些是扫描器真正在找的东西:

foroin\"https://app.example.com.evil.com"\"https://evil-app.example.com"\"https://app.example.com:1337"\"http://app.example.com"\"null";doecho"---$o"curl-s-D--o/dev/null https://api.example.com/api/user\-H"Origin:$o"|grep-i"access-control-allow"done

任何一个被回显,都说明白名单匹配逻辑写错了。

① ② 防「上线报错」,③ 防「上线埋雷」。三条必须都跑。

因为它们防的是方向相反的两种错误——只跑前两条,你会一路"优化"到反射 Origin。


七、渊源:这套机制是怎么长成今天这样的

理解历史包袱,很多"设计得怪怪的"地方就说得通了。

1995 — 同源策略诞生。Netscape Navigator 2.0 引入 JavaScript,同时就引入了同源策略。它是整个 Web 安全模型的基石,比 CORS 早了十年。

2005 前后 — JSONP 时代。跨域读数据没有正规途径,于是有了 JSONP:利用<script>标签不受同源策略约束的特性,让服务端返回一段callback({...})的 JS 代码来"偷渡"数据。

它能用,但代价很大:只支持 GET,无法处理错误,而且本质上是让第三方在你的页面里执行任意代码。JSONP 的种种不堪,直接催生了对正规方案的需求。

2005 — W3C 第一版草案。最初叫Authorizing Read Access to XML Content,还带着浓重的 XML 时代气息。

2009 — 浏览器各自为政。IE8 搞了个自己的XDomainRequest,功能残缺(不支持自定义头、不支持凭证),Firefox / Safari 走的是标准 CORS 路线。这段分裂期是很多老代码里奇怪 polyfill 的来源。

2014 年 1 月 16 日 — CORS 成为 W3C 正式推荐标准。这是 CORS 的"成年礼",各大浏览器实现开始统一。

2020 年 6 月 2 日 — W3C CORS 规范退役。内容并入 WHATWG 的Fetch Living Standard。所以今天你要查权威定义,别翻 W3C 那份,去看 Fetch 标准——这也是为什么 MDN 上的 CORS 文档链接都指向 Fetch。

2020 至今 — 收紧期。浏览器发现「服务端主动声明」这个模型太依赖开发者不犯错,于是开始从客户端加码:

  • Chrome 80(2020)起,Cookie 的SameSite默认值变为Lax。这意味着跨站请求默认不再自动携带 Cookie——大量原本"能用"的跨域方案在这次变更中集体崩溃,必须显式写SameSite=None; Secure。
  • COOP / COEP / CORP一系列新头出现,用于隔离进程、防御 Spectre 类侧信道攻击。
  • Private Network Access → Local Network Access。最初的设计是「对内网请求强制 CORS 预检、由内网设备自己 opt-in」,但推行不下去(内网设备很难上 HTTPS)。于是 Chrome 改变策略:从 Chrome 142 起,公网站点访问用户本地网络需要弹窗获得用户许可,不再依赖设备自己声明。

这条演进线的方向很明确:从「完全信任服务端的声明」,逐步走向「浏览器和用户拥有更多否决权」。因为二十年的实践证明了一件事——服务端的 CORS 配置,出错的概率实在太高了。


八、总结:关键点回顾

一句话总结

CORS 是服务端向浏览器出示的一张「许可便条」,声明「哪些源的 JS 可以读我的响应」。它由服务端声明、浏览器执行,因此拦不住任何攻击者,只能保护用户的凭证不被第三方站点盗用。

十条 Takeaway

  1. 同源 = 协议 + 域名 + 端口,三者全同。路径不算,默认端口省略,Origin 结尾没有斜杠。
  2. CORS 不阻止请求,只阻止 JS 读取响应。请求早就到服务端了,副作用也已经发生了。
  3. 服务端只是声明,浏览器才是执行者。所以 curl / Postman 完全无视 CORS——它们能通不代表配置正确。
  4. Postman 能通、浏览器不通 = 后端逻辑全对,只缺响应头。这其实是个好消息。
  5. CORS 不是访问控制。接口安全靠鉴权,两条独立的线,别指望 CORS 挡住攻击者。
  6. 反射 Origin +Allow-Credentials: true= 高危漏洞。等于亲手关掉同源策略,且功能上毫无异常,这是它能潜伏半年的原因。
  7. GET正常但POST/PUT报错,先查预检。OPTIONS不带 Cookie 和 Authorization,必须在鉴权之前放行。
  8. CORS 头只能有一个地方负责输出。重复输出 = 直接失败,且 curl 看不出来。
  9. always+Vary: Origin是 nginx 里两个最容易漏、后果最诡异的配置。前者导致「401 伪装成 CORS 错误」,后者导致「时好时坏」。
  10. 最好的 CORS 配置,是通过同源反代让它根本不需要出场。

最后一句

如果你今天只带走一件事,请带走这个:

修 CORS 报错时,别以「让红字消失」为目标,要以「让正确的 origin 通过」为目标。

前者的最优解是反射 Origin,五分钟解决问题,半年后变成一份高危报告。
后者的最优解是显式白名单,多花半小时,然后再也不用想它。


Learn more:

  1. Cross-Origin Resource Sharing
  2. Cross-Origin Resource Sharing publication history
  3. W3C Wiki
  4. Cross-Origin Resource Sharing
  5. wikipedia.org
  6. New permission prompt for Local Network Access
  7. Nuevo mensaje de permiso para el acceso a la red local
  8. The Private Network Access (PNA) for non-secure contexts deprecation trial is ending—implement the PNA permission prompt
  9. Local network access restrictions
  10. docs.stripe.com

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询