你的API在裸奔?踩坑8小时,从“越权裸奔”到“权限严控”:FastAPI+JWT+依赖注入,这套方案闭眼抄

9 阅读5分钟

fIR9SQsHr

凌晨三点,我盯着系统日志,冷汗直流——“用户A” 竟然查到了“用户B”的私密待办事项。

那个让我彻夜难眠的 “越权” 漏洞

作为程序员,我们都经历过这样的时刻:

功能跑通了,测试通过了,代码部署了——然后,一个不起眼的日志让你的心脏停跳半拍。

在设计时,只考虑了“功能实现”,却忽略了最致命的一环权限验证

试想一下:

  • 用户小明登录后,创建了私密待办:“向女友求婚计划”
  • 用户小红,通过修改URL中的ID参数,直接访问到了小明的待办详情

数据裸奔,隐私崩塌

这不是危言耸听。在没有做正确权限验证的API中,每个接口都可能是潜在的后门,每次请求都可能是数据泄露。

从 “功能优先” 到 “安全优先” 的认知撕裂

在传统开发中,你是否遇到过:“先让功能跑起来,权限后面再加”

现实是,“后面” 永远没有到来。业务压力、迭代需求、上线 deadline……权限验证永远排不上。

更糟糕的是,当我们决定 “加固” API 时,面临的往往是:

  1. 代码侵入性强:要在每个接口手动添加验证逻辑
  2. 一致性难保证:不同开发者实现方式各异,漏洞百出
  3. 维护成本高:权限逻辑散落在各个角落,牵一发而动全身

我在这个阶段,踩坑无数。都是泪~~~

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”按钮 开发者可以直接在文档界面测试认证接口

image-20260114225342260

点击并打开它,提示我们需要输入用户名和密码。

我们传入一个错误的密码。 username="wangerge" password="test" 点击 Authorize 提交。提示 “Auth Error Error: Unauthorized” 表示认证失败。

image-20260113150345877

我们再传入 username="wangerge" password="test1234" 点击 Authorize 提交。

显示如下,表示认证成功了。

image-20260114225357247

重新执行 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 安全建设的三个关键阶段:

  1. 身份认证:确保用户是谁
  2. 数据隔离:确保用户只能访问自己的数据
  3. 权限控制:基于角色管理不同级别的访问权限

你的API还在“裸奔”吗? 从今天起,花一小时,为你的接口穿上“防护服”。

想要获取本章完整代码,请在评论区回复 【FastAPI】,代码直接复制就能跑。

关于 FastAPI 的其他疑问

你的APP要用户反复登录?密码传来传去?FastAPI+JWT实战,一个令牌全打通,安全与体验兼得,代码直接抄

FastAPI 新手紧急避坑:10分钟搞定用户认证4大坑,代码复制即用

“警惕!FastAPI接口一夜「消失」” 95%程序员靠这招自救:我的路由分离血泪史

10分钟搞定FastAPI中“数据库连接管理、参数校验、文档维护”三大核心难题,让新手也能轻松地写出可落地的API

3分钟搞定FastAPI的数据库设置,把数据存储玩明白,复制代码就能用

相关内容我都给大家做好了,感兴趣的朋友来「我的主页」找一找,直接就可以看到。

关注我,每天分享「Python」、「职场」有趣干货,千万不要错过!