接入时反复收到鉴权失败,通常不是模型侧拒绝,而是密钥没被程序读到、请求头字段名或前缀不一致、或者环境变量取值串了。判断顺序建议是:先用最小请求确认密钥确实被读到;再对比不同环境下请求头字段名与大小写;接着在日志里看鉴权失败响应码和报文;最后用本地中间件看程序实际发出的请求。密钥和请求头不是二选一,但排查上先查密钥读取更省时间:密钥为空、为占位符或读错变量时,请求头写得再对也会失败。边界是,不同接入方对字段名、前缀和大小写要求不同,需要结合你的环境确认。
最小请求用显式密钥能通、用环境变量不通,问题通常在密钥读取或环境隔离;两种都失败,再查请求头字段名、前缀和大小写。看响应码可以区分:401 多与密钥缺失或无效有关,403 多与权限或访问范围有关,400 可能是请求头字段缺失。不要只改请求头就下结论。
用最小请求验证密钥是否被正确读取
先排除密钥本身没有被程序读到。建议在命令行里显式带一次密钥发最小请求,再让程序走环境变量发同样的请求,对比结果。请求路径和字段名按你的接入方替换,下面只是通用骨架,不保证与具体服务一致。
export GROK_API_KEY='你的密钥'
curl -i -X POST 'https://<host>/<path>' -H "Authorization: Bearer ${GROK_API_KEY}" -H "Content-Type: application/json" -d '{"input":"ping"}'
再在项目启动处打印密钥来源,不要打印完整值。看变量是否存在、长度是否符合预期、前后几位是否是你配置的那把。
import os
key = os.environ.get('GROK_API_KEY')
print('env name=', 'GROK_API_KEY')
print('exists=', key is not None)
print('len=', len(key) if key else 0)
print('preview=', (key[:4] + '...' + key[-4:]) if key and len(key) > 8 else 'too_short')
验证方式很直接:显式密钥通过、环境变量失败,就查 shell 是否 export、容器是否注入、systemd 或面板是否覆盖、.env 加载顺序是否晚于读取代码。风险边界是,密钥可能被平台脱敏或自动改写,需结合运行环境确认。
对比不同环境下请求头字段名与大小写
如果密钥确实读到了,接着确认请求头拼装是否一致。常见写法有 Authorization: Bearer <token>、Authorization: <token>、x-api-key: <token>、api-key: <token>、X-Api-Key。HTTP/1.1 头名不区分大小写,但部分网关或框架会按原样读取;HTTP/2 下头名要求小写。别在本地用一种写法,线上又换成另一种。
import requests
headers = {
'Authorization': 'Bearer ' + os.environ.get('GROK_API_KEY', ''),
'Content-Type': 'application/json',
}
req = requests.Request('POST', url, headers=headers, json={'input': 'ping'})
prepared = req.prepare()
for k, v in prepared.headers.items():
shown = v if k.lower() not in ('authorization', 'x-api-key', 'api-key') else v[:8] + '...'
print(repr(k) + ': ' + repr(shown))
这段日志用来确认实际拼出的头名、大小写、Bearer 后有没有空格、是否多带了同义头。建议把拼装收进一个函数,不同环境只改环境变量,不改字段名。检查点包括:字段名是否与接入方要求一致、前缀是否多余或缺失、值前后是否有空格或换行、是否把空字符串当成了有效密钥。
在日志里确认鉴权失败响应码与报文
响应码和响应体能区分密钥错误、请求头缺失和权限不足。401 多与密钥缺失或无效有关;403 多与权限、项目或访问范围有关;400 可能是请求头字段缺失或格式不对。记录时不要写完整 Authorization,保留状态码、响应体前几百字符和请求 ID(如果有)。
import logging
logger = logging.getLogger('grok_client')
def log_response(resp, request_id=None):
body = resp.text[:500]
logger.warning(
'auth_failed status=%s request_id=%s body=%s',
resp.status_code,
request_id,
body,
)
logger.warning('request header names=%s', list(resp.request.headers.keys()))
验证方式是同一密钥分别跑最小请求和系统请求。最小请求成功、系统请求 401,优先查请求头拼装和环境取值;两者都 401,先查密钥和字段名;返回 403 时,密钥可能有效但访问范围不对,需要结合接入方的权限配置确认。边界是,响应体可能被网关改写,不能只看错误文案。
用本地中间件复现已发送请求
想看到程序实际发出的请求内容,可以把 base_url 临时指向本机回显服务。它只记录请求头与请求体,不验证对方服务的真实鉴权逻辑,验证完要改回线上地址。下面是一个通用做法,不涉及具体第三方服务。
from http.server import BaseHTTPRequestHandler, HTTPServer
class Echo(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(length)
print('PATH', self.path)
for k, v in self.headers.items():
shown = v if k.lower() not in ('authorization', 'x-api-key', 'api-key') else v[:8] + '...'
print('H', repr(k), repr(shown))
print('BODY', body[:500])
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(b'{"ok":true}')
HTTPServer(('127.0.0.1', 18080), Echo).serve_forever()
执行后把客户端 base_url 指向 http://127.0.0.1:18080,再发一次系统请求。检查点:Authorization 是否存在、字段名是否和线上一致、Bearer 前缀是否被程序丢掉、环境变量是否被读取成空字符串、请求体是否被中间层改写。风险边界是,本地看到的请求不代表对方一定接受,它只回答“程序实际发了什么”。
整理可复用的配置模板与检查清单
把这次排查结果固化下来,下次接入先跑最小请求,再看请求头和日志。环境变量、请求头和最小请求可以统一成下面这套模板,字段名和路径按接入方替换。
# .env.example
GROK_API_KEY=
GROK_BASE_URL=https://<host>/<path>
GROK_AUTH_HEADER=Authorization
GROK_AUTH_SCHEME=Bearer
import os
key = os.environ['GROK_API_KEY']
header_name = os.environ.get('GROK_AUTH_HEADER', 'Authorization')
scheme = os.environ.get('GROK_AUTH_SCHEME', 'Bearer').strip()
value = (scheme + ' ' + key).strip() if scheme else key
headers = {
header_name: value,
'Content-Type': 'application/json',
}
curl -i -X POST "$GROK_BASE_URL" -H "$GROK_AUTH_HEADER: $GROK_AUTH_SCHEME $GROK_API_KEY" -H "Content-Type: application/json" -d '{"input":"ping"}'
检查清单可以按顺序过:密钥来源是否唯一,是否存在两套变量名;环境变量是否为空、是否仍是占位符;请求头字段名和大小写是否与接入方要求一致;Bearer 前缀和空格是否正确;日志是否脱敏;失败时是否记录了状态码和响应体;本地回显看到的请求头是否与最小请求一致。适用场景是接入方字段名、前缀和环境变量命名不完全一致,验证方式就是显式密钥最小请求与系统请求对比。风险边界是,模板不能替代接入方文档或实际权限配置,遇到 403 时仍需结合环境确认访问范围。