给接口加把锁: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 见。

相关推荐
hanbon3 分钟前
标书制作流程与技巧:从读标到装订
人工智能·招投标·ai写标书·技术标
知识分享小能手14 分钟前
深度学习学习教程,从入门到精通,深度学习中的正则化 — 完整知识点与代码示例(7)
人工智能·深度学习·学习
小小猪的春天17 分钟前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
TechEdu20260620 分钟前
[人工智能]国内国外大型语言模型技术比较指南V02(2026.9月)
人工智能·ai
AI人工智能集结号23 分钟前
2026年9月GEO优化与传统SEO怎么选?预算应该先投向哪一个?
人工智能·geo优化
console.log('npc')1 小时前
Git 冲突与 AI 协助指南
前端·人工智能·git·大模型
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-09-05
人工智能·深度学习·神经网络·搜索引擎·百度
魔众1 小时前
5 分钟用 AIGCPanel 部署阿里 SenseVoice,中粤日韩英语音识别 + 情感分析全搞定
人工智能·语音识别
xwz小王子1 小时前
机器人的“最后一毫米”: 新加坡南洋理工大学Facet-0如何教会基础模型“感受”自己的动作?
大数据·人工智能·机器人
今天AI了吗1 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee