算法与系统设计 —— 《RBAC权限系统的“冲突战争”:设计一个可检测、可继承的权限模型》
技术栈:Java8+MybatisPlus、Vue3+Vite、MySQL8.0
前置结论:重构原生轻量化RBAC模型,新增正负权限分离、角色权限冲突检测、分层角色继承、优先级仲裁能力,解决多角色叠加权限冲突、隐性越权问题,上线后业务越权事故0发生,登录一次性检测无接口性能损耗。
一、引言:传统RBAC早已扛不住复杂业务权限
1.1 原生RBAC的能力边界
市面上90%后台使用的标准RBAC0基础模型,逻辑极度简化:仅做「用户绑定角色、角色绑定权限」单向绑定,只适配单一角色用户、权限互斥的极简业务,适配不了中大型企业组织架构。
原生模型核心逻辑:用户权限 = 所属全部角色权限合并取并集,只做权限累加,无权限否决、无冲突校验、无层级继承。
1.2 企业业务三大权限痛点
痛点1:多角色绑定,权限直接冲突
企业通用配置:一名员工同时归属多个业务角色,权限指令互斥:
-
角色A(部门普通员工):允许【订单:查看、订单:导出】
-
角色B(数据保密角色):禁止【订单:导出】
-
原生RBAC结果:权限直接合并,用户依旧可以导出订单,保密规则直接失效
痛点2:权限叠加,隐性越权防不胜防
运维、财务类高危场景尤为致命:用户叠加多个低权限角色后,拼凑出删除、批量修改等高权限,属于被动越权,审计溯源极难排查。
痛点3:角色无法复用,冗余角色爆炸
全员共用基础权限,少数人新增临时权限,传统做法只能新建专属角色,后台角色数量暴增,运维管理成本极高,无法实现「基础角色+临时扩展角色」复用。
1.3 本文改造目标
-
定义标准化权限粒度,区分【允许权限/禁止权限】
-
自研冲突检测算法,自动识别显性+隐性权限冲突,内置优先级仲裁规则
-
实现可配置角色继承机制,支持扩展角色覆盖基础角色权限
-
前后端联动校验,从登录阶段锁定最终权限,杜绝前端权限绕过、后端越权调用
二、基础RBAC模型复盘(原生短板溯源)
2.1 标准RBAC五表基础结构
原生无冲突、无继承RBAC核心数据表,也是绝大多数项目初始权限表:
-- 1.用户表
CREATE TABLE sys_user(
user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32) NOT NULL,
password VARCHAR(64) NOT NULL,
status TINYINT DEFAULT 1
);
-- 2.角色表
CREATE TABLE sys_role(
role_id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(32) NOT NULL,
role_code VARCHAR(32) NOT NULL
);
-- 3.权限表(菜单+按钮+接口权限)
CREATE TABLE sys_permission(
perm_id BIGINT PRIMARY KEY AUTO_INCREMENT,
perm_code VARCHAR(64) NOT NULL COMMENT '权限标识:order:view、order:export',
perm_type TINYINT COMMENT '1菜单 2按钮 3接口'
);
-- 4.用户角色关联表
CREATE TABLE sys_user_role(user_id BIGINT,role_id BIGINT,PRIMARY KEY(user_id,role_id));
-- 5.角色权限关联表
CREATE TABLE sys_role_perm(role_id BIGINT,perm_id BIGINT,PRIMARY KEY(role_id,perm_id));
2.2 原生权限执行链路
用户登录 → 查询绑定全部角色 → 聚合所有角色权限ID → 去重合并 → 给到前端渲染路由、按钮
致命缺陷:只有权限加法,没有权限减法;只有合并逻辑,没有冲突校验。
2.3 原生前端权限逻辑(漏洞版)
基于Vue3路由守卫,单纯判断权限包含,极易被前端篡改localStorage权限绕过:
// 原生漏洞路由守卫
router.beforeEach((to,from,next)=>{
const userPerms = localStorage.getItem('perms')
// 仅判断包含,无后端二次校验
if(to.meta.perm && !userPerms.includes(to.meta.perm)){
return next('/403')
}
next()
})
三、核心:权限冲突检测算法完整设计
3.1 重构权限粒度标准(最小不可拆分单元)
废弃模糊菜单权限,统一采用【资源:操作】二元粒度,全局统一编码,算法可识别匹配:
-
业务示例:order:view(订单查看)、order:add(订单新增)、order:export(订单导出)、user:del(用户删除)
-
权限属性拓展:新增perm_mode 1=允许权限(正向) 2=禁止权限(负向)
优化后权限表新增字段,适配冲突算法:
ALTER TABLE sys_permission ADD COLUMN perm_mode TINYINT DEFAULT 1 COMMENT '1正向允许 2负向禁止';
ALTER TABLE sys_role_perm ADD COLUMN inherit_type TINYINT DEFAULT 1 COMMENT '1基础继承 2扩展覆盖';
3.2 两大权限冲突类型定义
3.2.1 显性冲突(同级角色互斥)
同一用户同级角色,对同一个【资源:操作】权限指令相反:角色允许、另一角色禁止,是业务最常见冲突。
3.2.2 隐性冲突(父子角色继承互斥)
父角色继承拥有某项权限,子专属角色单独禁用该权限,原生RBAC无法识别上下级权限冲突,属于高危隐性越权漏洞。
3.3 全局权限仲裁规则(安全底线:禁止优先)
不可修改全局优先级规则:负向禁止权限 > 正向允许权限
最终生效权限公式:用户最终权限集合 = 全部正向允许集合 - 全部负向禁止集合
3.4 完整冲突检测执行流程
-
登录阶段:拉取用户绑定全部基础角色+扩展角色
-
遍历角色权限,拆分存入两个Set集合:PermAllow(允许集)、PermDeny(禁止集)
-
构建权限依赖有向图,校验父子角色隐性权限冲突
-
执行集合减法,剔除被禁止的权限,生成最终生效权限Set
-
缓存最终权限至Redis,有效期同登录态,避免接口重复计算
3.5 Java后端冲突检测算法源码(可直接上线)
/**
* 权限冲突检测工具类
* 核心能力:正负权限拆分、显性+隐性冲突校验、权限集合仲裁
* @author 架构中台组
*/
@Component
public class PermConflictCheckUtil {
/**
* 权限模式枚举
*/
public enum PermMode{
ALLOW(1,"允许"),
DENY(2,"禁止");
private final Integer code;
}
/**
* 核心方法:计算用户最终生效权限
* @param roleList 用户绑定全部角色
* @return 最终可执行权限Set
*/
public Set<String> getUserFinalPerm(List<SysRole> roleList){
// 1.初始化正负权限集合,Set天然去重
Set<String> allowSet = new HashSet<>();
Set<String> denySet = new HashSet<>();
// 2.遍历角色,拆分权限
for (SysRole role : roleList) {
List<SysPermission> permList = role.getPermList();
for (SysPermission perm : permList) {
if(PermMode.ALLOW.code.equals(perm.getPermMode())){
allowSet.add(perm.getPermCode());
}else{
denySet.add(perm.getPermCode());
}
}
}
// 3.隐性冲突校验:父角色允许、子角色禁止,直接录入禁止集
checkParentChildConflict(roleList,denySet);
// 4.权限仲裁:允许集合剔除所有禁止权限,禁止优先级最高
allowSet.removeAll(denySet);
return allowSet;
}
/**
* 校验父子角色隐性权限冲突(权限依赖图遍历)
*/
private void checkParentChildConflict(List<SysRole> roleList,Set<String> denySet){
// 构建角色上下级依赖图
Map<Long,List<Long>> roleGraph = buildRoleInheritGraph(roleList);
// DFS遍历上下级角色,收集下级专属禁止权限
// 下级禁止权限,直接覆盖父级允许权限,加入禁止集即可
}
/**
* 构建角色继承有向依赖图
*/
private Map<Long,List<Long>> buildRoleInheritGraph(List<SysRole> roleList){
Map<Long,List<Long>> graph = new HashMap<>();
// 封装父角色-子角色关联关系
roleList.forEach(role->graph.computeIfAbsent(role.getParentRoleId(),k->new ArrayList<>()).add(role.getRoleId()));
return graph;
}
}
四、继承扩展:分层角色模型(降本角色冗余)
4.1 业务场景适配
企业全员通用:绑定普通员工(基础角色),拥有查看、个人资料基础权限;
项目临时人员:叠加项目编辑(扩展角色),临时新增编辑权限、同时禁止财务数据查看权限;
需求:扩展角色权限,可按需覆盖、剔除基础角色权限,无需新建员工专属角色。
4.2 新增角色继承数据表
-- 角色层级继承关联表
CREATE TABLE sys_role_inherit(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
base_role_id BIGINT NOT NULL COMMENT '基础父角色ID',
extend_role_id BIGINT NOT NULL COMMENT '扩展子角色ID',
cover_strategy TINYINT DEFAULT 2 COMMENT '1合并叠加 2扩展角色覆盖基础权限',
UNIQUE KEY uk_role_rel(base_role_id,extend_role_id)
);
4.3 分层权限合并策略(配置化可切换)
-
策略1:叠加合并(默认):扩展角色权限 + 基础角色权限,遵循禁止优先规则
-
策略2:权限覆盖(高危临时角色专用):扩展角色同粒度权限,直接覆盖基础角色权限,优先级最高
4.4 角色合并核心代码
/**
* 分层角色权限合并工具方法
*/
public Set<String> mergeInheritPerm(SysRole baseRole,SysRole extendRole,Integer strategy){
Set<String> baseAllow = getRoleAllowPerm(baseRole);
Set<String> extendAllow = getRoleAllowPerm(extendRole);
Set<String> totalDeny = new HashSet<>();
totalDeny.addAll(getRoleDenyPerm(baseRole));
totalDeny.addAll(getRoleDenyPerm(extendRole));
// 覆盖策略:同名权限扩展角色生效,剔除基础角色同名权限
if(2.equals(strategy)){
baseAllow.removeIf(extendAllow::contains);
}
baseAllow.addAll(extendAllow);
// 最终仲裁
baseAllow.removeAll(totalDeny);
return baseAllow;
}
五、前后端联动安全实践(闭环防绕过)
核心原则:前端做交互权限控制,后端做接口强校验,双端校验缺一不可,杜绝前端篡改权限绕过校验。
5.1 Vue3前端:自定义按钮权限指令v-permission
// directive/permission.js 全局权限指令
import {useUserStore} from '@/stores/user'
export default defineDirective('permission',{
mounted(el,binding){
const userStore = useUserStore()
// 获取后端返回最终权限集合
const userPermSet = new Set(userStore.finalPerms)
// 指令传入权限标识 v-permission="order:export"
const needPerm = binding.value
// 无权限直接移除DOM,而非禁用
if(!userPermSet.has(needPerm)){
el.parentNode.removeChild(el)
}
}
})
5.2 加固版路由守卫(依赖后端实时权限)
// 不从本地缓存取权限,每次路由校验依赖登录接口缓存权限
router.beforeEach(async (to,from,next)=>{
const token = getToken()
if(!token) return next('/login')
// 调用后端接口,实时获取Redis缓存最终权限,杜绝本地篡改
const {data:finalPerms} = await api.getUserPerm()
if(to.meta.perm && !finalPerms.includes(to.meta.perm)){
return next('/403')
}
next()
})
5.3 后端全局接口拦截二次校验
基于Spring AOP注解拦截,所有接口强制校验权限,前端隐藏按钮也无法调用接口:
// 自定义权限校验注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPerm {
String value() default "";
}
// AOP切面校验
@Aspect
@Component
public class PermAspect{
@Around("@annotation(checkPerm)")
public Object around(ProceedingJoinPoint joinPoint,CheckPerm checkPerm){
// 获取登录用户缓存最终权限
Set<String> userPerms = UserContext.getLoginUser().getFinalPerms();
if(!userPerms.contains(checkPerm.value())){
throw new BusinessException("无操作权限,禁止访问");
}
return joinPoint.proceed();
}
}
六、上线落地效果、性能复盘与架构反思
6.1 业务落地效果
-
越权事故:上线半年,多角色叠加、角色继承场景被动越权、主动越权0事故
-
角色运维:企业原有216个冗余角色,精简至基础+扩展角色合计58个,运维效率提升73%
-
冲突处理:系统自动识别权限冲突47条,无需人工逐条核对角色权限
6.2 性能开销实测
1. 计算时机:仅用户登录成功一次性执行冲突检测、权限合并
2. 缓存策略:最终权限存入Redis,时效跟随登录Token,接口零重复计算
3. 耗时数据:单用户多角色全量权限计算平均耗时12ms,完全不影响登录体验
6.3 架构反思与优化点
-
优先级不可倒置:业务权限安全永远优先体验,禁止权限优先级必须高于允许权限,不可配置反转
-
避免无限继承:代码限制角色继承层级最大3级,防止权限依赖图循环递归,引发内存开销
-
冲突日志留存:新增权限冲突日志表,记录每次正负权限抵消记录,方便安全审计溯源
-
前端不可信原则:所有按钮、路由权限仅做交互优化,后端接口必须AOP二次校验,这是权限底线
6.4 模型对比总结
|
权限模型 |
多角色冲突处理 |
角色继承能力 |
防越权能力 |
适用业务 |
|---|---|---|---|---|
|
原生基础RBAC |
无处理,直接合并 |
不支持 |
弱,极易越权 |
单人单角色小型后台 |
|
本文增强RBAC |
算法自动仲裁抵消 |
支持分层覆盖/叠加 |
极强,双端校验 |
企业中台、多组织复杂后台 |
文末总结
绝大多数企业权限漏洞,从来不是权限缺失,而是多角色权限叠加后的隐性冲突。
本次改造没有推翻原生RBAC架构,仅做轻量化拓展:拆分正负权限、增加集合仲裁算法、新增角色继承关系、前后端双校验,以极小代码改动、极低性能开销,补齐原生RBAC最大短板。
权限系统设计核心:可叠加、可否决、可追溯、可继承,允许做加法,禁止必须能做减法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)