给接口加把锁:Bearer Token 鉴权(附冒烟验收)

「从零到 AI 应用工程师」专栏 · 第 8 篇

阶段 1 · chat-api 收官补篇


上一篇用 Docker Compose 把整栈拉起来了。你会发现:几乎每条 curl 都带着这一行------

bash 复制代码
-H "Authorization: Bearer 你的令牌"

却很少有人单独讲清楚:这把锁是怎么挂上的?漏带会发生什么?如何用脚本证明「鉴权真的生效」?

chat-api 阶段再补这一刀:把鉴权做成可复用的依赖,再用一份冒烟脚本当交付验收。做完,阶段 1 才算真正能交出去。


一、今天要解决什么

目标 :未带 Token / Token 错误 → 401;正确 Token → 正常进业务。

最终效果(示意):

bash 复制代码
# 没锁 → 进不去
curl -i -X POST http://127.0.0.1:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u1","session_id":"s1","message":"你好"}'
# HTTP/1.1 401 ...

# 有锁 → 正常
curl -i -X POST http://127.0.0.1:8000/chat \
  -H "Authorization: Bearer 你的令牌" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u1","session_id":"s1","message":"你好"}'
# HTTP/1.1 200 ...

探活接口(/ping、/healthz)可以不鉴权------运维探活和业务读写要分开。


二、Bearer 到底是什么

HTTP 常见做法:在请求头里带:

http 复制代码
Authorization: Bearer <token字符串>
词 含义
Bearer 「持票人」模式:谁持有这串令牌,谁就被当成已授权
Token 一串密钥;本阶段用配置里的固定字符串即可(演示/内网够用)
依赖 Depends FastAPI 在进路由函数之前跑校验;失败直接 401,业务代码零侵入

本阶段不做完整 OAuth2 / JWT 签发体系。先把「接口默认不裸奔」立住;以后再换成验签、过期、多级 Key。

安全底线(写进习惯):

  • Token 放环境变量 / .env,不进 Git、不写进镜像层;
  • 对外文档用 你的令牌 / sk-xxx 占位,不贴真实值;
  • 日志里不要打印完整 Authorization。

三、最小实现:verify_token 依赖

python 复制代码
# core/auth.py(示意)
from fastapi import HTTPException, Request, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

# auto_error=False:不要框架默认文案,自己抛,方便统一成「说人话」的 JSON
security = HTTPBearer(auto_error=False)

# 从配置读取,例如环境变量 API_TOKEN
EXPECTED_TOKEN = "你的令牌"  # 实际:os.getenv("API_TOKEN")


async def verify_token(request: Request):
    credentials: HTTPAuthorizationCredentials | None = await security(request)
    if not credentials or credentials.credentials != EXPECTED_TOKEN:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="无效或未提供 Token",
            headers={"WWW-Authenticate": "Bearer"},
        )

挂到路由上------两种常见写法:

python 复制代码
# 写法 A:整个路由模块统一要鉴权
router = APIRouter(
    prefix="/chat",
    tags=["对话"],
    dependencies=[Depends(verify_token)],
)

# 写法 B:单个接口要鉴权
@router.post("/chat", dependencies=[Depends(verify_token)])
async def chat(...):
    ...

业务函数里不用 再写一遍 if token != ...。鉴权失败到不了你的 service 层,也就打不到大模型------既省钱,也少一次无效调用。

和「统一异常」篇衔接:若你已把 HTTPException 转成统一 JSON,401 也会变成:

json 复制代码
{
  "code": 401,
  "message": "无效或未提供 Token",
  "data": null,
  "request_id": "req_xxxxxxxxxxxx"
}

前端只认一种结构;监控也能按真实 HTTP 401 计数。


四、哪些要锁、哪些别锁

接口 建议 原因
POST /chat、历史查询等业务 要锁 直接花钱、读用户数据
GET /ping 可不锁 进程活着即可
GET /healthz 可不锁 K8s/Compose 探活;再加鉴权反而难运维
文档 /docs 开发开、生产关或加保护 避免接口清单裸奔

原则:探活宽松,业务收紧。


五、冒烟验收:用脚本证明「锁生效」

光靠手测容易漏。阶段 1 收官时,准备一份 smoke_test.sh(名字随意),至少覆盖:

  1. /ping、/healthz 成功;
  2. 错误 Token → 401;
  3. 正确 Token → /chat 成功;
  4. (可选)再问同一句,看缓存命中字段;
  5. (可选)拉一页历史。

伪代码节奏:

bash 复制代码
BASE_URL="${BASE_URL:-http://127.0.0.1:8000}"
API_TOKEN="${API_TOKEN:-你的令牌}"

# 1) 探活
curl -sf "$BASE_URL/healthz" >/dev/null || { echo "healthz FAIL"; exit 1; }

# 2) 鉴权必须失败
code=$(curl -s -o /dev/null -w "%{http_code}" \
  -X POST "$BASE_URL/chat" \
  -H "Authorization: Bearer wrong_token" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u","session_id":"s","message":"ping"}')
[ "$code" = "401" ] || { echo "auth FAIL want 401 got $code"; exit 1; }

# 3) 鉴权通过
curl -sf -X POST "$BASE_URL/chat" \
  -H "Authorization: Bearer ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"u","session_id":"s","message":"冒烟你好"}' \
  | grep -q '"code": *200' || { echo "chat FAIL"; exit 1; }

echo "smoke PASS"

Compose 起来之后跑一遍:

bash 复制代码
docker compose up -d --build
./scripts/smoke_test.sh

全绿,你才有底气说:别人按 README 操作,十分钟能验证你的交付。


六、常见坑

  1. 只校验「有没有 Header」,不校验值。 → 任意 Bearer xxx 都能进。
  2. Token 写死在代码里并提交。 → 换成环境变量;仓库只留 .env.example。
  3. 把 /healthz 也鉴权了,却忘了给探针配 Header。 → 探活失败,编排系统反复杀容器。
  4. 401 仍返回 HTTP 200 + code:401。 → 监控和网关会误判;状态码要真实。
  5. Swagger 里点「Authorize」忘了填,误以为接口挂了。 → 先确认 401 文案是鉴权,再查业务。
  6. 日志打印完整 Token。 → 最多打前后几位,或只打「已校验」。

七、阶段 1 真正收官

到这一篇,chat-api 骨架可以这样交卷:

篇 能力
02 FastAPI + 大模型,打通 /chat
03 路由 / 业务 / 客户端分层
04 统一响应、统一异常、request_id
05 PostgreSQL 历史 + 分页
06 Redis 缓存重复问题(可降级)
07 Docker Compose 一键拉起整栈
08 Bearer 鉴权 + 冒烟脚本验收

底座齐了:能对话、能记账、能省钱、能一键起、默认不裸奔。


八、带走这三条

  1. 鉴权用 Depends 前置拦截:失败不到业务、不打模型。
  2. 探活与业务权限分离 :/healthz 宽松,/chat 收紧。
  3. 冒烟脚本里必须有一条「错误 Token → 401」:证明锁是真的。

下一阶段进入专栏最硬核的一块:RAG------让大模型读懂你自己的资料,还不许瞎编。 第 9 篇先把概念和全貌讲清,再进入解析、分块、向量检索。

这是专栏第 8 篇,chat-api 阶段正式收官。两到三天一更,RAG 见。

相关推荐
王中阳Go10 小时前
预算给够了,时间花错了地方:三跳链路给了 27 秒,实际只用掉 9 秒
人工智能·后端·性能优化
方方洛10 小时前
ray教程-25-附录E-端到端实战案例
人工智能·分布式·机器学习
火山引擎开发者社区10 小时前
上万个 Agent 在线不乱跑,云沙箱给 Agent 造了间“独立办公室”
人工智能
神鹰手哥_起飞版10 小时前
一句话生成带品牌 logo 的架构图:方舟 Seed 2.1 负责听懂,代码负责画准
人工智能
老马识码10 小时前
WorkBuddy 的连接器是怎么统一到 MCP 的
人工智能
武子康10 小时前
动作和画面时间最近,就能配成训练样本吗?
人工智能·深度学习
wangfpp10 小时前
Skill渐进式披露指南
人工智能
Marco_康10 小时前
AgentFleet 跨设备调度实测:303 tasks/s,设备掉线 0 丢任务
人工智能
QYR_1110 小时前
年复合增长率11.4%!机器人指端视触觉传感器加速迈向规模化应用
人工智能·数码相机
武天旭11 小时前
AI安全--AI大模型的数据投毒攻击解析
人工智能·深度学习·安全