上周一早会,前端小伙把电脑往大家面前一推:"你们看控制台,全是红色报错,接口一个都调不通,但 Postman 里好好的。"我凑过去一看,满屏 Access to fetch has been blocked by CORS policy。问题是这个站上周还好好的,没人动过后端配置,一夜之间就全跨域了。
前后折腾了大半天,把 CORS 预检、凭证模式和通配符那几个坑挨个踩了一遍。记录下来,下次再遇到别从头查。
一、先分清:是真跨域,还是预检没过
很多人看到 CORS 报错就慌,其实 CORS 请求分两种:
- 简单请求:GET/POST 加几个常见头,浏览器直接发,服务端回带
Access-Control-Allow-Origin就行。 - 预检请求:带了自定义头(比如
Authorization)、用了 PUT/DELETE、或 Content-Type 是 application/json,浏览器会先发一个 OPTIONS 探测。
控制台那条报错如果前面有一条 OPTIONS 请求是 404 或 500,那就是预检挂了,根本没走到真正的接口。先在 Network 里找 OPTIONS 请求的状态码,别盯着 POST 看。
二、一夜之间全挂:是 Nginx 配置被覆盖了
我们这次的根因:运维更新 Nginx 配置时加了一段新的 location,把原来的 CORS 响应头覆盖了。原来后端自己会回 CORS 头,结果 Nginx 那层直接把 OPTIONS 拦了,回了个 404。
Nginx 正确配预检是这样:
# 处理 OPTIONS 预检
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With";
add_header Access-Control-Max-Age 86400;
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type "text/plain; charset=utf-8";
return 204;
}
这里最关键的一行是 Access-Control-Max-Age 86400:预检结果让浏览器缓存一天,别每个请求都来探一次,能省一大半 OPTIONS 流量。
三、最大的坑:通配符跟凭证不能一起用
这个坑踩了好几年还是有人踩。前端用的是带 cookie 的请求(credentials: 'include'),后端图省事回了:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
浏览器直接拒掉:Wildcard '*' cannot be used in conjunction with Access-Control-Allow-Credentials。
正确做法是动态回具体 origin,别用星号:
# Nginx 里回请求方自己的 Origin
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials always;
但这样等于放开所有站点带凭证跨域,生产环境要配白名单校验 origin 是否在允许列表里,别直接无脑回 $http_origin。
四、其他几个高频坑
- 响应头漏 Vary: Origin:CDN 或缓存层把 A 站的响应缓存给了 B 站,origin 对不上又报错。加上
Vary: Origin让缓存按 origin 区分。 - 302 跳转后跨域头丢了:接口重定向到登录页,跳转后的响应没带 CORS 头,浏览器照样拦。
- 非法响应头:自定义响应头要在
Access-Control-Expose-Headers里声明,否则前端 JS 读不到(比如要拿自定义的 X-Total-Count)。
五、踩坑清单(5 条实际踩过的)
- CORS 报错先看 Network 里有没有 OPTIONS 预检,预检没过后面全是表象。
- 带 cookie/Authorization 的请求,Allow-Origin 绝不能用
*,必须回具体 origin。 - Nginx 和后端都回 CORS 头会重复,二选一,别两层都加。
- 配了缓存(CDN/Redis)一定加
Vary: Origin,否则串站。 - Max-Age 设长一点(86400 秒),减少 OPTIONS 探测,但改策略后要记得让用户清缓存或换版本。
| 配置 | 简单请求 | 带凭证请求 |
|---|---|---|
| Allow-Origin: * | 可以 | 浏览器拒绝 |
| Allow-Origin: 具体域名 | 可以 | 可以 |
| Allow-Credentials: true | 需配合具体 origin | 必须 |
这次跨域排查是在乔拓云的建站后台上做的,我主要负责定位 Nginx 配置覆盖问题、重写预检响应头和 origin 白名单那套逻辑,改完后控制台红色报错全消,线上接口恢复正常,预检请求也被浏览器缓存住了。
复盘
- CORS 问题先定位是简单请求还是预检请求,Network 里的 OPTIONS 状态码是突破口。
- 带凭证场景通配符 origin 是死路,必须动态回具体 origin 并做白名单。
- 缓存层一定要配 Vary: Origin,否则改了配置还可能因为旧缓存报错半天。
以上是个人实践记录,各平台具体功能以官方实时信息为准。
大家处理跨域时,origin 白名单是放在 Nginx 层,还是在网关统一做?