用户角色权限设计

在多角色的系统中(比如管理端)一般会给不同角色的用户分配不同的权限。权限一般有以下分类:

1
2
3
4
* 页面权限
* 操作权限
* 数据权限
* API 权限

页面一般通过路由分发,所以页面权限也叫做路由权限。在这些权限中,API 权限是后端权限,其他都是前端权限,下面从前端角度介绍用户角色权限的设计。

权限配置

页面权限包含了所有权限类型,操作和数据都在页面内,操作权限和数据权限可作为页面权限的子权限。

权限既可以在服务端配置,也可以在客户端配置,在服务端配置更容易维护。

  • 服务端配置
1
2
3
4
5
6
7
8
9
10
{
id: '', // 页面 ID
parentId: '', // 父级页面 ID
title: '', // 页面名称(用于页面管理列表的显示名称或菜单名称)
path: '', // 路由(支持动态路由 /agent/report/:userId?)或 URL
sort: 0, // 排序
icon: '', // 图标
hidden: '', // 是否隐藏,不生成菜单。默认 false
functions: [] // 操作权限(增、删、改、查...)
}

页面权限列表是一个树形结构,描述了页面的从属关系,决定了菜单的生成层级。对于那些不需要生成菜单的页面(由按钮触发或地址直接打开的页面),可通过 hidden 字段描述菜单项是否显示。

实际操作中,一般由页面管理生成总的页面权限列表,角色管理时为角色勾选权限,然后在用户管理中为用户赋予角色。

  • 客户端配置

以在前端路由表中配置为例(当然不一定写在路由表中,独立一个菜单权限文件也可以):

1
2
3
4
5
6
7
8
9
{
meta : {
hidden: false // 是否生成菜单
roles: ['admin', 'teacher'], // 空表示不需要权限,任何角色都可访问
functions: {
admin: ['add', 'delete', 'edit', 'export']
}
}
}

鉴权

  • 路由鉴权

页面权限采用白名单制,不在此名单内的访问和操作都是非法的,实际需求中并不是所有的页面都需要权限,比如 404,登录页,活动页等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hasPagePermission(path, router) {
// 是否需要鉴权(不是所有的页面都需要鉴权)
// 支持服务端配置的动态路由
const needPermission = this.allPagePermitList.some(
(item) => isPathMatched(router, path, item.data.pagePath)
);
if (!needPermission) {
return true;
}
// 是否具有权限
return Array.from(this.myPagePermitMaps.keys()).some((item) =>
isPathMatched(router, path, item)
);
}

或者将这些不需鉴权页面加入到我的页面权限列表进行鉴权,但这需要维护白名单。

1
2
3
4
5
6
7
// 将白名单列表合并到我的权限列表
myPagePermitList = [...pagePermitWhiteList, ...myPagePermitList]
if (myPagePermitList.includes(curRouteName)) {
// 有权限
} else {
// 无权限
}
  • 操作鉴权

不是所有的操作都需要参与鉴权,根据需求选择性鉴权即可。

1
2
3
if (!myFunPermitList.includes(curFunName)) {
// 无权限
}

在 Vue 中通常配合 v-if 或自定义指令实现操作按钮的隐藏显示。