切换日光/暗黑模式
058. 认证中间件与接口文档 Token 调试
学习目标
这一节完成认证中间件,并处理接口文档里的 token 调试体验。
学完后,你应该能理解:
- 中间件链为什么像 Koa 洋葱模型;
- 认证中间件要处理哪些分支;
- 异常中间件为什么要放在认证中间件外层;
- 白名单接口如何跳过 token 校验;
- Swagger 为什么需要全局安全依赖;
- 导入路径为什么要统一。
计时中间件
先用一个简单计时中间件理解中间件结构。
它在请求开始时记录时间。
业务接口执行完后,再计算耗时。
最后把耗时写到响应头里。
例如响应头里出现:
txt
x-process-time这说明中间件确实参与了请求流程。
中间件必须返回响应
中间件里调用 call_next(request) 后,要把 response 返回出去。
如果只 await call_next(request),但没有 return response,后续链路会断。
最终可能出现类似“None 不能被调用或等待”的错误。
对 JS 开发者来说,可以把它理解成 Express/Koa 中间件里忘记把结果继续返回。
认证中间件的职责
认证中间件要处理很多分支。
它通常需要:
- 初始化
request.state.user; - 放行预检请求;
- 判断当前路径是否在白名单;
- 从请求头读取
Authorization; - 解析 bearer token;
- 解码 token payload;
- 判断是否开启全局 token 校验;
- 校验 API token 类型;
- 查询当前用户;
- 判断用户状态是否有效;
- 把用户写入
request.state.user。
认证中间件不是只做一行 token 判断。
它是全局入口的安全门。
预检请求
浏览器跨域请求可能先发 OPTIONS 预检请求。
预检请求不应该被普通 token 校验挡住。
否则真正的业务请求还没发出,浏览器就已经因为跨域失败而终止。
所以中间件里要优先处理预检请求。
白名单接口
白名单接口直接放行。
例如:
- 登录;
- 注册;
- 激活账号;
- 刷新 token;
- 文档页;
- 静态资源;
- 测试接口。
白名单之外的接口,才进入 token 校验流程。
401 与 403
没有 token、token 无效、token 过期,通常返回 401。
用户存在但状态不可用,例如未激活或被禁用,可以返回 403。
这两个状态码含义不同:
- 401: 认证失败;
- 403: 已识别身份,但不允许访问。
区分它们有助于前端做正确处理。
异常中间件
如果认证中间件里抛出 HTTPException,默认可能被包装成 500。
这样前端只能看到 Internal Server Error。
异常中间件负责把预期内的 HTTPException 转成统一 JSON 响应,并保留原状态码。
例如 token 无效时,前端应该看到 401,而不是 500。
中间件顺序
中间件是从外到内执行请求,再从内到外返回响应。
可以把它理解成洋葱模型。
如果异常中间件要捕获认证中间件里的错误,它必须包在认证中间件外层。
顺序放错,认证中间件抛出的异常就不会被它捕获。
所以中间件顺序不是随便写的。
全局 token 认证
认证中间件实现后,大部分接口都会被 token 保护。
白名单接口仍然可以不带 token 访问。
例如测试白名单路径可以直接返回。
非白名单接口不带 token 时,会返回 token 无效或过期。
Swagger 文档里的锁标记
FastAPI 文档会根据安全依赖显示锁标记和授权按钮。
如果每个接口都手动加安全依赖,文档显示会比较准确,但代码会重复。
项目里可以在应用初始化时添加全局安全依赖。
这样 Swagger 里所有接口都会显示需要认证,并且授权后调试接口会自动带 token。
全局安全依赖不会替代中间件
这里的全局安全依赖主要是为了文档调试体验。
真正控制接口是否放行的是认证中间件和白名单逻辑。
所以白名单接口即使文档上显示锁,也仍然可以按中间件规则放行。
导入路径统一
后端项目里导入路径要统一。
如果 IDE 把某个目录标记成源码目录,导入可以少写一层前缀。
但不同同学环境不一致时,就可能出现导入报错。
为了减少差异,课程代码会把导入路径统一成带应用前缀的写法。
这样更适合跨环境运行。
这一节的重点
这一节把认证从局部依赖推进到全局中间件。
你需要记住:
- 中间件必须返回 response;
- 认证中间件要处理白名单、预检、token 和用户状态;
- 异常中间件要放在能包住认证中间件的位置;
- Swagger 的授权按钮服务于调试;
- 导入路径要统一,避免依赖 IDE 私有设置。