Skip to content

多租户架构与空间治理 ​

EIAM 采用“共享数据库、按租户逻辑分片、安全插件强制拦截”的多租户架构,核心聚焦于两大技术目标:数据底层强隔离与跨租户安全协同。

EIAM 多租户数据隔离流转架构


1. 三级租户矩阵 ​

EIAM 在模型上将租户划分为三层,满足全局治理、个人体验与企业生产的不同诉求:

租户类型标识规范空间定位核心特权与权限模型
系统根租户tenant_id = 1
(代码标识 system)
系统的母体空间与全局中枢拥有最高运维治理视角,负责管理系统公共应用与全域共享资源
个人独立空间tenant_id > 1
(代码标识 {username}-personal)
新用户首次登录时自动初始化的专属沙箱当前独立空间的超级管理员 (admin),空间内完全自治,放手体验不扰生产
企业组织租户tenant_id > 1
(如 infra-core、sec-ops)
各部门、业务线协作的生产资源池严格遵循细粒度权限策略与行级数据范围约束

2. 双租户上下文机制 ​

系统在用户认证握手与全链路请求中注入两个关键租户字段,实现身份鉴权与数据过滤的解耦:

  • 身份归属租户 (origin_tenant_id · 身份平面):
    • 锚定当前账号真实的母体归属,全链路不可篡改;
    • 权限策略判定始终以此身份展开,作为跨租户准入底线:普通租户严禁越权调用,仅系统根租户超管在特权巡检时具备跨空间准入凭证。
  • 数据操作租户 (tenant_id · 数据平面):
    • 控制当前业务数据 CRUD 的过滤边界,由底层数据插件自动注入 SQL 过滤条件;
    • 管理员在控制台切换至不同工作空间时,仅动态改变此操作目标,实现“身份不变,安全操作目标空间数据”。

NOTE

工作空间秒级切换:切换工作空间时,服务端即刻销毁旧会话并重签新凭证,杜绝令牌重放;同时持久化记录用户最近活跃租户,下次登录直接还原上次工作面板。


3. GORMx 零信任底层强隔离 ​

为防止业务开发因疏漏遗漏 WHERE tenant_id = ?,EIAM 在底层封装了多租户插件(pkg/gormx/tenant_plugin.go),全生命周期接管数据库交互:

3.1 生命周期回调接管 ​

插件在 GORM 初始化阶段注册四组核心回调钩子,在 SQL 构建与执行前自动织入安全边界:

go
func (p *TenantPlugin) Initialize(db *gorm.DB) error {
    cb := db.Callback()
    // 注册前置钩子:写屏障自动注入、读屏障安全过滤、更新/删除防跨租户越权
    _ = cb.Create().Before("gorm:create").Register("tenant:handle_create", p.handleCreate)
    _ = cb.Query().Before("gorm:query").Register("tenant:handle_query", p.handleQuery)
    _ = cb.Update().Before("gorm:update").Register("tenant:handle_update", p.handleStrict)
    _ = cb.Delete().Before("gorm:delete").Register("tenant:handle_delete", p.handleStrict)
    return nil
}

3.2 读拦截与共享模型重写 (Fail-Closed) ​

读取数据时,若包含租户列但上下文中缺失合法租户,插件直接报错熔断(Fail-Closed);同时支持通过 eiam:"shared" 标签自动合并全局公共数据:

go
func (p *TenantPlugin) handleQuery(db *gorm.DB) {
    if p.shouldSkip(db) || !p.hasTenantColumn(db) {
        return
    }

    tid := ctxutil.GetTenantID(db.Statement.Context)
    // 零信任熔断:若包含 tenant_id 字段但上下文缺失合法租户,直接报错拒绝执行
    if tid <= 0 {
        _ = db.AddError(errors.New("多租户安全拦截:未显式声明 IgnoreTenant 且缺失有效租户上下文"))
        return
    }

    conf := p.getSharedConfig(db.Statement.Schema)
    // 1. 共享模型 (eiam:"shared"):自动重写 SQL 合并系统根租户 (ID: 1) 的全局公共数据
    if conf.IsShared {
        p.injectQueryPolicy(db, tid.Int64(), conf) // 注入: WHERE tenant_id = ? OR tenant_id = 1
        return
    }

    // 2. 常规私有模型:强制严格隔离
    db.Where("tenant_id = ?", tid.Int64())
}

3.3 写拦截自动注入 ​

创建实体时,插件通过反射自动提取上下文中的当前 tenant_id 并设入模型字段,上层业务层代码完全无感知,彻底杜绝漏写:

go
func (p *TenantPlugin) setTenantField(ctx context.Context, field *schema.Field, value reflect.Value, tid int64) {
    // 仅在租户字段为零值时自动注入当前上下文租户,既保证安全又不破坏离线迁移时的显式指定
    if _, isZero := field.ValueOf(ctx, value); isZero {
        _ = field.Set(ctx, value, tid)
    }
}

4. 租户成员入驻双轨机制 ​

EIAM 解耦了全局账号 (User) 与 租户成员契约 (Membership)。同一个账号在不同租户内的成员状态和权限互相独立。

根据管理特权层级,成员入驻设计了两种互补途径:

机制操作主体流转链路典型场景
系统管理员直接划拨系统根租户管理员在后台直接选择平台已有用户,批量划归至目标租户,免受邀人确认。企业统一建号、系统初始化、组织批量划拨
租户间安全邀约普通企业租户管理员生成加密邀请码/链接,受邀人主动接受申请,经免审或审批后入驻。跨团队项目协同、外部伙伴加入、团队自主吸纳新成员

4.1 租户间安全邀约闭环 ​

租户之间互为不可信边界。普通租户管理员无权窥探全局花名册,绝不能任意将外部人员强行拉入自己的空间,必须遵循邀约流转:

  • 全生命周期管控:支持配置最大使用次数与失效时间,超时或达上限自动失效;
  • 预设角色注入:创建邀请时可预先绑定角色,成员成功加入后即刻生效对应权限;
  • 主动撤回与联动失效:管理员可随时手动撤回邀请码,未处理的关联申请自动批量失效。

TIP

了解多租户隔离与工作空间治理后,若需了解企业多层级部门架构与用户组赋权体系,请继续阅读 组织架构与人员治理。

基于 MIT 许可发布