API 返回 401 的密钥配置与鉴权排查
调用 API 时返回 401,通常不是模型或参数问题,而是鉴权链路中某个环节没有把正确的密钥送到服务端。本文从请求头、环境变量、多环境覆盖、代理转发四个层面拆解 401 的触发条件,并给出可复现的排查步骤。
说明:本文不绑定某一具体厂商的端点、模型名或密钥前缀。文中出现的 URL、模型名、密钥格式均为占位示例,实际值请以目标 API 的官方文档为准。
一、先确认 401 的语义边界
401 Unauthorized 表示服务端没有收到有效凭证,或凭证格式不被接受。它与 403 不同:403 是身份已识别但无权限,401 是身份本身未被确认。因此排查方向应集中在“密钥是否发出、格式是否正确、是否被中途替换或丢弃”,而不是模型权限或配额。
二、Authorization 头与 Bearer 前缀
多数 HTTP API 采用 Bearer 鉴权方案,请求头形如:
Authorization: Bearer
但具体方案、头名称、前缀写法与密钥格式必须以目标 API 官方文档为准。常见错误包括:
- 缺少
Bearer前缀,只写了密钥本身; Bearer与密钥之间缺少空格;- 大小写错误,如
bearer或BEARER(部分网关对大小写敏感); - 密钥前后混入换行、空格或引号,尤其在从文件读取时;
- 把密钥写进了
api-key、x-api-key等非标准头,而目标 API 实际要求的是Authorization。
排查时先用 curl 直接构造请求,排除 SDK 封装带来的干扰:
curl -sS -o /dev/null -w "%{http_code}\n" \
https:/// \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{"model":"","messages":[{"role":"user","content":"ping"}],"max_tokens":1}'
如果 curl 返回 200 而应用返回 401,问题在应用侧;如果 curl 也返回 401,问题在密钥或网络链路。
三、环境变量加载顺序与多环境覆盖
应用侧 401 最常见的原因是环境变量没有按预期生效。需要检查:
- 加载时机:
.env文件是否在读取密钥之前被加载。若在模块导入后才调用load_dotenv(),读取到的仍是空值。 - 覆盖顺序:Shell 已导出的变量、
.env文件、容器编排注入的变量、CI/CD 密钥,四者优先级不同。后加载的.env可能覆盖掉正确的值,或反之。 - 多环境串扰:本地、测试、预发、生产各自持有不同密钥。若部署时把测试密钥带到生产,或反向带入,都会得到 401。
- 空值与默认值:
os.getenv("API_KEY", "")在变量缺失时返回空串,拼出的头是Bearer,服务端同样返回 401。
建议在启动日志中只打印密钥长度或哈希值,例如 key_len=48 或 key_sha256=ab12...,既能确认加载成功,又不泄露完整密钥。
四、代理与网关转发时的密钥丢失
经过反向代理、API 网关或服务网格时,401 可能由转发环节引入:
- 网关默认剥离
Authorization头,或只转发白名单内的头; - 代理做了头重写,把
Bearer前缀去掉; - 多层代理中某一层未透传该头;
- 网关自身鉴权失败,返回的 401 并非来自目标 API。
区分方法:在应用出口抓包或打印实际发出的请求头,确认 Authorization 是否完整。若经过网关,可临时绕过网关直连 API 域名做对照测试。
五、最小请求体定位法
把变量降到最少,逐步加回,是定位 401 最有效的方法:
- 用 curl 加正确头,请求最小 body,确认密钥本身有效;
- 换用应用中的 HTTP 客户端,保持相同头和 body,确认客户端行为一致;
- 加回 SDK,确认 SDK 是否自动改写头;
- 加回代理或网关,确认转发是否丢头;
- 加回多环境配置,确认最终生效的密钥与预期一致。
每一步只改变一个变量,401 出现的那一步就是问题所在。
六、工程建议
- 在 CI 中增加一条冒烟测试,用最小请求验证密钥可用性,避免部署后才发现 401;
- 对密钥做启动时校验:非空、长度符合预期、来源可追溯,不合法直接 fail fast。不要写死特定前缀(如
sk-),因为不同厂商或不同版本的密钥格式可能不同; - 日志中禁止输出完整密钥,统一脱敏。建议只打印密钥长度或哈希,避免输出密钥前后各 4 位,以降低日志泄露后的关联风险;
- 网关配置中显式声明透传
Authorization,不要依赖默认行为; - 多环境使用独立密钥,并在部署清单中标注来源,减少串扰。
401 本身不复杂,复杂的是密钥从配置到请求头之间经过的每一层。按“头格式—环境变量—代理转发—最小请求”的顺序逐层收敛,通常能在几分钟内定位到具体环节。