Skip to content

5.8 多租户隔离 ​

概述

多租户是硬隔离:9 张租户级表的 tenant_id 过滤与盖章由全局钩子强制执行,业务代码零感知、不可绕过。租户状态与过期由 Tenant 中间件每请求校验(60s 缓存)。

数据模型 ​

text
goframe_tenant:code(T+4位ID,Account 生成)、status(1 正常/2 禁用)、
               expire_time(nil=永不过期)、max_user_num 等

9 张租户级表(带 tenant_id 列,hooks.tenantTables 登记):
  goframe_user / role / dept / level / position / article / category / notice / link

平台级(无租户过滤):其余 16 张表

读侧隔离(不可绕过) ​

text
dao.Ctx(hooks.ApplyCtx):非平台上下文自动追加
  WHERE tenant_id IN (0, 当前租户)     ← 0 = 平台共享行

超管(ctx tenant_id=0):不加过滤,全量可见
写侧禁止手写该条件——dao.Ctx 是唯一查询入口

写侧归属(insertHook 盖章规则) ​

text
租户用户插入未显式指定 tenant_id → 自动填自身(防逃逸)
超管未显式指定                  → 填 tenant.defaultId(默认 1)
显式指定具体值                  → 从显式值
显式 0                          → 创建平台共享行(如全局字典/参数)

业务取归属租户统一 consts.GetTenantID(ctx)(超管归默认租户);
CtxTenantID(ctx) 原始值仅供租户设施使用。

请求级校验(Tenant 中间件) ​

text
位于 JWT 之后:
  * 公开路径放行
  * ctx tenant_id=0(平台/超管)直接放行
  * 其余加载租户摘要(缓存 easygoadmin:tenant:info:{id},TTL 60s):
    - status != 1 → code=1006 租户已禁用
    - expire_time 早于现在 → code=1005 租户已过期

LoadTenant 语义:租户不存在 → 按禁用处理(失效关闭);
数据库基础错误 → 返回 nil,中间件放行(避免存储抖动导致全站不可用)

租户状态机 ​

text
         Account 开户
              │
              ▼
        ┌─────────┐   Status(2)   ┌─────────┐
        │ 1 正常   │─────────────▶│ 2 禁用   │
        └─────────┘               └─────────┘
              │                        │
              │ expire_time 到期        │ Status(1)
              ▼                        │
        过期(1005 拒绝访问)◀──────────┘
        (expire_time nil = 永不过期)

状态/过期变更后 tenant:info:{id} 缓存由 tenant 模块 AfterUpdate clearCache 钩子清理。

租户开户(tenant.Account,POST /api/tenant/account) ​

text
创建租户 + 初始管理员:事务内
  1) 建租户(code 按 T+ID 规则生成)
  2) 建管理员用户(bcrypt 事务外预计算)
  3) 分配角色

max_user_num 上限校验(1007)在 user.Add 生效(checkTenantUserMax)
事务纪律:事务内不做 HTTP/文件 IO,bcrypt 等 CPU 计算事务外预计算

配套接口:GET /api/tenant/users/:tenantId 查询租户用户(sys:tenant:users)。

安全审计要点 ​

租户红线

  1. 业务代码禁止 g.DB() 直查业务表
  2. 业务代码禁止手写 del_flag / tenant_id 条件
  3. 新增租户级表必须登记 hooks.tenantTables(漏登=跨租户泄漏)
  4. 唯一预检范围必须等于唯一索引范围(含 tenant_id 索引须 Group:"tenant_id")
  5. 平台级全量可见是设计预期,管理端点须收紧 permission

排障 ​

症状原因
租户看到别家数据tenantTables 漏登记该表;或业务绕过 dao.Ctx
租户看不到平台共享行业务手写了 tenant_id = ?(应交给 dao.Ctx 的 IN(0,x))
禁用租户仍能访问等 60s 缓存过期,或 DEL easygoadmin:tenant:info:{id}
新增报租户用户数上限1007——超过 max_user_num
超管新增数据归属不对显式指定 tenantId 或调 tenant.defaultId 配置

小蚂蚁云团队 · 提供技术支持