土法炼钢兴趣小组的算法知识备份

【身份与访问控制工程】B2B SaaS 多租户权限设计

文章导航

分类入口
architecturesecurity
标签入口
#multi-tenant#b2b-saas#authorization#rbac#organization-tree#data-isolation#tenant-aware

目录

单租户的权限问题已经够复杂了(见 RBAC/ABAC/ReBAC 选型)。多租户额外引入了一个维度:同一个 SaaS 平台上有 100 个公司,每个公司内部有自己的角色体系、组织结构和审批规则。这意味着你需要在同一个数据库里同时运行 100 套互相隔离的权限系统。

一、租户隔离:物理 vs 逻辑

多租户的第一个决策是隔离级别:

隔离方式 数据层 认证层 授权复杂度 成本
单 DB + tenant_id 字段 逻辑隔离(WHERE tenant_id = ?) 共享 IdP 高(所有租户共用同一套权限表)
DB per Tenant 物理隔离(独立数据库) 可共享或独立 IdP 低(每个租户有独立的权限表)
Schema per Tenant PostgreSQL Schema 级别隔离 共享 IdP

绝大多数 B2B SaaS 选择第一种方案(单 DB + tenant_id),因为它在工程和运维复杂度之间取得了平衡。但要付出额外的代价:每个涉及权限的 SQL 查询都要带 WHERE tenant_id = ?,忘记加这个条件就会造成跨租户数据泄露。

工程陷阱 1:在”单 DB + tenant_id”模式下,大多数跨租户数据泄露漏洞不是来自”恶意攻击者精心构造请求”,而是来自”新加入的后端工程师在 JOIN 查询中忘了加 WHERE tenant_id = ?“。防御手段是:应用层的 ORM 自动注入 tenant_id 过滤器(如 Hibernate 的 @Filter、Django 的 Row-level security),或者数据库层的 RLS(Row-Level Security,PostgreSQL 14+ 支持)。

二、多租户权限的四层架构

flowchart TD
  L1["第一层:平台级权限<br/>(Platform Admin, Billing Admin)"]
  L2["第二层:租户级功能权限<br/>(租户自定义角色: Admin, Manager, Viewer, ...)"]
  L3["第三层:组织/部门级数据权限<br/>(只能看本部门和下属部门的数据)"]
  L4["第四层:资源级协作权限<br/>(谁能看这个文档/这个项目)"]

  L1 --> L2 --> L3 --> L4

2.1 第一层:平台级权限

平台运营团队需要的权限——管理所有租户、查看所有 billing 信息、介入客户支持。这层不与租户内部权限重叠,通常用全局 RBAC 角色(Platform Admin、Ops、Support)实现。

2.2 第二层:租户级功能权限

这是 RBAC 在租户维度的扩展——每个租户可能有自己的角色定义。

数据结构方案:

-- 全局角色表(平台预定义的角色模板)
CREATE TABLE roles (
    id          UUID PRIMARY KEY,
    tenant_id   UUID,  -- NULL = 平台角色(跨租户共享), NOT NULL = 租户自定义
    name        VARCHAR(128),
    permissions JSONB,  -- ["user.read", "user.write", "report.export"]
    UNIQUE (tenant_id, name)
);

-- 用户-角色分配
CREATE TABLE user_roles (
    user_id     UUID,
    role_id     UUID REFERENCES roles(id),
    tenant_id   UUID,  -- 冗余字段用于快速筛选
    PRIMARY KEY (user_id, role_id)
);

租户自定义角色通过 tenant_id 隔离——租户 A 的 “Custom-Admin” 角色和租户 B 的 “Custom-Admin” 角色是两条不同的记录,带不同的权限。

2.3 第三层:组织树与数据权限

这是多租户权限中最复杂的部分。一个典型的 B2B 场景:

公司 A:
  ├── 总部 (Department)
  │   ├── 工程部
  │   │   ├── 平台组 (Team)
  │   │   └── 产品组
  │   └── 市场部
  └── 分部 (Department)
      └── 客户支持

需求:平台组的 Manager 应能看到本组和下属组(如果有)的数据,
      同时能看到工程部的汇总数据,但看不到市场部的数据。

这种需要的是”组织树 + 作用域”模型:

CREATE TABLE org_nodes (
    id          UUID PRIMARY KEY,
    tenant_id   UUID NOT NULL,
    parent_id   UUID REFERENCES org_nodes(id),  -- 树结构
    name        VARCHAR(255),
    type        VARCHAR(64)  -- 'department', 'team', 'division'
);

-- 用户角色在特定作用域中生效
CREATE TABLE user_role_scopes (
    user_id     UUID,
    role_id     UUID,
    org_node_id UUID,  -- 该角色在哪个组织范围内生效
    tenant_id   UUID,
    PRIMARY KEY (user_id, role_id, org_node_id)
);

授权检查时,需要从用户当前组织节点出发,向上遍历祖先,收集所有 user_role_scopes 中生效的权限。如果用户是”工程部”的 Manager,在查询”平台组”的数据时,授权检查应沿组织树上溯,判断”工程部”的 Manager 角色是否覆盖”平台组”。

工程陷阱 2:组织树的深度通常不大(5-8 层),但每个授权检查都要做一次图遍历(从叶子节点上溯到根)。如果权限查询频繁(如每个 API 请求都需要),需要做组织树缓存——预计算每个用户的”有效组织范围列表”,缓存到 Redis,在组织变更时失效。

2.4 第四层:资源级权限(Zanzibar 风格)

文档、项目、文件夹的共享和协作权限适合用 Zanzibar 模型(见上一篇文章)。在多租户场景中,Zanzibar 的 namespace 需要是 tenant-aware 的——不同租户的 relation tuple 完全隔离:

# Zanzibar 式 relation tuple,带 tenant 前缀
⟨tenant:a/doc:readme.txt #viewer user:zhangsan⟩
⟨tenant:a/group:eng-leads #member user:lisi⟩

# 而非
⟨doc:readme.txt #viewer tenant:a/user:zhangsan⟩

前者保证了不同租户之间的关系元组在物理索引上隔离,避免跨租户的图遍历。

三、跨租户授权

有些场景中,一个租户的用户需要访问另一个租户的数据。典型场景:

跨租户授权的架构选择:

  1. 委托授权(Delegated Access):租户 A 在界面上选择”允许租户 B 的用户 X 访问我的 Y 数据”。生成一条跨租户的 relation tuple。
  2. 父租户模型:在租户之间建立父子关系,父租户的管理员自动获得子租户的指定权限。
  3. 代理账号(Service Account per Tenant):为第三方创建目标租户下的服务账号,限定权限范围。

四、小结

多租户权限不是”单租户权限 × N”。它是一个新的维度,会和你已有的 RBAC、组织树、Zanzibar 风格的资源级权限互相交叉:

  1. 首先选好租户隔离模型(单 DB + tenant_id 是多数 SaaS 的起点)。
  2. 四层权限架构(平台级→功能级→数据级→资源级)作为设计蓝图,每层有独立的实现方式。
  3. 组织树缓存是性能关键——预计算用户的”有效组织范围”。
  4. ID 被遗忘的 WHERE tenant_id 是跨租户数据泄露的首要原因——从第 1 天就在 App 层建立防呆机制。

上一篇OPA、Cedar 与策略引擎落地 下一篇API Gateway、BFF 与边界认证授权

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-13 · architecture / security

【身份与访问控制工程】IAM 全景:为什么这是高价值赛道

从 2020 年 SolarWinds 到 2024 年 Okta 支持系统泄露,身份基础设施的安全失败反复证明一件事:IAM 不是 IT 支撑系统,而是安全架构的承重墙。本文建立现代 IAM 的全景地图——从认证协议、令牌体系、权限模型到身份治理与平台选型,给出 5 个贯穿全系列的核心问题。

2026-06-17 · architecture / security

【身份与访问控制工程】RBAC、ABAC、ReBAC:权限模型怎么选

RBAC 简单但会角色爆炸,ABAC 灵活但策略管理失控时更可怕,ReBAC 表达力强但引入了图遍历的性能约束。三种模型不是'选一个升级另一个'的线性关系,而是在表达能力、管理成本和性能三者之间做工程权衡。本文从每种模型的本质数据结构出发,拆解选型框架。

2026-06-18 · architecture / security

【身份与访问控制工程】Zanzibar 风格权限系统:Google 的全球授权引擎

Google Zanzibar 论文在 2019 年发布后,引发了开源授权系统的一波重新设计:Auth0 FGA、SpiceDB、Permify、Ory Keto——全都基于 Zanzibar 的'关系图+命名空间配置'模型。但论文本身只讲了 What,没深入 Why。本文从 Zanzibar 的 relation tuple 模型、namespace config 的语义、consistency 模型(Zookie)和工程权衡出发,拆解为什么 Zanzibar 的设计决策是这样的,以及你自己实现时要面对什么。

2026-06-18 · architecture / security

【身份与访问控制工程】OPA、Cedar 与策略引擎落地

OPA 是 CNCF 的策略引擎标准答案,Rego 是它的策略语言;Cedar 是 AWS 开源的新竞争者,基于 Rust 的 WASM 编译执行、语法更接近 SQL。两者在架构模式(sidecar vs 中心化)、策略语言设计哲学和性能特征上有根本差异。本文从策略引擎的架构模式出发,拆解 OPA Rego 的核心语义与性能限制、Cedar 的设计取舍,以及策略即代码(Policy as Code)在 CI/CD 中的落地。


By .