大猫OPC

共享库 + tenant_id:最省事也最容易越权

阅读 5 更新于 2026-10-27

先说结论

多租户有两条路:DB-per-tenant(一户一个库)和共享库行级隔离(一张表加 tenant_id)。前者隔离彻底但运维重,后者轻但每一行查询都要正确。

城市站选了后者:一套库,所有业务表带 tenant_id。

为什么不用一户一库

方案 优点 代价
DB-per-tenant 隔离彻底、可单独备份/迁移 几十个租户就是几十个库,迁移与升级成本翻倍
共享库 + tenant_id 一套迁移、一套备份、成本低 所有查询必须带租户条件,漏一处就串数据

对一个租户量不大、站点量极大的系统,共享库更合适:站点是数据,不是实例。

隔离的六道闸

  1. 查询统一入口:所有业务查询走 tenantTable(),自动追加 where tenant_id = ?
  2. 写操作双条件:更新/删除必须 id + tenant_id,防止猜 id 改别家数据
  3. 归属校验:保存文章时校验 site_id、category_id 属于当前租户
  4. 上下文缺失即拒绝:TenantContext 未设置时直接抛异常,绝不"没选租户就全表查"
  5. 媒体目录隔离:文件存在 static/upload/{tenant_id}/,删除时校验路径
  6. 平台功能单独鉴权:租户/域名这类平台表只有超管可写

最危险的写法

// 错:只按 id 更新,租户 A 能改租户 B 的文章
UPDATE city_articles SET title = ? WHERE id = ?

// 对:id + tenant_id
UPDATE city_articles SET title = ? WHERE id = ? AND tenant_id = ?

这类漏洞不会报错,只会在某天客户说"我后台出现了别人的数据"。

什么时候该拆库

  • 单租户数据量超过总库 50%
  • 客户要求数据物理隔离(合同/合规)
  • 写入并发成为瓶颈

扩展点在 TenantResolver:解析出租户后切换数据库连接,业务查询代码不用改。

本页内容对你有帮助吗? 感谢反馈(演示按钮,暂未接入后端)