KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

配套项目 · 权限与多租户最小可运行实现 — keel 龙骨

权限与多租户:从若依模型到注解式鉴权 的配套项目:配套项目 · 权限与多租户最小可运行实现

这门课的每个结论都在这个项目里跑过。它不追求框架完整,只保证判定内核只有一份,
并且能被真实 HTTP 请求验证。

文件

文件 作用 对应章节
schema.sql 15 张表:租户/组织/账号/成员/工作空间/权限点/菜单/角色/ACL 01、02、06
db.py SQLite 连接与建表(刻意不用 ORM,SQL 原文更好读) —
errors.py 拒绝原因也结构化(403 / 404 分开) 05
identity.py 身份上下文:user → member → 租户内身份,ContextVar 传递 03
rbac.py 角色 → 权限点 → 数据范围(菜单 / 接口 / 数据三层) 02、04
acl.py 资源级 ACL,带权重(use<observe<edit<manage) 06
guards.py 三种落地形态:函数内判定 / 依赖注入 / 装饰器 02、03
api.py FastAPI 接线,真实 HTTP 验证 03
seed.py 演示数据:两个租户,同一人在两边角色不同 01
demo.py 跑一遍全部判定,打印正文引用的输出 全部
test_auth.py 14 条 pytest,每条都是一次「主动破坏」 全部
api_probe.py 对 api.py 打 10 个真实请求,打印状态码 03、05

跑起来

# 1) 全流程演示(正文里的输出片段都来自这里)
python demo.py

# 2) 判定内核的测试
python -m pytest test_auth.py -q          # 14 passed

# 3) 真实 HTTP 验证(需要 fastapi + httpx)
python api_probe.py

三处值得注意的实现选择

  1. 租户 id 永远由服务端认定:客户端传的 X-Tenant-Id 只是"选择哪个上下文",
    验证权在 member 表(identity.resolve_identity)。所以伪造它只会得到
    403 not_a_member_of_tenant,不会让你变成另一个租户的人。
  2. 不存在与无权返回同一个 404:跨租户 id 和真不存在的 id 走同一条分支
    (acl.load_resource),避免用状态码探测别人的资源 id。
  3. 身份放 ContextVar,依赖与端点都必须 async def:FastAPI 会把同步的依赖
    和端点丢进线程池,而 ContextVar 不跨线程池传递。这条在开发时真的踩了两次
    (端点同步 → 依赖同步),正文 02 章有完整记录。

进入 keel 阅读