RBAC权限控制具体实现
今天记录一个RBAC权限控制的具体实现方法
- 每个用户 可分配 若干角色
- 每个角色 分配 若干功能
超级管理员:
- 修改每个用户的角色
- 修改每个角色的功能
页面权限
所谓页面权限包含两部分内容:
- 用户可看到的:左侧
menu
菜单的item
展示 - 用户看不到的:路由表配置
我们知道 左侧 menu
菜单是根据路由表自动生成的。 所以以上第一部分的内容其实就是由第二部分引起的。
路由表分成了两部分:
-
私有路由表
privateRoutes
:依据权限进行动态配置的 -
公开路由表
publicRoutes
:无权限要求的创建路由表时,只导入公开路由表,私有路由表后续通过 addRoute动态导入
TypeScript
export const privateRoutes = [...]
export const publicRoutes = [...]
const router = createRouter({
history: createWebHashHistory(),
routes: publicRoutes
})
那么想要实现 页面权限 核心的点就是在 **私有路由表 **privateRoutes
我们期望的是:不同的权限进入系统可以看到不同的路由 。那么换句话而言是不是就是:根据不同的权限数据,生成不同的私有路由表?
对于 vue-router 4
而言,提供了 addRoute API ,可以 动态添加路由到路由表中 ,那么我们就可以利用这个 API
生成不同的路由表数据。
TypeScript
/**
* 根据权限筛选路由
*/
filterRoutes(context, menus) {
const routes = []
// 路由权限匹配
menus.forEach(key => {
// 权限名 与 路由的 name 匹配
routes.push(...privateRoutes.filter(item => item.name === key))
})
// 最后添加 不匹配路由进入 404
routes.push({
path: '/:catchAll(.*)',
redirect: '/404'
})
context.commit('setRoutes', routes)
return routes
}
TypeScript
// 判断用户资料是否获取
// 若不存在用户信息,则需要获取用户信息
if (!store.getters.hasUserInfo) {
// 触发获取用户信息的 action,并获取用户当前权限
const { permission } = await store.dispatch('user/getUserInfo')
// 处理用户权限,筛选出需要添加的权限
const filterRoutes = await store.dispatch(
'permission/filterRoutes',
permission.menus
)
// 利用 addRoute 循环添加
filterRoutes.forEach(item => {
router.addRoute(item)
})
// 添加完动态路由之后,需要在进行一次主动跳转
return next(to.path)
}
next()
总结一下以上所说的内容:
- 页面权限实现的核心在于 路由表配置
- 路由表配置的核心在于 **私有路由表 **
privateRoutes
- 私有路由表
privateRoutes
的核心在于 addRoute API
那么简单一句话总结,我们只需要:**根据不同的权限数据,利用 addRoute API 生成不同的私有路由表 ** 即可实现 页面权限 功能
页面权限实现方法:
整个 页面权限 实现分为以下几步:
- 从后端获取 权限数据
- 私有路由表 不再被直接加入到
routes
中 - 利用 addRoute API 动态添加 与后端权限数据匹配的路由到 路由表 中
功能权限
功能权限 的难度低于页面权限,所谓功能权限指的只有一点:
- 根据不同的 权限数据 ,展示不同的 功能按钮
对于 功能权限 而言,只需要:根据权限数据,隐藏功能按钮 即可
功能权限实现方法:
整个 功能权限 实现分为以下几步:
- 获取 权限数据
- 定义 隐藏按钮方式(通过vue 的自定义指令)
- 依据数据隐藏按钮
所以首先我们先去创建这样一个指令(vue3 自定义指令)
- 我们期望最终可以通过这样格式的指令进行功能受控
v-permission="['importUser']"
- 以此创建对应的自定义指令
directives/permission
TypeScript
import store from '@/store'
function checkPermission(el, binding) {
// 获取绑定的值,此处为权限
const { value } = binding
// 获取当前用户的所有的功能权限
const points = store.getters.userInfo.permission.points
// 当传入的指令集为数组时
if (value && value instanceof Array) {
// 匹配对应的指令
const hasPermission = points.some(point => {
return value.includes(point)
})
// 如果v-permission的数组中没有当前用户的功能权限
// 则表示当前用户无该指令,那么删除对应的功能按钮
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
// eslint-disabled-next-line
throw new Error('v-permission value is ["admin","editor"]')
}
}
export default {
// 在绑定元素的父组件被挂载后调用
mounted(el, binding) {
checkPermission(el, binding)
},
// 在包含组件的 VNode 及其子组件的 VNode 更新后调用
update(el, binding) {
checkPermission(el, binding)
}
}
- 在
directives/index
中绑定该指令
JavaScript
...
import permission from './permission'
export default (app) => {
...
app.directive('permission', permission)
}
- 在所有功能中,添加该指令
views/role-list/index
html
<el-button
...
v-permission="['distributePermission']"
>
{{ $t('msg.role.assignPermissions') }}
</el-button>
后端是如何存储权限数据的
userinfo表:
-
这里的roldId存储的是改用户的角色(可以有多个)
role表:
-
这里的id:1,2,3分别代码不同的角色
user permission表:
-
这里的id对应role表的角色id,这里存储的是每个角色对应的权限有哪些
-
纯数字的是页面权限,
X-X
格式的是功能权限