浏览器跨域报错:CORS与预检请求排查
接口在命令行能返回数据,网页里的 fetch 或 XMLHttpRequest 却提示 CORS,并不矛盾。curl 不执行浏览器的跨域读取限制。需要检查的是浏览器实际请求到了哪里,以及那一层返回了什么响应头,不能只凭 curl 成功就认定前端写错。
来源由协议、主机名和端口共同决定。https://www.example.com与https://api.example.com属于不同来源;同一域名换了端口也可能跨域。本文的域名和 /api/status都是示例,操作时替换为自己项目的只读接口。
先找到报错的那一次请求
打开浏览器开发者工具的“网络”,保留日志后重新触发一次操作。记录页面来源、接口完整地址、请求方法和状态码,再看同一地址前面是否出现 OPTIONS。不要仅复制控制台的一句“跨域错误”。
请求带 Authorization、使用某些请求方法或内容类型时,浏览器可能先发预检。没有 OPTIONS 不一定异常:部分请求不需要预检,也可能命中了预检缓存。证书错误、DNS 失败、连接超时应先处理;它们不会因为补一个 CORS 头就恢复。

宝乐云请求流程示意。预检和实际响应分别检查;部分跨域请求不会经过预检。
用OPTIONS检查服务器允许什么
下面在 Linux 或 macOS 终端执行,模拟“带 Authorization 请求头的 GET 接口”预检。只写请求头名称,不填写真实令牌。方法、来源、路径和请求头名称,要与浏览器记录一致:
curl -q -sS --max-time 15 -D - -o /dev/null \
-X OPTIONS 'https://api.example.com/api/status' \
-H 'Origin: https://www.example.com' \
-H 'Access-Control-Request-Method: GET' \
-H 'Access-Control-Request-Headers: authorization'
正常的预检应返回成功状态,并允许对应来源、方法和请求头。下列是与本例匹配的响应结构;具体状态也可能是其他合适的 2xx:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: authorization
Vary: Origin
允许的方法应包含真正要发出的 GET,不能只写 OPTIONS。浏览器若请求 content-type等额外头,也要按业务需要加入允许列表。返回允许来源时不带路径,不能用逗号把多个域名塞进同一个值。
| 看到的结果 | 优先处理 |
|---|---|
| OPTIONS 返回 401、403 或跳到登录页 | 检查代理或认证中间件是否把预检当成业务登录。让正确的 CORS 处理逻辑先处理允许的预检,实际业务请求仍须认证。 |
| OPTIONS 返回 404、405 | 确认路由和处理层支持预检;不要把它转到前端首页。 |
| 有允许头,仍提示不匹配 | 逐字核对协议、域名、端口、实际方法和请求头名称。 |
| 出现两份允许来源头 | 检查应用和代理是否重复添加,选定一层统一生成策略。 |
预检通过,还要检查实际响应
用同一只读地址查看实际响应头。下面不携带登录信息,需要认证的接口返回 401 可以是正常结果;这里重点看错误响应有没有正确的 CORS 头:
curl -q -sS --max-time 15 -D - -o /dev/null \
-H 'Origin: https://www.example.com' \
'https://api.example.com/api/status'
如果实际接口是 500、502,先按同一时间核对Nginx 与应用日志。网关错误页缺少允许头,可能让浏览器只显示跨域提示,真正故障仍在后端。若接口返回的是网页 HTML,检查是否被单页应用的回退规则接走,可参考前端上线与API路由处理。
生产接口应维护明确的来源白名单,只为匹配的来源生成响应。不要把收到的任意 Origin 原样回显,也不要为省事允许所有网站读取私有接口。响应按来源变化时,缓存还需正确处理 Vary: Origin,避免把一个来源的结果错误复用给另一个来源。
带登录状态时,分别核对Cookie和跨域策略
跨域使用 Cookie 时,前端需要正确设置凭据选项,服务端允许来源必须是明确值,并按需要返回 Access-Control-Allow-Credentials: true,不能搭配 *。跨站 Cookie 还受 SameSite、Secure 和浏览器第三方 Cookie 策略限制;CORS 通过不等于 Cookie 一定会发送。
修改后回到浏览器,用测试账号重新检查预检、实际状态、响应内容和登录行为;预检有独立缓存,普通刷新不一定重新发送 OPTIONS。最终还要确认未授权来源没有获得读取权限。不要用关闭浏览器安全功能或 mode: 'no-cors'绕开报错,后者通常只得到脚本无法读取内容的不透明响应。
CORS 不是身份认证,也不是防跨站请求伪造措施。即使脚本读不到响应,请求也可能已被执行,接口仍需正常校验身份、权限及业务操作。