1. 多租户权限系统设计背景
在SaaS系统开发中,多租户架构下的权限管理一直是个棘手问题。我最近用PHP-Casbin实现了一套支持用户-租户-角色-权限四层关系的RBAC模型,这里分享下核心表结构设计和实现思路。
传统权限系统往往只考虑用户直接关联角色,但在多租户场景下,同一个用户在不同租户下可能需要不同的权限组合。比如张三在A公司是管理员,在B公司可能只是普通成员。这种复杂关系用常规RBAC很难优雅实现,而Casbin的ABAC特性正好能解决这个问题。
2. 核心表结构设计
2.1 用户与租户关联表
sql复制CREATE TABLE `user_tenant` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`tenant_id` int(11) NOT NULL COMMENT '租户ID',
`status` tinyint(4) DEFAULT '1' COMMENT '关联状态',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_tenant` (`user_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这个表建立了用户与租户的多对多关系。注意唯一索引防止重复关联,status字段可用于禁用特定租户下的用户。
2.2 租户角色表
sql复制CREATE TABLE `tenant_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tenant_id` int(11) NOT NULL COMMENT '租户ID',
`name` varchar(50) NOT NULL COMMENT '角色名称',
`description` varchar(255) DEFAULT NULL,
`is_system` tinyint(1) DEFAULT '0' COMMENT '是否系统预设角色',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
每个租户有自己独立的角色体系,is_system标记像"超级管理员"这类系统内置角色。
2.3 用户角色关联表
sql复制CREATE TABLE `user_tenant_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`tenant_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_tenant_role` (`user_id`,`tenant_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这张表建立了用户在特定租户下的角色绑定关系。唯一索引确保不会重复授权。
3. Casbin策略规则设计
3.1 策略表结构
sql复制CREATE TABLE `casbin_rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ptype` varchar(10) NOT NULL COMMENT '策略类型(g/p)',
`v0` varchar(100) DEFAULT NULL COMMENT '角色/用户',
`v1` varchar(100) DEFAULT NULL COMMENT '租户/资源',
`v2` varchar(100) DEFAULT NULL COMMENT '操作',
`v3` varchar(100) DEFAULT NULL,
`v4` varchar(100) DEFAULT NULL,
`v5` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这是Casbin的标准策略表,我们需要重点设计v0-v5的字段含义。
3.2 多租户RBAC策略示例
bash复制# 角色继承关系
g, admin, member, 1 # 在租户1下admin继承member权限
# 权限定义
p, member, 1, /project, read
p, admin, 1, /project, write
v1字段存储租户ID,这样相同的角色名在不同租户下可以有不同权限。
4. 权限校验实现
4.1 中间件设计
php复制function checkPermission($request) {
$tenantId = getTenantFromRequest($request);
$userId = getCurrentUserId();
$path = $request->getPathInfo();
$method = $request->getMethod();
$enforcer = new Enforcer('model.conf', 'policy.csv');
if (!$enforcer->enforce($userId, $tenantId, $path, $method)) {
throw new AccessDeniedException();
}
}
这里关键是把租户ID作为第二个参数传入,实现用户+租户的联合校验。
4.2 性能优化方案
- 使用Casbin的Watcher机制监听策略变更
- 为高频访问的租户缓存策略规则
- 批量加载用户所有租户的权限
5. 常见问题处理
5.1 跨租户数据隔离
重要:永远不要在SQL中只靠权限校验,必须同时在where条件中加入tenant_id过滤
php复制// 错误的做法
$projects = Project::where('user_id', $userId)->get();
// 正确的做法
$projects = Project::where([
'user_id' => $userId,
'tenant_id' => $currentTenantId
])->get();
5.2 超级管理员实现
在Casbin策略中添加特殊规则:
bash复制p, super_admin, *, *, full-access
然后在中间件中优先检查超级管理员身份。
6. 扩展设计思路
6.1 部门级权限控制
可以在策略中加入v3作为部门ID:
bash复制p, dept_manager, 1, /project, write, 5 # 租户1下对部门5的项目有写权限
6.2 临时权限授予
添加有效期字段:
sql复制ALTER TABLE `user_tenant_role`
ADD COLUMN `expire_time` datetime DEFAULT NULL;
在中间件中额外检查权限是否过期。