当前位置: 首页 > news >正文

SpringBoot实现RBAC权限校验模型

最近想做一个管理系统,记录下自己的消费记录,实现个文件上传之类的功能,正好最近看了下RBAC模型,想学习一下,在这分享下实现过程,方便以后查看

RBAC模型简单来说就是创建5张表(用户表,角色表,权限表,角色与用户表,角色与权限表)

通过给用户分配不同角色,以及给角色分配不同权限,实现用户的权限控制

不过这次我做的项目是个后台管理系统,主要是自己和朋友使用,所以角色其实就分成了管理员和普通用户,因此我省略掉了角色和角色权限表,这样虽然不够灵活,但毕竟使用的人少,所以管理起来也非常方便

数据库表

数据库主要创建三张表

用户表:

主要用来使用用户登录

  • id 用户主键
  • account 登录账号
  • password 登录密码
  • type 区分普通角色和管理员身份
  • username 用户名
  • status 用户的状态 
+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int         | NO   | PRI | NULL    | auto_increment |
| account   | char(15)    | YES  |     | NULL    |                |
| password  | varchar(50) | YES  |     | NULL    |                |
| type      | int         | YES  |     | NULL    |                |
| username  | varchar(20) | YES  |     | NULL    |                |
| status    | int         | YES  |     | NULL    |                |
+-----------+-------------+------+-----+---------+----------------+

权限表:

主要用来记录权限信息

  • id 权限主键
  • name 权限名称
  • parent_id 父权限的id 如果没有父权限则为null
  • status 权限是否启用
  • remark 权限备注
  • level 权限的层级,主要用来排序的字段
+-------------+-------------+------+-----+---------+----------------+
| Field       | Type        | Null | Key | Default | Extra          |
+-------------+-------------+------+-----+---------+----------------+
| id          | int         | NO   | PRI | NULL    | auto_increment |
| name        | varchar(20) | YES  |     | NULL    |                |
| parent_id   | int         | YES  |     | NULL    |                |
| status      | int         | YES  |     | NULL    |                |
| remark      | varchar(50) | YES  |     | NULL    |                |
| create_user | int         | YES  |     | NULL    |                |
| create_date | date        | YES  |     | NULL    |                |
| update_user | int         | YES  |     | NULL    |                |
| update_date | date        | YES  |     | NULL    |                |
| level       | int         | YES  |     | NULL    |                |
+-------------+-------------+------+-----+---------+----------------+

用户权限表:

实现多对多关系的表

  • id 主键
  • user_id 用户的ID
  • permission_id 权限的ID
+---------------+------+------+-----+---------+----------------+
| Field         | Type | Null | Key | Default | Extra          |
+---------------+------+------+-----+---------+----------------+
| id            | int  | NO   | PRI | NULL    | auto_increment |
| user_id       | int  | YES  | MUL | NULL    |                |
| permission_id | int  | YES  |     | NULL    |                |
+---------------+------+------+-----+---------+----------------+

主要解释下权限表,因为管理系统需要根据权限判断是否提供菜单,因此我将菜单作为了一级权限(parent_id=null),一级权限下的增删改查等操作定义为二级权限(parent_id=对应的一级权限)

如果用户拥有某些权限就在用户权限表中创建一条记录

后端实现

后端实现起来非常的方便,我使用的方法是定义一个注解通过AOP扫描这个注解,然后判断当前用户是否拥有对应的权限,如果没有直接抛出异常,通过全局异常处理器进行捕捉

定义注解类:

因为后台管理实现的非常简单,几乎不存在多个表之间相互嵌套,因此我用了个value表示当前Controller需要使用到的权限的ID

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PermissionCheckAnnotation {int value();
}

定义枚举类:

主要用来定义对应权限的ID值


public class PermissionEnum {public final static int USER_QUERY = 14;public final static int USER_UPDATE = 15;public final static int USER_CREATE = 16;
}

定义AOP切面

    @Pointcut("execution(扫描当前包中的全部文件) && @annotation(annotation.PermissionCheckAnnotation)")public void pt(){}@Around("pt()")public Object PermissionCheck(ProceedingJoinPoint jp) throws Throwable {MethodSignature signature = (MethodSignature) jp.getSignature();
// 反射拿到权限IDPermissionCheckAnnotation annotation = signature.getMethod().getAnnotation(PermissionCheckAnnotation.class);int value = annotation.value();// 获取用户IDInteger userId = UserContext.getUserId();// 查询当前用户和权限ID之间是否存在关联关系,如果不存在抛出异常Integer hasPermission = userPermissionMapper.hasPermission(value, userId);if (hasPermission == null || hasPermission == 0){throw new PermissionException("权限不足,请联系管理员");}return jp.proceed();}

定义controller

    @PermissionCheckAnnotation(PermissionEnum.USER_CREATE)@PostMapping("/createUser")public Result<String> createUser() {}

后端修改权限

前端将修改完的权限发送过来,大体格式就是一个整数数组([1,2,3,4,5]),表示当前用户的全部权限的ID集合

 权限更新的主要思路就是将该用户的全部权限删除,再重建

如果嫌弃太慢的话,我觉得可以取交集之类的操作,提升效率,或者再额外定义一个字段,用来判断当前用户权限的状态(delete字段)这样应该会比delete insert效率高一些

不过俺是个菜鸟小白+懒狗,怎么简单怎么来了

删除用户不拥有的权限

    <delete id="deletePermission">delete from user_permission where user_id = #{userId}and permission_id not in<foreach item="item" collection="permissionList" open="(" close=")" separator=",">#{item}</foreach></delete>

插入新权限

因为是全部插入,权限可能会产生重复,因此我给user_id和permission_id添加唯一约束,并在插入时忽略错误

    <insert id="createPermission">insert ignore into user_permission<foreach collection="permissionList" open="values" item="item" separator=",">(null,#{userId},#{item})</foreach></insert>

前端

我前端不是很好,只会单纯画个界面,因此我还没有实现按钮级别的权限控制,不过大体思路是自定义指令,然后检查是否拥有对应的权限,如果没有权限直接隐藏该按钮

至于菜单级别的权限,我的做法是每次都发送一个请求,判断能用多少菜单,即使被人扒出来其他界面,后端也做了权限的校验,数据还是拿不到

这里主要记录下如何在前端修改权限

查询返回的数据格式大致如下

{id: 15level: 2name: "操作查询"parentId: 1remark: "操作菜单下的查询功能"status: 1
}
{id: 1level: 1name: "操作菜单"parentId: nullremark: "操作菜单"status: 1
}

我想要在element-Plus中的树形控件中实现展示,操作功能,大致需要将上面的数据变成这样的格式

const data: Tree[] = [{label: 'Level one 1',children: [{label: 'Level two 1-1',},],},
]

这里通过dfs实现的(同层权限之间不会产生交集,所以应该能用最小生成树完成,不过俺是个懒狗,写了个最简单的方式)

const st = new Map();for (let i = 0; i < res.length; i++) {st.set(res[i].id, 0);
}
const list = [];
for (let i = 0; i < res.length; i++) {if (res[i].parentId == null) {const children = dfs(i, res);list.push(children);}
}
tree.value = list;function dfs(idx, res) {st.set(res[idx].id, 1);const children = []for (let i = 0; i < res.length; i++) {if (st.get(res[i].id) == 1) {continue;}if (res[i].parentId == res[idx].id) {children.push(dfs(i, res))}}if (children.length == 0)return { id: res[idx].id, label: res[idx].name }return { id: res[idx].id, label: res[idx].name, children }
}

然后把操作完的数据扔到elTree中

<el-tree v-loading="treeLoading"
@check="checkChange" 
style="max-width: 200px" 
ref="elTreeRef"
:data="tree"
:props="defaultProps" 
show-checkbox 
node-key="id" />

然后就是用户的初始权限,通过点击不同的用户向后端查询该用户拥有的权限,el-tree有个属性可以设置默认勾选的复选框,但我不知道为什么点击不同用户时,树中上一个勾选的复选框不会被取消,因此使用了el-tree的方法,当点击不同用户时触发,传入的数据是一个整数数组([1,2,3]),表示勾选的节点的id,需要在el-tree标签中提前指定node-key

elTreeRef.value.setCheckedKeys(hasPermission.value)

最后就是权限修改,大致就是用户点击不同的复选框,选择或取消不同的权限,通过绑定@check事件,然后获取里面的数据,这里说一下当点击子复选框时,父复选框也会触发一次,当父选框下的全部子复选框都取消时,父复选框也会取消,通过下面这个方法能拿到当前勾选的复选框,然后发送数据

    const checkChange = function (data1, data2) {currentPermission = [...data2.checkedKeys, ...data2.halfCheckedKeys]}

总结

就这样,实现了一个简单的权限校验模型,虽然缺少了角色表,但大体功能上大差不差,添加上角色表后,可能还得考虑权限的迁移,权限的分配等等问题

不过对于一个简单的系统来说还是十分够用力

还有为什么csdn还对封面图大小设置了,把我的卡兹米都压成什么样了😭😭😭


http://www.mrgr.cn/news/96482.html

相关文章:

  • C++进阶——位图+布隆过滤器+海量数据处理
  • 小学数学解题方法专题3-列表法-提升2
  • 3.27学习总结 爬虫+二维数组+Object类常用方法
  • RocketMQ - 从消息可靠传输谈高可用
  • 在Qt中判断输入的js脚本是否只包含函数
  • fluent_UDF学习笔记
  • 横扫SQL面试——连续性登录问题
  • 在bootstrap下实现万年历
  • Muduo网络库实现 [二] - Buffer模块
  • 基于自定义注解+反射+AOP+Redis的通用开关设计:在投行交易与风控系统的落地实践
  • SpringBoot 概述
  • Dubbo分布式开发框架
  • 机器学习课程
  • 深入解析音频:格式、同步及封装容器
  • 一文聊聊接入钉钉H5微应用系统实现免登操作技术思路实现验证
  • 【导航定位】GNSS数据协议-RINEX OBS
  • iOS审核被拒:Missing privacy manifest 第三方库添加隐私声明文件
  • 最小二乘求解器lstsq,处理带权重和L2正则的线性回归
  • nlf 原理剖析
  • 人工智能通识速览一(神经网络)(编辑中)