和同事朋友聊天有了解到涉及了
RBAC
或是其他鉴权方案,在做中后台管理系统时,权限是一个非常重要的功能,权限的方案非常之多,在众多方案中最常用的辨识RBAC
,那我也是基于网上资料结合我之前做过的一些RBAC
的思想简单聊一下RBAC
的一些简单实现方案,一起来学习一下吧!
一、相关术语
用户:操作的账号,为权限的载体
资源:将页面(路由)、页面内容、数据,接口等可以进行权限控制的都称为资源。
角色:权限的载体,通常是指具有某些共同特征的一组人,比如财务、运维、研发等角色;角色和权限(资源)绑定,通过不同的角色关联不同的权限
二、常见的权限模型
ACL(Access Control List):访问控制列表,直接将权限与用户绑定,当用户数量很多的时候,操作一次权限就需要对每个用户都进行授权,工作量很大。
RBAC(Role-Based Access List): 基于角色的权限控制,通过 用户 -> 角色 -> 权限
的模型,增加了一层角色来间接的赋予用户权限,从而让用户与权限解耦。
ABAC(Attribute-Based Access Control): 基于属性的访问控制,通过定义一系列属性(用户属性、环境属性、资源属性),通过动态计算来确定用户是否有权限访问。对于资源的管控力度更细,比如运营(角色)在杭州(环境)能查询杭州订单(资源)。这种在前端并不常见,有兴趣自行了解。
三、前端的权限控制
前端对权限控制粒度的不同,可以分为:
- 页面权限:包括登录权限、路由、菜单这类,本质上就是对路由进行控制。
- 页面内容权限:比如没有编辑权限,则隐藏掉相关的UI组件,或者不同权限则展示不同数据,通常和后台接口挂钩。
角色固定且较少的方案
这种应该是目前比较常见的一种,角色以及对应的权限由后台维护,前端通常会维护一份菜单(路由)与角色的 map 配置,例如:
const menusConfig = [
{
path: '/path1',
meta: {
title: 'path1',
roles: ['role1', 'role2', 'role3'] // 该条路由那些角色可以访问
}
},
{
path: '/path2',
meta: {
title: 'path2',
roles: ['role1', 'role2']
}
},
{
path: '/path3',
meta: {
title: 'path3',
roles: ['role3']
}
}
]
这份配置一般维护在前端,流程通常是:
- 打开站点,登录
- 从后台获取到当前用户的角色
- 根据该角色,生成有权限的路由和菜单,进行页面初始化
用户的 角色
属性,需要在创建的时候绑定,可以提供一个 用户管理 的模块进行配置。
四、页面权限
虽然流程在第三步生成的菜单后,页面上不存在入口让用户点击进入,但如果用户记住某条路由(比如保存到书签),直接用过url进入,那么还是会打开的。所以还需要在路由层面做权限判断,如果没有权限,则跳转403页面。
vue 和 react 的路由拦截都有各自的实现,这里不列出来,只给出主要逻辑伪代码
const {enableView} = usePermisesion();
if (!enableView) {
// 定向到 403 页面
} else {
// 正常逻辑
}
这里的 usePermisesion
通过 menusConfig
和 用户角色 来判断是当前路由是否权限进入,用户角色 建议通过全局状态、context之类的注入。
当然这里简化了操作,例如应该先进行 404 的判断,否则随便输入的 url 都会跳转到 403
五、内容权限控制
内容权限是比较难处理的,不像页面权限可以通过全局的拦截器进行统一的拦截,需要通过侵入业务代码中。
一些实现方案是将权限需要的角色写到业务中处理:
<PermissionControl permisstion={["role1", "role2"]}>
<Button />
</PermissionControl>
// or
<Button v-permission=["role1", "role2"] />
这种方式适合角色比较少的情况,但维护比较麻烦,一旦要增加一个角色,或者删除一个角色,需要到处找代码。还是可以通过 usePermisesion
来获取当前页面的其他权限。只需扩展下 menusConfig
:
const menusConfig = [
{
path: '/path1',
meta: {
title: 'path1',
permission: { // 对应的操作权限需要的角色
view: ['role1', 'role2', 'role3'],
edit: ['role1', 'role2']
}
}
},
{
path: '/path2',
meta: {
title: 'path2',
permission: {
view: ['role1', 'role2'],
edit: ['role2']
}
}
},
{
path: '/path3',
meta: {
title: 'path3',
permission: {
view: ['role3'],
edit: ['role3']
}
}
}
]
然后封装通用的 权限组件 或者 vue指令,来获取当前页面需要对权限
const PermissionControl = ({permissionkey, children}) => {
// 通过获取当前路由来找到对应的配置
const {enableEdit} = usePermisesion();
if (!enableEdit) {
return null
}
return children
}
// 使用
<PermissionControl><Button /></PermissionControl>
// or
<Button v-permission />
六、内容权限控制
上面的 edit
权限还是针对整个页面级别,如果希望只是页面某个按钮需要更加特殊的权限,比如 【文档发布】 按钮,需要更高的管理权限,依然可以通过上面的 menusConfig
来处理:
const menusConfig = [
{
path: '/path1',
meta: {
title: 'path1',
permission: { // 对应的操作权限需要的角色
view: ['role1', 'role2', 'role3'],
edit: ['role1', 'role2'],
xxxButton: ['administrator'] // 具体页面控件的key
}
}
}
]
const PermissionControl = ({permissionkey, children}) => {
const {enableEdit} = usePermisesion(permissionkey); // 可以传入 key 来获取指定特定对资源权限
if (!enableEdit) {
return null
}
return children
}
// 使用
<PermissionControl permissionkey="administrator"><Button /></PermissionControl>
// or
<Button v-permission="administrator" />
总结:
今天所列的是基本上就能够覆盖权限验证在前端需要的几个场景,这种方式适合于小型系统,角色有限的情况。通过 menusConfig
来维护权限与页面资源的映射,也比较清晰简单。但也有明显的不足:
- 对于大型项目,权限管理往往是复杂的,比如一个系统有几十个不同类别的应用,用户量都比较大,不能简单的抽象成几个简单的角色来控制。
为了能够更加方便,更灵活的进行管理权限,往往会采用角色动态创建的方案,通过自定义不同类型的角色,以支持更加细粒度的权限管理。这里就不详细说明,可移步下面文章学习更多:
回见!!