FastAPI 从 0 到 1 增加管理员登录

2 阅读5分钟

FastAPI 从 0 到 1 增加管理员登录(并强制全接口登录后访问)

这篇文档完整记录了本项目如何从“无鉴权”演进到“管理员登录 + 全接口保护”的过程,适合新人照着做一遍。


1. 改造前现状

改造前项目是一个典型的 FastAPI + SQLAlchemy 后端,特点如下:

  • 有多个业务路由:客户、商品、订单、还款、看板
  • 所有接口默认可直接访问
  • 没有登录接口
  • 没有 token 校验
  • 没有统一权限依赖

入口文件 app/main.py 直接 include_router(...),没有任何 Depends(...) 鉴权依赖。


2. 需求拆解

原始需求是:

  1. 增加一个管理员登录接口
  2. 只有管理员登录后,才能访问所有业务接口

把它转成工程任务就是:

  1. 定义管理员账号密码来源(环境变量)
  2. 提供登录接口,校验账号密码后签发访问令牌
  3. 提供令牌校验依赖(依赖注入)
  4. 把所有业务路由挂上该依赖
  5. 修改测试,先登录再调用业务接口

3. 技术方案选择

为了让新人更容易理解,这次采用了“轻量自定义 token”方案,而不是一步上 OAuth2/JWT 第三方库。

为什么这样做

  • 代码少,便于学习鉴权主流程
  • 依赖少,不需要额外安装包
  • 能覆盖核心能力:登录、签发 token、校验 token、路由保护

令牌设计

  • payload{"sub": 用户名, "exp": 过期时间戳}
  • HMAC-SHA256 对 payload 签名
  • token 结构:base64(payload).base64(signature)
  • 请求头使用标准 Authorization: Bearer <token>

说明:这是“可用于内部系统”的轻量实现。若未来有多角色、多端登录、刷新 token、吊销 token 等需求,建议升级为标准 JWT 方案(如 python-jose + OAuth2)。


4. 代码落地步骤(核心)

步骤 1:新增认证核心模块 app/auth.py

该文件承担三件事:

  1. 读取管理员配置(环境变量)
  2. 生成 token(create_access_token
  3. 校验 token + 提供依赖(require_admin

关键点:

  • ADMIN_USERNAMEADMIN_PASSWORDADMIN_TOKEN_SECRETADMIN_TOKEN_EXPIRE_SECONDS 全部支持环境变量
  • verify_access_token 会校验:
    • token 格式是否合法
    • 签名是否匹配
    • 是否过期
  • require_admin 作为 FastAPI 依赖函数,在认证失败时统一抛 401

你可以在这里看到实现:app/auth.py


步骤 2:新增管理员登录路由 app/routers/auth.py

新增接口:

  • POST /api/admin/login

请求体:

{
  "username": "admin",
  "password": "admin123456"
}

处理流程:

  1. 对比输入账号密码与环境变量配置
  2. 正确则签发 token
  3. 返回 access_tokentoken_typeexpires_in
  4. 错误则返回 401

你可以在这里看到实现:app/routers/auth.py


步骤 3:补充认证相关 Schema app/schemas.py

新增两个模型:

  • AdminLoginRequest:登录请求参数
  • AdminLoginResponse:登录响应结构

这样做的好处:

  • 请求/响应结构清晰
  • OpenAPI 文档自动可见
  • 后续前后端联调更稳定

步骤 4:在应用入口统一接入权限依赖 app/main.py

这是最关键的一步:把“保护逻辑”集中在路由挂载点,不侵入每个业务函数。

改造方式:

  1. 先挂载登录路由(不加鉴权依赖)
  2. 业务路由全部挂载 dependencies=[Depends(require_admin)]
  3. 根路由 / 也加管理员依赖

伪代码如下(便于理解):

app.include_router(auth.router)  # 登录接口公开
app.include_router(customers.router, dependencies=[Depends(require_admin)])
app.include_router(products.router, dependencies=[Depends(require_admin)])
app.include_router(orders.router, dependencies=[Depends(require_admin)])
app.include_router(payments.router, dependencies=[Depends(require_admin)])
app.include_router(dashboard.router, dependencies=[Depends(require_admin)])

这种方式的优点:

  • 业务路由文件几乎不用改
  • 权限策略集中、可维护
  • 后续新增路由时,是否保护一眼可见

步骤 5:配置管理员账号密码到 .env

新增 .env

ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123456
ADMIN_TOKEN_SECRET=change-this-in-production
ADMIN_TOKEN_EXPIRE_SECONDS=28800

并将 .env 加入 .gitignore,避免敏感配置提交到仓库。


5. 测试是怎么同步改造的

鉴权改造后,原有测试直接访问业务接口会返回 401,所以测试必须同步升级。

测试改造策略

  1. 先调用 /api/admin/login 获取 token
  2. 在后续请求中加 Authorization: Bearer <token>
  3. 增加一个“未登录访问应返回 401”的用例,防止未来回归

改过的测试文件

  • tests/test_main.py
  • tests/test_orders.py
  • test_paid.py(原本是脚本式调用,已改为 pytest 测试函数)

最终验证结果:

  • uv run pytest -q
  • 6 passed

6. 请求调用示例(给前端/联调用)

1)先登录

curl -X POST http://localhost:8000/api/admin/login \
  -H "Content-Type: application/json" \
  -d "{\"username\":\"admin\",\"password\":\"admin123456\"}"

示例响应:

{
  "access_token": "xxxxx.yyyyy",
  "token_type": "bearer",
  "expires_in": 28800
}

2)带 token 访问业务接口

curl http://localhost:8000/api/customers \
  -H "Authorization: Bearer xxxxx.yyyyy"

7. 新人最容易踩的坑

  1. 只写了登录接口,忘了保护业务路由 结果是“看起来有登录,实际上谁都能访问”。

  2. 测试没改,误以为功能坏了 鉴权后测试需要先登录拿 token。

  3. 把账号密码写死在代码里 正确做法是放在 .env,并忽略提交。

  4. ADMIN_TOKEN_SECRET 使用默认值上线 生产环境必须替换成高强度随机密钥。

  5. token 过期策略没设计 现在是固定过期,后续可加刷新机制。


8. 安全与演进建议(下一步)

当前方案已满足“管理员登录 + 全接口保护”目标。若系统继续发展,建议按下面顺序演进:

  1. 密码加密存储 当前管理员密码来自环境变量明文,后续可迁移到数据库并存哈希值(如 bcrypt)。

  2. 标准 JWT 引入标准库,支持更多 claim(如 iatnbfrole)。

  3. 角色权限(RBAC) 从“只有管理员”扩展到“管理员/店员/只读”等角色。

  4. 刷新 token 与吊销机制 支持长会话与强制下线。

  5. 审计日志 记录登录成功/失败、敏感操作轨迹。


9. 一句话总结

这次改造的核心不是“写一个登录接口”,而是建立了完整的鉴权闭环:

配置来源(.env) -> 登录签发 token -> 依赖校验 token -> 路由统一保护 -> 测试验证回归。

只要掌握这个闭环,新人就能快速在任何 FastAPI 项目里落地基础权限体系。