多租户 SaaS 权限怎么设计?从组织、角色到资源隔离

多租户 SaaS 权限怎么设计?从组织、角色到资源隔离 很多 SaaS 系统一开始只有个人用户。数据库表里有一个user_id查询数据时加上WHERE user_id ?基本就能满足需求。但当系统开始支持企业版、团队协作、成员管理后权限模型就会变复杂。例如一个企业有多个成员 不同成员权限不同 管理员可以管理组织 普通成员只能查看自己的内容 某些记录属于整个团队 某些数据只属于个人这时如果继续只靠user_id判断很快就会出现权限混乱。多租户 SaaS 权限设计的核心是先区分几个概念用户是谁 他属于哪个租户 他在租户里是什么角色 他能访问哪些资源一、什么是多租户多租户可以简单理解为一套系统服务多个企业或组织但不同组织之间的数据必须隔离。例如A 公司使用同一个 SaaS 系统 B 公司也使用同一个 SaaS 系统它们可以共用同一套代码、同一套服务甚至同一个数据库集群。但 A 公司不能看到 B 公司的数据。因此系统中通常会引入一个核心字段tenant_id也可以叫organization_id team_id workspace_id company_id具体命名不重要重要的是它代表“数据属于哪个组织”。二、基础表结构怎么设计一个简单的多租户模型可以包含三张核心表。用户表CREATE TABLE user ( id BIGINT PRIMARY KEY, email VARCHAR(128) NOT NULL, name VARCHAR(64), created_at DATETIME NOT NULL );租户表CREATE TABLE tenant ( id BIGINT PRIMARY KEY, name VARCHAR(128) NOT NULL, plan_type VARCHAR(32), created_at DATETIME NOT NULL );成员关系表CREATE TABLE tenant_member ( id BIGINT PRIMARY KEY, tenant_id BIGINT NOT NULL, user_id BIGINT NOT NULL, role VARCHAR(32) NOT NULL, status VARCHAR(32) NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY uk_tenant_user (tenant_id, user_id) );这里不要把tenant_id直接写死在用户表里。因为一个用户未来可能属于多个组织。例如用户自己有个人账号 同时是 A 公司成员 又被邀请进 B 公司项目用成员关系表会更灵活。三、角色怎么设计最常见的是 RBAC也就是基于角色的访问控制。基础角色可以设计为OWNER组织拥有者 ADMIN管理员 MEMBER普通成员 VIEWER只读成员不同角色拥有不同能力权限动作OWNERADMINMEMBERVIEWER管理成员是是否否修改套餐是否否否创建任务是是是否查看团队记录是是按规则按规则删除组织是否否否一开始不建议把角色设计得太细。如果上来就有十几个角色后期维护会很麻烦。更推荐先用少量稳定角色覆盖主要场景再在复杂业务中补充具体权限点。四、权限判断不要只看角色很多系统会写成if (member.getRole().equals(ADMIN)) { allow(); }这样虽然简单但不够灵活。更好的方式是判断某个用户是否拥有某个动作权限。例如permissionService.check( userId, tenantId, record:delete );权限点可以设计成member:invite member:remove record:view record:delete task:create billing:manage setting:update这样业务代码不需要关心用户是管理员还是拥有者只需要关心他能不能执行当前动作。角色和权限点之间的对应关系可以放在配置中。五、数据隔离必须放在后端前端可以隐藏按钮但不能作为权限安全边界。例如普通成员不应该看到“删除团队记录”按钮。但即使按钮被隐藏用户仍然可能通过接口请求DELETE /api/records/10001所以后端必须检查当前用户是否属于该租户 当前资源是否属于该租户 当前用户是否有删除权限例如查询记录时SELECT * FROM translation_record WHERE id ? AND tenant_id ?;不要只写WHERE id ?否则只要猜到资源 ID就可能越权访问其他组织的数据。六、资源表都要带 tenant_id 吗多数业务表都建议带上tenant_id。例如CREATE TABLE translation_record ( id BIGINT PRIMARY KEY, tenant_id BIGINT NOT NULL, created_by BIGINT NOT NULL, title VARCHAR(128), created_at DATETIME NOT NULL );这样做有几个好处查询时可以直接按租户过滤 更容易建立联合索引 方便做数据归档和迁移 权限判断更明确常见索引可以这样建CREATE INDEX idx_tenant_created ON translation_record(tenant_id, created_at);这样查询某个组织下的记录列表时性能会更稳定。七、个人资源和团队资源要分清SaaS 产品里经常同时存在两类资源个人资源 团队资源例如同言翻译Transync AI这类实时翻译产品用户可能既有个人翻译记录也可能在企业组织下产生会议记录、翻译任务、会议总结和成员使用数据。这时就要明确个人记录只有创建者能看 团队记录组织内有权限的人能看 企业使用明细管理员或拥有者能看可以在资源表中增加owner_type owner_id tenant_id created_by例如owner_type PERSONAL owner_id user_id owner_type TENANT owner_id tenant_id这样系统可以同时支持个人版和企业版而不是为两套业务复制两套表。八、邀请成员也要考虑状态成员邀请通常不只是插入一条成员记录。更完整的状态包括INVITED已邀请未接受 ACTIVE已加入 REMOVED已移除 EXPIRED邀请过期成员表可以增加status字段。用户真正登录并接受邀请后再变成ACTIVE。权限判断时只允许ACTIVE成员访问组织资源。if (!member.getStatus().equals(ACTIVE)) { throw new ForbiddenException(); }这样可以避免“邀请了但未加入”的账号提前拥有权限。九、权限系统要记录操作日志企业版功能通常需要审计。例如谁邀请了成员 谁删除了记录 谁修改了组织设置 谁查看了使用明细 谁调整了套餐或席位可以建立操作日志表CREATE TABLE audit_log ( id BIGINT PRIMARY KEY, tenant_id BIGINT NOT NULL, operator_id BIGINT NOT NULL, action VARCHAR(64) NOT NULL, target_type VARCHAR(64), target_id BIGINT, created_at DATETIME NOT NULL );操作日志的价值不只是在出问题时追责也能帮助管理员理解团队内部发生了什么。十、多租户权限检查清单设计 SaaS 权限时可以检查1. 是否区分 user_id 和 tenant_id 2. 一个用户是否可以加入多个组织 3. 成员关系是否有状态 4. 是否定义了清晰的角色 5. 角色是否映射到具体权限点 6. 后端是否校验资源所属租户 7. 业务表是否包含 tenant_id 8. 个人资源和团队资源是否分开建模 9. 管理员和普通成员的数据范围是否不同 10. 是否记录关键操作日志总结多租户 SaaS 权限设计不能只靠一个user_id字段解决。更稳妥的模型通常包括用户表 租户表 成员关系表 角色与权限点 资源 tenant_id 隔离 个人资源与团队资源区分 操作审计日志对于个人版产品来说权限模型可以简单一些。但一旦产品进入企业版、团队协作、组织管理阶段就应该尽早建立多租户模型。否则后期再补tenant_id、角色、成员状态和资源隔离改造成本会非常高。