RBAC 模型的简单实现
创作时间:
作者:
@小白创作中心
RBAC 模型的简单实现
引用
CSDN
1.
https://blog.csdn.net/qq_53195681/article/details/146285115
RBAC(Role-Based Access Control,基于角色的访问控制)是一种广泛应用的权限管理模型。它的核心思想是通过角色来管理权限,而不是直接分配权限给用户。用户被赋予一个或多个角色,而每个角色拥有不同的权限。
RBAC 模型基本介绍
RBAC(Role-Based Access Control,基于角色的访问控制)是一种广泛应用的权限管理模型。它的核心思想是通过角色来管理权限,而不是直接分配权限给用户。用户被赋予一个或多个角色,而每个角色拥有不同的权限。
RBAC 的核心组件
- 用户(User):系统的使用者。
- 角色(Role):一组权限的集合。
- 权限(Permission):对系统资源的具体操作(如读、写、删除等)。
- 用户-角色分配(User-Role Assignment):将用户与角色关联。
- 角色-权限分配(Role-Permission Assignment):将角色与权限关联。
RBAC 的工作流程
- 管理员定义角色(如管理员、编辑、访客)。
- 为每个角色分配相应的权限(如管理员可以读写,访客只能读)。
- 将用户分配到相应的角色。
- 用户通过角色间接获得权限
举例:
场景:存在某系统,系统包含 1 ~ 10 个菜单目录,现在用户群体 A 需要拥有 1~3 的菜单权限,用户群体 B 需要拥有 3~5 菜单的权限,用户群体 C 需要拥有 6~10 的菜单权限……
RBAC 处理方式:
- 系统管理员定义角色,为用户群体 A 创建角色 A1,A1包含 1~3 的菜单权限,为用户群里 B 创建角色 A2……
- 将角色分配到不同用户群体。
- 不同用户根据分配到的角色,间接获得系统的部分权限。
RBAC 优缺点
优点
- 简化权限管理:通过角色间接分配权限,减少了直接管理用户权限的复杂性。
- 易于扩展:新增用户只需分配角色,无需重新定义权限。
- 支持最小权限原则:可以为角色分配最小必要的权限,降低安全风险。
- 适合多用户系统:在企业级应用中,RBAC 可以很好地支持复杂的权限需求。
缺点
- 角色爆炸问题:当角色过多时,管理角色本身会变得复杂。
- 灵活性不足:对于需要动态权限的场景(如基于时间、地点的权限),RBAC 的支持较弱。
- 权限粒度有限:RBAC 的权限控制通常是粗粒度的,难以实现细粒度的权限控制。
RBAC 的实现
数据库设计
图中由左至右,依次为:菜单表、菜单-角色对应表、角色表、角色-用户对应表、用户表;
菜单表和角色表之间为多对多关系;
角色表和用户表之间为多对多关系;
后端实现(SpringBoot + SaToken)
@PostMapping("/addMenu")
@Operation(summary = "新增菜单")
@SaCheckPermission("system:menu:add") // 只有用户含有该权限标识符,才会放行请求,否则抛出异常
public ResponseResult addMenu(@Validated @RequestBody MenuForm menuForm) {
log.info("addMenu:{}", menuForm);
menuService.addMenu(menuForm, StpUtil.getLoginId().toString());
return ResponseResult.ok().message("添加成功");
}
SaCheckPermission实现原理
public void checkPermissionAnd(String... permissionArray){
// 先获取当前是哪个账号id
Object loginId = getLoginId();
// 如果没有指定权限,那么直接跳过
if(permissionArray == null || permissionArray.length == 0) {
return;
}
// 获取该用户的所有权限
List<String> permissionList = getPermissionList(loginId);
for (String permission : permissionArray) {
// 判断该权限是否在该用户的权限列表中,若不存在,则抛出 NotPermissionException 异常
if(!hasElement(permissionList, permission)) {
throw new NotPermissionException(permission, this.loginType).setCode(SaErrorCode.CODE_11051);
}
}
}
前端实现
前端实现主要涉及动态路由和菜单的加载。通过从后端获取路由信息,动态生成前端路由和菜单结构。
动态路由
const dynamicRoutesFromBackend = (backendRoutes: MenuEntity[]): RouteType[] => {
// 辅助函数:如果路径以 '/' 开头,则去掉 '/'
const normalizePath = (path?: string): string => {
return path && path.startsWith('/') ? path.slice(1) : path || ''; // 确保路径存在后再处理
};
// 生成动态路由
const dynamicChildren: RouteType[] = [];
backendRoutes
.filter(route => route.status === 1)
.forEach(route => {
if (route.menuType === 'M') {
const normalizedPath = normalizePath(route.path);
if (normalizedPath && !route.children) {
dynamicChildren.push({
path: normalizedPath,
element: withLoadingComponent(
lazy(() =>
(modules[`/src/views/${normalizedPath}/index.tsx`]?.()
.then((module) => ({ default: (module as { default: React.ComponentType }).default }))) // 类型断言
|| import('../views/Error')
.then((module) => ({ default: (module as { default: React.ComponentType }).default }))
)
),
children: []
});
}
if (!route.path && route.children) {
route.children.forEach(childRoute => {
const normalizedChildPath = normalizePath(childRoute.path);
dynamicChildren.push({
path: normalizedChildPath,
element: withLoadingComponent(
lazy(() =>
modules[`/src/views/${normalizedChildPath}/index.tsx`]
? (modules[`/src/views/${normalizedChildPath}/index.tsx`] as () => Promise<{ default: React.ComponentType }> )() // 使用类型断言并确保返回 Promise<{ default: React.ComponentType }>
: import('../views/Error').then((mod) => ({ default: mod.default })) // 错误页面的处理
)
),
children: []
});
});
}
}
});
}
动态加载菜单
function covertRoutesToMenuItems(routes: RouteType[]): MenuItem[] {
// 查找 path 为 '/' 的根路由
const rootRoute = routes.find(route => route.path === '/');
if (!rootRoute || !rootRoute.children) {
return [];
}
// 递归解析子路由
const convertToMenu = (routeList: RouteType[]): MenuItem[] => {
return routeList
.filter(route => !route.hidden) // 过滤掉 hidden 为 true 的路由
.map(route => ({
key: route.path,
label: route.name,
icon: route.icon,
children: route.children ? convertToMenu(route.children) : undefined, // 递归处理子菜单
}));
};
// 开始处理 '/' 的子路由
return convertToMenu(rootRoute.children);
}
完整代码
前端完整代码地址:Gitee
后端完整代码地址:Gitee
热门推荐
如何找文献的原始数据库
曾艳:守护三峡文物的“忠州女将”
兄弟姐妹之间,有这些表现,也许早就不是一家人了
北京哪个科技馆好玩?
非高峰时段、客货分离!大兴机场线也开始运快递啦
绍兴和议的内容是什么?绍兴和议的签订对宋朝有何影响?
眼压太高了要怎么降低?这点很重要
探寻菠萝蜜的原乡(从历史到现实,菠萝蜜生长的地理环境和文化背景)
佛寺里面一般种什么花(6款“寺庙”独有的花卉,每款都是药材)
一文读懂 | 动车、高铁的编号规则
如何正确处理商住房产权问题?这些处理方式有哪些法律风险?
商住两用房50年产权到期了怎么办
高中生助学金申请书写作指南及范文
王文采手稿《植物分类学简史》:一位植物分类学大家的学术回顾与展望
2024上海职工生活成本系列调查之“住” :力求“通勤时长”与“房租”最优解
超过10年的二手车还有必要买车损险吗?
洗地机自动上下水是解放双手还是新坑?安装费劲维护贵,这些家庭慎入!
电池跳电的原因和解决方法是什么?这种问题对电池性能有何影响?
怎么用excel做族谱
震瓯说法|企业家签订婚前协议的必要性
体检发现窦性心律不齐是得了心脏病吗?
化疗期间可以吃西洋参吗
智能手机的正常温度是多少以及如何防止过热?
健康科普:你的眼药点对了吗?
《自然》揭晓2025年七大技术突破,生命科学领域独占四席
工资、养老金将有新消息,事关退休和在职,很重要,一起了解下
怀孕期间这些过敏食物要少吃
双氧水可以美白皮肤和祛斑吗?专业解析及安全替代方案
夫妻之间吵架了怎么和解缓和矛盾
夹竹桃的耐寒温度有多高(探究夹竹桃对低温的适应能力及其保护方法)