RBAC-基于角色的访问控制(附经典五张数据表结构)

RBAC-基于角色的访问控制

传统的权限分配的方式是将用户与权限绑定,也就是直接将权限绑定到用户身上,例如之前盛行的ACL模型。这种做法的缺陷在于效率低下,设置权限时没有统一的标准。当用户量过多时,操作复杂麻烦。

什么是RBAC

RBAC 是基于角色的访问控制(Role-Based Access Control ),在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。

简单来说,一个用户拥有多个角色,每个角色拥有若干权限。这样就构成了“用户-角色-权限”的授权模型。在这个模型中,用户与角色、角色与权限之间是多对多的关系。

在RBAC中需要对系统的所有资源进行权限控制,系统资源可以简单地概括为静态资源(功能操作、数据列)和动态资源(数据),也分别称为对象资源和数据资源。RBAC的目标是对系统中所有对象资源和数据资源进行权限控制

什么是角色

角色可以理解为一定数量的权限的集合,也就是权限的载体。当用户数量越来越大的时候,就需要为用户分组,每个用户组内拥有不同的用户,除了可以给用户授权外,也可以给用户组授权。如此一来,用户拥有的所有权限就是用户个人拥有的权限与该用户所在用户组拥有的权限之和。

角色同时具有用户集合和权限集合的概念,角色是把两个集合联系在一起的中间媒介。

什么是权限

权限表现在对功能模块的操作,例如对上传文件的删除和修改,对菜单的访问,甚至是对页面上某个按钮或图片可见性的控制,都是属于权限的范畴。在做数据表建模时可将功能操作和资源进行统一管理,直接与权限表进行关联,这样更具便捷性和易扩展性。

可见权限分为

  • 页面权限
    Web系统由一个一个页面组成,页面构成了模块,用户能够看到这个页面的菜单,是能进入某个页面就成为页面权限。
  • 操作权限
    用户在系统中交互动作都是操作权限,典型的例如增删改查操作。
  • 数据权限
    业务管理系统中对数据私密性有要求时,哪些人可以看到哪些数据,看不到哪些数据。

RBAC本质

RBAC的本质是单纯地用户和权限进行解耦,将用户与角色、角色与权限关联。

RBAC认为权限的过程可以抽象为:判断谁(Who)是否可以对什么(What)进行怎样(How)的访问操作(Operator),简单来说,就是对这个逻辑表达式的值是否为真(True)的求解过程。进而将权限问题转换为Who、What、How的问题,因此Who、What、How构成了访问权限的三元组。

  • Who 权限的拥有者或主体,如User、Role、Group、Actor、Principal...
  • What 权限针对的对象或资源,如Operator、Object、Class、Resource...
  • How 具体的权限,主要是Privilege,可进行正向授权和反向授权。

RBAC安全原则

RBAC支持公认的安全原则:

  • 最小特权的安全原则

在RBAC模型中可通过限制分配给角色权限的多少和大小来实现,分配给与某用户对应的角色的权限只要不超过该用户完成其任务的需要就可以了。

  • 责任分离原则

在RBAC模型中可通过在完成敏感任务过程中分配两个任务上相互约束的两个角色来实现,例如在清查账目时,只需要设置财务管理员和会计两个角色参加就可以了。

  • 数据抽象原则

数据抽象是借助于抽象许可权的概念实现的,例如在账目管理活动中,可使用信用、借方等抽象许可权,而不是使用操作系统提供的读、写、可执行等具体的许可权。RBAC中并强迫实现这些原则,安全管理员可以允许配置RBAC模型使其不支持这些原则。因此,RBAC支持数据抽象的程度与实现细节有关。

RBAC优缺点

  • 基于角色与权限之间的变化要比角色与用户之间的变化相对少得多,也就减少了授权管理的复杂性,降低了管理的开销。
  • 灵活地支持企业的安全策略,并对企业的变化由很大的伸缩性。
  • RBAC没有提供操作顺序控制机制,这一缺陷使得RBAC模型很难应用于要求有严格操作次序的实体系统。

 

 

RBAC经典五张表

 

在权限管理系统中一般会涉及5张表,分别为

  • 1.sys_users 用户表
  • 2.sys_roles 角色表
  • 3.sys_permissions 权限或资源表
  • 4.sys_users_roles 用户角色关系表
  • 5.sys_roles_permissions 角色权限关系表

用户表、角色表、权限表

用户和组多对多关系,存在中间表

用户和权限多对多关系,存在中间表

用户表

CREATE TABLE `sys_users` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` varchar(45) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户名',
  `password` varchar(45) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
  `sex` tinyint(1) DEFAULT '0' COMMENT '性别 0男 1女',
  `idcard` varchar(225) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '身份证',
  `email` varchar(45) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(45) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '电话',
  `status` tinyint(1) DEFAULT '0' COMMENT '状态(0启用 1冻结 2删除)',
  `createtime` datetime DEFAULT NULL COMMENT '用户创建时间',
  `lasttime` datetime DEFAULT NULL COMMENT '最后登录时间',
  `salt` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '加密盐值',
  `roleid` int(225) DEFAULT NULL COMMENT '角色id',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `updatetime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

角色表

CREATE TABLE `sys_roles` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色名称',
  `description` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色描述',
  `pid` bigint(11) DEFAULT NULL COMMENT '父节点,当前角色的上级节点',
  `available` tinyint(1) DEFAULT NULL COMMENT '是否锁定(0否 1是)',
  `createtime` datetime DEFAULT NULL COMMENT '角色创建时间',
  `updatetime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

权限表

CREATE TABLE `sys_permissions` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '权限id',
  `name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限名称',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限描述',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限访问路径',
  `perms` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '权限标识',
  `parentid` bigint(11) DEFAULT NULL COMMENT '父级权限id',
  `type` tinyint(1) DEFAULT NULL COMMENT '类型   0:目录   1:菜单   2:按钮',
  `order` int(3) DEFAULT NULL COMMENT '排序',
  `icon` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '图标',
  `status` tinyint(1) DEFAULT '0' COMMENT '状态:0有效;1删除',
  `createtime` datetime DEFAULT NULL COMMENT '创建时间',
  `updatetime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

用户-角色表

CREATE TABLE `sys_users_roles` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '用户角色关系表id',
  `userid` bigint(11) DEFAULT NULL COMMENT '用户id',
  `roleid` bigint(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

角色-权限表

CREATE TABLE `sys_roles_permissions` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '角色权限关系表id',
  `roleid` bigint(11) DEFAULT NULL COMMENT '角色id',
  `permissionid` bigint(11) DEFAULT NULL COMMENT '权限id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

注意:以上表结构仅作示例,具体内容应根据实际的业务逻辑进行修改调整。

数据库到数据封装

数据库连接查询

SELECT  mt.*
        FROM sys_user u, sys_role r,user_role ur, menu_item mt, role_menu rp
        WHERE u.loginName = 'admin'
          AND u.userId = ur.userId
          AND r.roleId = ur.roleId
          AND r.roleId = rp.roleId
          AND mt.itemId = rp.itemId;

封装菜单树型结构

菜单实体类MenItem:

public class MenuItem {
    Integer itemId;
    String itemName;
    String itemIcon;
    String itemUrl;
    Integer parentId;
//get set方法
}

树形结构VO类:

public class MenuTreeVO {
    Integer itemId;
    String itemName;
    String itemIcon;
    String itemUrl;
    Integer parentId;
    List<MenuTreeVO> children;
       //get和set方法
}

将数据处理为树形的工具类TreeUtil:

 
public class TreeUtil {
 
    /**
     * 所有待用"菜单"
     */
    private static List<MenuTreeVO> all = null;
 
    /**
     * 转换为树形
     * @param list 所有节点
     * @return 转换后的树结构菜单
     */
    public static List<MenuTreeVO> toTree(List<MenuTreeVO> list) {
        // 最初, 所有的 "菜单" 都是待用的
        all = new ArrayList<>(list);
 
        // 拿到所有的顶级 "菜单"
        List<MenuTreeVO> roots = new ArrayList<>();
 
        for (MenuTreeVO menuTreeVO : list) {
            if (menuTreeVO.getParentId() == 0) {
                roots.add(menuTreeVO);
            }
        }
 
        // 将所有顶级菜单从 "待用菜单列表" 中删除
        all.removeAll(roots);
 
        for (MenuTreeVO menuTreeVO : roots) {
            menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));;
        }
        return roots;
    }
 
    /**
     * 递归函数
     *      递归目的: 拿到子节点
     *      递归终止条件: 没有子节点
     * @param parent 父节点
     * @return  子节点
     */
    private static List<MenuTreeVO> getCurrentNodeChildren(MenuTreeVO parent) {
        // 判断当前节点有没有子节点, 没有则创建一个空长度的 List, 有就使用之前已有的所有子节点.
        List<MenuTreeVO> childList = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren();
 
        // 从 "待用菜单列表" 中找到当前节点的所有子节点
        for (MenuTreeVO child : all) {
            if (parent.getItemId().equals(child.getParentId())) {
                childList.add(child);
            }
        }
 
        // 将当前节点的所有子节点从 "待用菜单列表" 中删除
        all.removeAll(childList);
 
        // 所有的子节点再寻找它们自己的子节点
        for (MenuTreeVO menuTreeVO : childList) {
            menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));
        }
        return childList;
    }
}

使用方法:根据登录名loginName,查找到用户的权限,将刚才SQL执行的结构封装到LIST中:

 public  List<MenuTreeVO> getMenu(String loginName){
       List<MenuItem>menus  = userMapper.getMenu(loginName);
        List<MenuTreeVO> menuTreeVOS = new ArrayList<>();
        TreeUtil treeUtil= new TreeUtil();
        for (MenuItem menu : menus) {
            MenuTreeVO menuTreeVO = new MenuTreeVO();
            BeanUtils.copyProperties(menu, menuTreeVO);
            menuTreeVOS.add(menuTreeVO);
          }
         return  TreeUtil.toTree(menuTreeVOS);
}

Controller调用这个方法:

@GetMapping("/getmenu")
@ApiOperation(value="菜单查询")
public ApiResult getuser(@RequestParam String loginName) {
    List<MenuTreeVO>menu = userService.getMenu(loginName);
    return ApiResultHandler.buildApiResult(200, "查找成功", menu);
}

返回JSON数据:

 

微信关注

编程那点事儿

阅读剩余
THE END