开发者

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  |     | NphpULL    |                |
| 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表示当前Cojavascriptntroller需要使用到的权限的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();
// 反射拿到权限ID
        PermissionCheckAnnotation annotation = signature.getMethod().getAnnotation(PermissionCheckAnnotation.class);
        int value = annotation.value();
        // 获取用户ID
        Integer userId = UserContext.getUserId();
        // 查询当前用户和权限ID之间是否存在关联关系,如果不存在抛出异常
        Integer hASPermission = userPermissionMapper.hasPermission(value, userId);php
        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: 15
    level: 2
    name: "操作查询"
    parentId: 1
    remark: "操作菜单下的查询功能"
    status: 1
}
{
    id: 1
    level: 1
    name: "操作菜单"
    parentId: null
    remark: "操作菜单"
    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" 
styNgedfimnle="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还对封面图大小设置了,把我的卡兹米都压成什么样了

到此这篇关于SpringBoot实现RBAC权限校验模型的示例的文章就介绍到这了,更多相关SpringBoot RBAC权限校验 内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜