SaaS系统RBAC+多租户权限体系设计(原理+表结构+SQL示例)
SaaS系统RBAC+多租户权限体系设计(原理+表结构+SQL示例)
一、核心设计思想:RBAC 与 租户的本质区别
1.1 核心定位区分
很多开发者疑惑:已有成熟的 RBAC 权限体系,为什么 SaaS 系统必须额外引入租户概念?核心原因:RBAC 管控功能权限,租户管控数据边界,二者无法互相替代,是两层独立的权限校验关卡。
-
RBAC(用户-角色-权限):解决 用户能做什么 的问题,管控按钮、接口、菜单等功能操作,无数据隔离能力。
-
租户(Tenant):解决 用户能看哪些数据 的问题,是 SaaS 系统最高级的数据隔离边界,实现多企业共用一套系统、数据互不互通。
1.2 仅用RBAC的致命缺陷
若系统只依赖 RBAC 做权限控制,会出现严重的数据越权漏洞:
场景:租户A(企业A)、租户B(企业B)的管理员,均拥有系统 admin 超级角色
-
RBAC 仅限制功能:admin 拥有全部增删改查权限
-
无租户隔离:企业A管理员可直接查询、操作企业B的所有业务数据
-
补救弊端:若为每个企业单独新建角色,会导致角色无限膨胀,且手动写数据过滤条件极易遗漏,产生安全漏洞
1.3 标准双层权限校验模型
行业通用 SaaS 权限校验逻辑,两道关卡缺一不可:
-
第一层:租户硬边界(数据隔离):系统自动根据登录用户的租户ID,强制过滤数据,用户只能访问所属租户的数据,从根源杜绝跨租户越权。
-
第二层:RBAC功能校验(操作隔离):在当前租户的数据范围内,判断用户是否拥有对应接口、按钮的操作权限。
1.4 权限层级完整结构
租户 → 组织/部门 → 用户 → 角色 → 功能权限(菜单/接口/按钮)
1.5 RBAC与租户对比表
| 维度 | RBAC权限体系 | 多租户体系 |
|---|---|---|
| 管控对象 | 功能、接口、按钮、菜单 | 业务数据、系统资源、业务配置 |
| 核心作用 | 控制用户操作权限 | 控制用户数据访问边界 |
| 作用范围 | 单个租户内部 | 跨租户全局隔离 |
| 越权风险 | 功能越权(无权操作按钮/接口) | 数据越权(查看/操作其他企业数据) |
| 可替代性 | 无法替代租户 | 无法替代RBAC细粒度权限 |
二、多租户三种隔离方案选型
主流 SaaS 系统多租户隔离方案,适配不同业务场景:
| 隔离方案 | 隔离方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 共享库共享Schema(主流) | 所有租户共用数据库和数据表,通过 tenant_id 行级隔离 |
成本低、运维简单、扩展方便 | 隔离性最弱,需代码强制过滤租户数据 | 90%中小型SaaS系统 |
| 共享库独立Schema | 共用数据库,每个租户独立一套数据表Schema | 隔离性中等,数据独立性强 | 开发、运维复杂度高 | 中大型企业付费客户 |
| 独立数据库 | 一个租户对应一个独立数据库 | 隔离性最高,安全性极强 | 成本高、运维繁琐、资源占用大 | 大型政企、金融等高安全需求客户 |
本文采用共享库共享Schema(行级租户隔离),为行业通用标准方案。
三、完整数据库表设计(MySQL8.0)
3.1 设计核心规范
-
所有业务表、权限关联表必须携带
tenant_id -
tenant_id = 0代表平台超级管理员,拥有全租户权限 -
权限菜单表全局通用,所有租户共用一套权限编码
-
角色、用户、权限关联表租户隔离,租户间数据互不干扰
3.2 建表SQL语句
-- 1.租户主表
CREATE TABLE sys_tenant (
tenant_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '租户ID',
tenant_name VARCHAR(100) NOT NULL COMMENT '租户名称(企业名)',
tenant_code VARCHAR(64) UNIQUE NOT NULL COMMENT '租户编码',
contact_phone VARCHAR(20) COMMENT '联系电话',
expire_time DATETIME COMMENT '套餐到期时间',
status TINYINT DEFAULT 1 COMMENT '0禁用 1正常',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT='租户表';
-- 2.用户表(每个用户归属唯一租户)
CREATE TABLE sys_user (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '所属租户ID',
username VARCHAR(50) NOT NULL COMMENT '账号',
password VARCHAR(100) NOT NULL COMMENT '加密密码',
real_name VARCHAR(50),
phone VARCHAR(20),
status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tenant (tenant_id)
) COMMENT='系统用户表';
-- 3.角色表(租户隔离,各租户角色独立)
CREATE TABLE sys_role (
role_id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '租户id,0=平台全局角色',
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
role_code VARCHAR(50) NOT NULL COMMENT '角色编码 admin/editor',
remark VARCHAR(200),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tenant (tenant_id)
) COMMENT='角色表';
-- 4.用户-角色中间表(多对多关联)
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
UNIQUE KEY uk_user_role (tenant_id,user_id,role_id),
INDEX idx_tenant (tenant_id)
) COMMENT='用户角色关联';
-- 5.权限菜单表(全局通用,无租户隔离)
CREATE TABLE sys_permission (
perm_id BIGINT PRIMARY KEY AUTO_INCREMENT,
perm_name VARCHAR(100) NOT NULL COMMENT '权限名称',
perm_code VARCHAR(100) UNIQUE NOT NULL COMMENT '权限标识:order:list,user:add',
type TINYINT COMMENT '1菜单 2按钮 3接口',
path VARCHAR(200) COMMENT '前端路由',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT='功能权限表';
-- 6.角色-权限中间表(多对多关联,租户隔离)
CREATE TABLE sys_role_perm (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
perm_id BIGINT NOT NULL,
UNIQUE KEY uk_role_perm (tenant_id,role_id,perm_id),
INDEX idx_tenant (tenant_id)
) COMMENT='角色权限关联';
-- 7.业务表示例(订单表,所有业务表需统一携带tenant_id)
CREATE TABLE biz_order (
order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
tenant_id BIGINT NOT NULL COMMENT '归属租户',
order_no VARCHAR(50) UNIQUE NOT NULL,
amount DECIMAL(12,2),
status TINYINT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_tenant (tenant_id)
) COMMENT='业务订单表';
3.3 数据表关系说明
-
租户包含多个用户、多个自定义角色;
-
用户通过用户角色关联表绑定角色,实现多角色挂载;
-
角色通过角色权限关联表绑定全局权限,实现功能授权;
-
所有业务数据绑定租户ID,实现行级数据隔离。
四、核心业务SQL示例
模拟登录用户信息:user_id=1001,tenant_id=2001(普通租户管理员)
4.1 租户数据隔离查询(核心)
所有业务查询自动拼接租户条件,禁止前端传参控制租户ID,从Token解析自动获取:
-- 仅查询当前租户的订单数据,杜绝跨租户越权
SELECT * FROM biz_order
WHERE tenant_id = 2001;
4.2 RBAC权限校验查询
校验当前用户是否拥有 order:list 订单查询权限:
SELECT 1
FROM sys_user_role ur
JOIN sys_role_perm rp ON ur.role_id = rp.role_id
JOIN sys_permission p ON rp.perm_id = p.perm_id
WHERE ur.user_id = 1001
AND ur.tenant_id = 2001
AND p.perm_code = 'order:list'
LIMIT 1;
查询有结果 = 有权限;无结果 = 403无权限访问。
五、后端接口校验伪代码
// 1.从Token解析当前登录用户信息
LoginUser loginUser = getLoginUser();
Long currentTenantId = loginUser.getTenantId();
// 2.防越权校验:禁止用户操作非自身租户数据
if (queryParam.getTenantId() != null
&& !currentTenantId.equals(queryParam.getTenantId())) {
throw new RuntimeException("禁止跨租户越权访问");
}
// 3.RBAC功能权限校验
checkPermission("order:list");
// 4.执行查询,AOP自动拼接租户过滤条件
sql.where("tenant_id = #{currentTenantId}");
六、开发避坑核心规范
-
❌ 禁止前端传递
tenant_id查询参数,必须从登录Token解析,防止篡改越权; -
❌ 所有业务表、权限关联表禁止缺失
tenant_id字段; -
✅ 平台超级管理员(tenant_id=0),查询数据不拼接租户条件,可查看全租户数据;
-
✅ 租户内管理员仅能操作自身租户数据,无法跨租户访问;
-
✅ 统一通过AOP/拦截器自动追加租户过滤条件,避免手动写SQL遗漏。

浙公网安备 33010602011771号