凌晨三点,我盯着系统日志,冷汗直流——“用户A” 竟然查到了“用户B”的私密待办事项。
那个让我彻夜难眠的 “越权” 漏洞
作为程序员,我们都经历过这样的时刻:
功能跑通了,测试通过了,代码部署了——然后,一个不起眼的日志让你的心脏停跳半拍。
在设计时,只考虑了“功能实现”,却忽略了最致命的一环:权限验证。
试想一下:
- 用户小明登录后,创建了私密待办:“向女友求婚计划”
- 用户小红,通过修改URL中的ID参数,直接访问到了小明的待办详情
数据裸奔,隐私崩塌
这不是危言耸听。在没有做正确权限验证的API中,每个接口都可能是潜在的后门,每次请求都可能是数据泄露。
从 “功能优先” 到 “安全优先” 的认知撕裂
在传统开发中,你是否遇到过:“先让功能跑起来,权限后面再加”
现实是,“后面” 永远没有到来。业务压力、迭代需求、上线 deadline……权限验证永远排不上。
更糟糕的是,当我们决定 “加固” API 时,面临的往往是:
- 代码侵入性强:要在每个接口手动添加验证逻辑
- 一致性难保证:不同开发者实现方式各异,漏洞百出
- 维护成本高:权限逻辑散落在各个角落,牵一发而动全身
我在这个阶段,踩坑无数。都是泪~~~
FastAPI + JWT + 依赖注入的 “三位一体” 防护体系
经过 8 小时的深度重构,我构建了一套简洁、优雅、强大的权限验证体系。下面是完整的实战复盘:
01、用户身份认证
核心武器:JWT(JSON Web Token) + OAuth2密码流
# 统一的认证依赖
from fastapi.security import OAuth2PasswordBearer
oauth2_bearer = OAuth2PasswordBearer(tokenUrl="token")
async def get_current_user(
token: Annotated[str, Depends(auth2_bearer)]):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
user_id: int = payload.get("id")
user_role: str = payload.get("role") # 新增:角色信息
if not username or not user_id:
raise HTTPException(status_code=401, detail="认证失败")
return {"username": username, "id": user_id, "role": user_role}
except JWTError:
raise HTTPException(status_code=401, detail="认证失败")
# 依赖类型注解,让代码更清晰
user_dependency = Annotated[dict, Depends(get_current_user)]
02、权限隔离——用户只能访问自己的数据
关键技巧:在数据库查询中强制添加用户ID过滤
@router.get("/todo/{todo_id}", status_code=status.HTTP_200_OK)
async def read_todo(
user: user_dependency, # 自动注入当前用户
db: db_dependency,
todo_id: int = Path(gt=0)):
# 双重验证:1. 用户是否登录 2. 数据是否属于该用户
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Failed Authentication")
todo_model = db.query(Todos).filter(
Todos.id == todo_id
).filter(
Todos.owner_id == user.get("id") # 关键过滤条件
).first()
if todo_model is not None:
return todo_model
raise HTTPException(status_code=404, detail="Todo not Found")
03、给角色分权限——实现 Admin 特权
在 JWT 中嵌入用户角色,实现接口级权限控制
# 1. 在Token创建时,加入角色信息
def create_access_token(username: str, user_id: int, role: str):
encode = {
"sub": username,
"id": user_id,
"role": role, # 新增角色字段
"exp": datetime.now(timezone.utc) + timedelta(minutes=20)
}
return jwt.encode(encode, SECRET_KEY, algorithm=ALGORITHM)
# 2. 在Token解码时,读取角色信息,并返回
async def get_current_user(token: Annotated[str, Depends(auth2_bearer)]):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
user_id: int = payload.get("id")
user_role: int = payload.get("role") # 新增角色字段
if username is None or user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate user.")
return {
"username": username,
"id": user_id,
"user_role": user_role
}
except JWTError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate user.")
# 3. 在接口处,判断用户角色信息
@router.get("/admin/todos")
async def admin_read_all(
user: user_dependency,
db: Session = Depends(get_db)
):
# 角色验证:必须是admin
if not user or user.get("role") != "admin":
raise HTTPException(status_code=403, detail="权限不足")
# 管理员可以查看所有数据
return db.query(Todo).all()
04、Swagger UI 集成测试
FastAPI 自动在 Swagger UI 添加“Authorize”按钮 开发者可以直接在文档界面测试认证接口
点击并打开它,提示我们需要输入用户名和密码。
我们传入一个错误的密码。 username="wangerge" password="test" 点击 Authorize 提交。提示 “Auth Error Error: Unauthorized” 表示认证失败。
我们再传入 username="wangerge" password="test1234" 点击 Authorize 提交。
显示如下,表示认证成功了。
重新执行 GET /admin/todos 接口请求。返回响应码 200,接口请求成功。
此时,之所以成功。一方面是因为添加了用户认证。另一方面是因为 wangerge 是 admin 用户。
┌────┬──────────┬───────────┬───────┐
│ id │ username │ is_active │ role │
├────┼──────────┼───────────┼───────┤
│ 1 │ wangerge │ 1 │ admin │
└────┴──────────┴───────────┴───────┘
我们新添加一个用户 userone,角色设置为空。
┌────┬──────────┬───────────┬───────┐
│ id │ username │ is_active │ role │
├────┼──────────┼───────────┼───────┤
│ 1 │ wangerge │ 1 │ admin │
│ 2 │ userone │ 1 │ │
└────┴──────────┴───────────┴───────┘
此时,我们退出 wangerge ,登录 userone 用户。
找到 admin 下 GET /admin/todo 接口,执行查询请求。返回 响应码 401,响应体:
{
"detail": "Failed Authentication"
}
小结:
admin 用户 wangerge 可以执行 GET /admin/todo 接口请求,获取所有待办数据。
而 非 admin 的用户 userone 没有权限执行 GET /admin/todo 接口请求。
总结:从 “功能实现” 到 “安全专业” 的蜕变
通过这个完整的实战案例,我们经历了 API 安全建设的三个关键阶段:
- 身份认证:确保用户是谁
- 数据隔离:确保用户只能访问自己的数据
- 权限控制:基于角色管理不同级别的访问权限
你的API还在“裸奔”吗? 从今天起,花一小时,为你的接口穿上“防护服”。
想要获取本章完整代码,请在评论区回复 【FastAPI】,代码直接复制就能跑。
关于 FastAPI 的其他疑问
你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄
FastAPI 新手紧急避坑:10分钟搞定用户认证4大坑,代码复制即用
“警惕!FastAPI接口一夜「消失」” 95%程序员靠这招自救:我的路由分离血泪史
10分钟搞定FastAPI中“数据库连接管理、参数校验、文档维护”三大核心难题,让新手也能轻松地写出可落地的API
3分钟搞定FastAPI的数据库设置,把数据存储玩明白,复制代码就能用
相关内容我都给大家做好了,感兴趣的朋友来「我的主页」找一找,直接就可以看到。
关注我,每天分享「Python」、「职场」有趣干货,千万不要错过!