服务端调用 DeepSeek API 返回 401 Unauthorized,通常不是单一原因,而是鉴权链路中某一环被污染或改写。排查时应把「密钥本身是否有效」与「密钥是否被正确送达服务端」分开验证,否则容易在错误方向上反复重试。
说明:本文涉及鉴权头格式的部分,按常见 Bearer Token 鉴权模型分析,具体请求头字段与 Token 格式请以 DeepSeek 官方文档为准。当前没有官方一手资料可引用,因此不将具体格式作为已验证的 DeepSeek 产品事实。
一、先确认鉴权头的构造形态
若目标接口采用 Bearer Token 鉴权(请以官方文档为准),请求头通常形如 Authorization: Bearer。常见错误有三类:
- 缺少
Bearer前缀,直接写入密钥; - 前缀与密钥之间缺少空格,或使用了全角空格;
- 密钥字符串本身被引号包裹,例如
Bearer "sk-xxx"。
这些错误在本地用 curl 手写时不易出现,但在代码中通过字符串拼接、模板字符串或配置读取时极易引入。建议在发出请求前打印鉴权头的长度与首尾字符,而不是打印完整密钥,避免泄露。
二、换行符与不可见字符污染
从环境变量、.env 文件或配置中心读取密钥时,最常见的污染是尾部换行符 \n、回车符 \r 或首尾空格。这类字符在日志中不可见,但会直接导致服务端比对失败。
排查方法:对读取到的密钥做 trim() 处理,并检查其字节长度是否与预期一致。如果密钥来自文件,注意文件末尾是否有多余空行;如果来自 CI/CD 变量,注意平台是否自动追加换行。
另一个隐蔽问题是编码:某些配置中心返回的字符串可能带有 BOM 头或非 ASCII 空白字符。可用十六进制方式打印密钥首尾若干字节,确认没有异常字节。
三、环境变量加载时序
在容器化部署中,401 常源于密钥未在进程启动前注入。典型场景:
- 应用在模块顶层读取
process.env.DEEPSEEK_API_KEY,但容器编排的 env 注入发生在进程启动之后; - 使用
.env文件加载库时,加载顺序晚于首次 API 调用; - 多环境配置覆盖,测试环境的空值覆盖了生产密钥。
建议将密钥读取延迟到实际发起请求时,或在应用启动阶段做一次显式校验:若密钥为空或长度异常,直接 fail-fast 并输出明确错误,而不是等到第一次调用才返回 401。
比对本地与容器环境差异时,可在容器内执行 env | grep -i key 或等价命令,确认变量存在且值非空。注意不要将完整密钥输出到共享日志。
四、网关与代理重写请求头
当请求经过 API 网关、反向代理或服务网格时,401 可能由代理层引起:
- 代理未透传
Authorization头,或将其加入黑名单; - 代理对请求头做了大小写归一化,而下游校验逻辑区分大小写;
- 代理重写了
Host或路径,导致请求被路由到需要不同鉴权的后端; - 代理自身要求鉴权,返回的 401 并非来自 DeepSeek API。
区分方法:查看响应体与响应头。若响应头出现代理特有标识(如特定 Server、Via、X-Forwarded-* 或认证域指向代理),可怀疑代理层;但 WWW-Authenticate 是 401 响应的标准头部之一,并非代理特有,仅凭该头部存在不能断定拦截发生在代理层。仍需结合出站请求头与实际链路证据确认。此时应抓取实际出站请求头,确认 Authorization 是否原样到达目标服务。
五、最小 HTTP 客户端隔离验证
当上述环节都无法定位时,用最小 HTTP 客户端做隔离验证:
- 在相同运行环境(同一容器、同一网络出口)中,用 curl 或几行代码直接向 DeepSeek API 发起请求;
- 手动写入密钥,不经过任何配置加载逻辑;
- 观察是否仍返回 401。
若手动请求成功,说明密钥有效,问题在配置加载或代理链路;若手动请求同样失败,则密钥本身可能已失效、被撤销或复制错误。
六、排查顺序建议
按以下顺序推进,可最快缩小范围:
- 抓取实际出站请求头,确认
Authorization存在且格式正确; - 检查密钥首尾是否有不可见字符,做 trim 与长度校验;
- 比对本地与容器环境变量,确认注入时序与覆盖关系;
- 检查网关/代理是否透传或改写鉴权头;
- 用最小 HTTP 客户端在相同环境隔离验证密钥有效性。
401 的根因通常落在「密钥失效」「编码污染」「代理改写」三者之一。把鉴权头的构造与密钥的注入路径分开验证,比反复更换密钥更有效。