RBAC简单的前后端分离权限管理思路一、数据库设计二、后

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

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

用户拥有当前角色下的对应权限,一个用户可以有多个角色,每个角色都可以有多个菜单。

image-20210727164225662

一、数据库设计

  • 用户表

image-20210727164852348

CREATE TABLE `admin_user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
  `mobile` char(11) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
  `avatar` varchar(512) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '头像',
  `email` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '邮箱',
  `password` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码',
  `status` int unsigned NOT NULL DEFAULT '1' COMMENT '状态 1正常 2黑名单',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `delete_time` datetime DEFAULT NULL COMMENT '是否删除',
  `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_mobile` (`username`,`mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
复制代码
  • 角色表

image-20210727165141521

CREATE TABLE `admin_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名称',
  `remarks` varchar(512) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `delete_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
复制代码
  • 菜单表

    每条记录保存 parent_id ,当parent_id = 0 就是根节点(一级菜单)。

    type 标记菜单的类型,区分是菜单还是权限控制。渲染菜单的时候只需要 type = 1 的数据。

    route_name 前后端分离菜单是由前端控制,前端通过 route_name 匹配菜单是否动态添加到路由表中。

    api_route_name 后端接口标识,当请求来时后端根据 api_route_name 判断是否有接口权限。

    icon 前端渲染菜单展示的图标。

    cache 前端页面是否使用 keep-alive 缓存。

    hidden 是否在菜单栏显示,有的路由不需要在菜单栏显示,但是需要权限控制的时候使用。

image-20210727165404877

CREATE TABLE `admin_menu` (
  `id` int NOT NULL AUTO_INCREMENT,
  `parent_id` int unsigned NOT NULL DEFAULT '0' COMMENT '父级菜单id 0是一级菜单',
  `type` int unsigned NOT NULL DEFAULT '1' COMMENT '1菜单 2按钮或操作权限',
  `route_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '前端路由名称',
  `api_route_name` varchar(128) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '接口路由名称',
  `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单名称',
  `icon` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '菜单图标',
  `cache` int NOT NULL DEFAULT '0' COMMENT '0不缓存 1缓存',
  `affix` int NOT NULL DEFAULT '0' COMMENT '0不固定到标签栏 1固定',
  `breadcrumb` int NOT NULL DEFAULT '1' COMMENT '0不显示在面包屑 1显示',
  `hidden` int NOT NULL DEFAULT '1' COMMENT '0菜单显示 1菜单隐藏',
  `remarks` varchar(512) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注信息',
  `sort` int NOT NULL DEFAULT '0' COMMENT '排序',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `delete_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
复制代码
  • 角色与用户绑定关系表
CREATE TABLE `admin_role_user` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int unsigned NOT NULL COMMENT '用户id',
  `role_id` int unsigned NOT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
复制代码
  • 角色与菜单绑定关系表
CREATE TABLE `admin_role_menu` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `menu_id` int unsigned NOT NULL DEFAULT '0' COMMENT '菜单id',
  `role_id` int unsigned NOT NULL DEFAULT '0' COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=234 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
复制代码

二、后端实现权限管理思路

基本上大多数框架都有请求拦截功能,例如 nodejs(Eggjs/Koa)PHP(ThinkPHP/Laravel) 的中间件、 SpringBoot 的请求拦截或者AOP

  • 每个请求都一个唯一名称。中间件传参,可以在路由定义时候传递到中间件;springboot可以用注解参数。对应菜单中字段 api_route_name

  • 通过登录凭证获取用户ID,通过用户ID在 admin_role_user 表中查看用户所有的角色

  • 通过 api_route_name 获取请求是否在 admin_menu 中,不存在则放行。

  • 如果菜单存在就在 admin_role_menu 表中查看用户所拥有的的角色下是否有菜单权限,有权限则放行。

image-20210727172910872

Egg.js版

ThinkPHP6版

三、前端实现思路

Vue 等框架前后端分离前端独立控制路由,菜单可以使用路由动态生成,通过获取当前用户的权限去比对路由表,生成当前用户具有的权限可访问的路由表,菜单表 admin_menu 中其他字段可以用在前端路由生成的参数。菜单中区分类型菜单/权限;菜单是用作前端路由表生成,权限用作页面按钮或者操作的控制。

  • 用户登录成功获取用户信息,后端返回用户角色下所有的菜单。
  • 通过返回的列表中的菜单类型动态生成前端路由,并将菜单下对应权限 type=2 放入对应路由 meta 中;编写自定义指令通过获取当前路由中 meta 权限可以实现按钮级别的权限控制。