FastAPI 从 0 到 1 增加管理员登录(并强制全接口登录后访问)
这篇文档完整记录了本项目如何从“无鉴权”演进到“管理员登录 + 全接口保护”的过程,适合新人照着做一遍。
1. 改造前现状
改造前项目是一个典型的 FastAPI + SQLAlchemy 后端,特点如下:
- 有多个业务路由:客户、商品、订单、还款、看板
- 所有接口默认可直接访问
- 没有登录接口
- 没有 token 校验
- 没有统一权限依赖
入口文件 app/main.py 直接 include_router(...),没有任何 Depends(...) 鉴权依赖。
2. 需求拆解
原始需求是:
- 增加一个管理员登录接口
- 只有管理员登录后,才能访问所有业务接口
把它转成工程任务就是:
- 定义管理员账号密码来源(环境变量)
- 提供登录接口,校验账号密码后签发访问令牌
- 提供令牌校验依赖(依赖注入)
- 把所有业务路由挂上该依赖
- 修改测试,先登录再调用业务接口
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
该文件承担三件事:
- 读取管理员配置(环境变量)
- 生成 token(
create_access_token) - 校验 token + 提供依赖(
require_admin)
关键点:
ADMIN_USERNAME、ADMIN_PASSWORD、ADMIN_TOKEN_SECRET、ADMIN_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"
}
处理流程:
- 对比输入账号密码与环境变量配置
- 正确则签发 token
- 返回
access_token、token_type、expires_in - 错误则返回
401
你可以在这里看到实现:app/routers/auth.py
步骤 3:补充认证相关 Schema app/schemas.py
新增两个模型:
AdminLoginRequest:登录请求参数AdminLoginResponse:登录响应结构
这样做的好处:
- 请求/响应结构清晰
- OpenAPI 文档自动可见
- 后续前后端联调更稳定
步骤 4:在应用入口统一接入权限依赖 app/main.py
这是最关键的一步:把“保护逻辑”集中在路由挂载点,不侵入每个业务函数。
改造方式:
- 先挂载登录路由(不加鉴权依赖)
- 业务路由全部挂载
dependencies=[Depends(require_admin)] - 根路由
/也加管理员依赖
伪代码如下(便于理解):
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,所以测试必须同步升级。
测试改造策略
- 先调用
/api/admin/login获取 token - 在后续请求中加
Authorization: Bearer <token> - 增加一个“未登录访问应返回 401”的用例,防止未来回归
改过的测试文件
tests/test_main.pytests/test_orders.pytest_paid.py(原本是脚本式调用,已改为 pytest 测试函数)
最终验证结果:
uv run pytest -q6 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. 新人最容易踩的坑
-
只写了登录接口,忘了保护业务路由 结果是“看起来有登录,实际上谁都能访问”。
-
测试没改,误以为功能坏了 鉴权后测试需要先登录拿 token。
-
把账号密码写死在代码里 正确做法是放在
.env,并忽略提交。 -
ADMIN_TOKEN_SECRET使用默认值上线 生产环境必须替换成高强度随机密钥。 -
token 过期策略没设计 现在是固定过期,后续可加刷新机制。
8. 安全与演进建议(下一步)
当前方案已满足“管理员登录 + 全接口保护”目标。若系统继续发展,建议按下面顺序演进:
-
密码加密存储 当前管理员密码来自环境变量明文,后续可迁移到数据库并存哈希值(如 bcrypt)。
-
标准 JWT 引入标准库,支持更多 claim(如
iat、nbf、role)。 -
角色权限(RBAC) 从“只有管理员”扩展到“管理员/店员/只读”等角色。
-
刷新 token 与吊销机制 支持长会话与强制下线。
-
审计日志 记录登录成功/失败、敏感操作轨迹。
9. 一句话总结
这次改造的核心不是“写一个登录接口”,而是建立了完整的鉴权闭环:
配置来源(.env) -> 登录签发 token -> 依赖校验 token -> 路由统一保护 -> 测试验证回归。
只要掌握这个闭环,新人就能快速在任何 FastAPI 项目里落地基础权限体系。