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 权限校验逻辑,两道关卡缺一不可:

  1. 第一层:租户硬边界(数据隔离):系统自动根据登录用户的租户ID,强制过滤数据,用户只能访问所属租户的数据,从根源杜绝跨租户越权。

  2. 第二层: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 数据表关系说明

  1. 租户包含多个用户、多个自定义角色;

  2. 用户通过用户角色关联表绑定角色,实现多角色挂载;

  3. 角色通过角色权限关联表绑定全局权限,实现功能授权;

  4. 所有业务数据绑定租户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遗漏。

posted @ 2026-08-28 09:19  当下是吾  阅读(19)  评论(0)    收藏  举报