AI API 命令行能用,网页却报 CORS?跨域错误与预检请求排查教程
API 在 curl 中正常,网页却报跨域错误?通过 OPTIONS、Origin 和响应头区分预检失败与真实接口错误,讲清后端转发及密钥保护。
AI API 命令行能用,网页却报 CORS?跨域错误与预检请求排查教程
同一个 API 地址和模型,用命令行可以拿到回答,放进网页却出现 blocked by CORS policy 或 TypeError: Failed to fetch。这不一定是密钥失效,也不意味着必须充值或更换服务商:浏览器对跨域读取有额外限制,而 curl 等命令行客户端不会按浏览器的 CORS 规则拦截响应。
这篇教程帮助你区分网络故障、预检失败和响应不可读。它讲解通用 HTTP 行为,不是对某一家 API 中转站的实测,也不假设所有服务商都允许浏览器直接调用。
一、先确认是不是跨域问题
“源”由协议、主机名和端口共同决定。https://app.example.com 与 https://api.example.com 不同源;同一域名的 HTTP 和 HTTPS 不同源;开发环境的 http://localhost:3000 与 http://localhost:3001 也不同源。
浏览器从一个源请求另一个源的数据时,会检查服务端是否允许当前网页读取响应。CORS 是浏览器实施的访问机制,不是 API 密钥验证机制。命令行能调用,只能证明那次请求成功,不能证明网页请求也被允许。
打开浏览器开发者工具的 Console 和 Network,重新触发一次短请求,按下面的情况分流:
| 观察结果 | 优先检查 |
|---|---|
| Console 明确提示 CORS,Network 出现 OPTIONS | 预检状态和允许来源、方法、请求头 |
| OPTIONS 通过,但 POST 仍提示跨域 | 实际响应及错误响应是否也带正确的 CORS 头 |
| POST 可读取,返回 JSON 格式的 401/403 | 按鉴权或权限错误排查,不是同一类故障 |
| 只有 Failed to fetch,没有具体 CORS 说明 | DNS、TLS、连接中断、内容安全策略和浏览器拦截 |
| HTTPS 网页请求 HTTP API | 先排查混合内容限制,不能靠增加 CORS 头解决 |
Network 信息有时不完整,要结合 Console 提示与服务端日志判断。不要把所有网络错误都归结为跨域。
二、OPTIONS 是预检,不是第二次模型调用
网页跨域发送带 Authorization 的请求,或发送 Content-Type: application/json 的 POST,通常会先触发预检。浏览器用 OPTIONS 询问:这个来源能否使用 POST,并携带这些请求头?
典型预检包含 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers。它不是让模型生成内容的请求;正常实现不应把 OPTIONS 当成推理任务或计入生成扣费。是否被服务商的请求总数统计,需要另行确认。
如果预检没有通过,浏览器通常不会发送后续业务请求。常见原因包括:网关不接受 OPTIONS、把预检跳转到登录页、鉴权中间件要求预检也带业务密钥,或只允许了错误的来源。
预检本身不应被要求携带模型调用的 Bearer 密钥。允许受控的预检,并不等于取消实际 POST 的鉴权;两者应分别处理。
三、不带密钥检查预检响应
下面是一条诊断命令。两个域名都是示例:把第一个改成服务商文档中的完整 API 地址,把 Origin 改成实际网页的源,不附带路径或末尾斜杠。只向你有权使用的接口发送检查请求。
curl -i -X OPTIONS "https://api.example.com/v1/chat/completions" -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: authorization,content-type"
Windows PowerShell 中可把命令开头的 curl 改成 curl.exe,其余保持不变。该命令不包含 API Key,也不发送用户问题。
对于这个示例,一种允许结果可能是:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, content-type
Vary: Origin
状态也可能是其他成功状态,不能只认 204。重点是允许的来源是否匹配,以及方法、请求头是否覆盖实际请求。Authorization 应显式列在允许头中,不要以为一个通配符就能覆盖所有情况。
若服务端根据请求 Origin 动态选择响应来源,应使用经过核准的来源白名单,并正确设置 Vary: Origin,避免缓存把某个来源的响应头错发给另一个来源。不要直接把任意 Origin 原样反射回去。
如果浏览器请求使用 credentials: 'include',还需检查 Access-Control-Allow-Credentials: true,此时允许来源不能使用 *。Cookie 是否真的随请求发送,还受 SameSite 和浏览器第三方 Cookie 策略影响。不要为了消除报错就无理由启用 credentials。
curl 不执行浏览器 CORS 检查,因此这条命令只能帮助查看服务端返回了什么,不能代替真实浏览器验收。另外,预检通过后,实际 POST 响应也要带匹配的允许来源头;401、429、5xx 等错误响应也应按相同来源策略处理,否则前端可能只看到跨域错误而看不到真正原因。
四、建网站时,把平台密钥留在后端
如果你为自己的网站付费调用 AI,通常应采用以下链路:
用户浏览器 → 你的网站后端 → 服务商 API
浏览器只请求你自己网站的接口,平台 API Key 存在后端环境变量或密钥管理系统中。后端向服务商发请求,不受浏览器 CORS 机制限制,但仍需处理网络、TLS、鉴权、额度和超时问题。若浏览器和你自己的后端也不同源,那一段仍要配置 CORS。
后端接口至少需要这些约束:
- 验证用户身份和调用权限;采用 Cookie 会话时,同时处理 CSRF 防护。
- 固定或白名单限制上游地址和模型,禁止用户随意传入转发目标。
- 设置单用户频率、并发、费用和请求体大小限制。
- 控制超时和输出预算,避免无限重试。
- 不把密钥、上游完整错误页面或敏感对话写进公开日志。
否则只是把“浏览器暴露密钥”变成了“任何人都能消耗你的后端额度”。CORS 不能代替这些后端保护,因为其他程序不需要遵守浏览器规则。密钥存放方式可继续看 API Key 安全指南。
五、这几种做法解决不了根本问题
- 设置
mode: 'no-cors': 跨域响应会变成 opaque,JavaScript 无法读取其响应体和头部,请求方法与请求头也受限制,不能用于正常读取聊天 JSON。 - 前端添加
Access-Control-Allow-Origin: 这是服务端响应头,前端自己加同名请求头不会获得读取权限,还可能增加预检负担。 - 在网页源码中硬编码平台密钥: 打包、压缩和混淆无法把已发到浏览器的密钥变成秘密。
- 要求用户关闭浏览器安全检查或安装绕过插件: 这不是可交付给普通用户的网站修复方案。
- 看到跨域报错就立即重试: 如果实际 POST 已经发送,只是浏览器不能读取响应,上游仍可能执行并产生费用。
因此,先在 Network 中判断只有 OPTIONS,还是已经发送了业务 POST,再结合服务商调用记录确认执行状态,避免重复请求。
六、修复后的验收清单
使用一条短请求和测试账户,检查允许来源能否调用、未允许来源能否读取、错误提示是否可见。未允许来源被浏览器阻止读取,不等于后端一定没有执行,请同时检查服务端的身份和权限控制。
上线前确认:真实密钥不在网页文件与浏览器请求中;自己的后端限制了上游目标;测试过预检和实际响应;401、429、5xx 有可读提示;日志不包含密钥。非跨域的调用问题可以继续按 API 调用失败排查流程定位。
向服务商反馈时,提供时间、网页 Origin、接口域名与路径、OPTIONS/POST 状态和脱敏后的响应头即可。不要直接公开导出的 HAR 文件;它可能带有 Authorization、Cookie、查询参数或请求正文。
参考资料(2026-09-30 查阅):MDN:CORS、MDN:预检请求、MDN:Request.mode。实际 API 路径、鉴权方式和允许来源政策,以服务商当前文档为准。