前端基于'RBAC'鉴权方案


和同事朋友聊天有了解到涉及了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']
        }
    }
]

这份配置一般维护在前端,流程通常是:

  1. 打开站点,登录
  2. 从后台获取到当前用户的角色
  3. 根据该角色,生成有权限的路由和菜单,进行页面初始化

用户的 角色 属性,需要在创建的时候绑定,可以提供一个 用户管理 的模块进行配置。

四、页面权限

虽然流程在第三步生成的菜单后,页面上不存在入口让用户点击进入,但如果用户记住某条路由(比如保存到书签),直接用过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 来维护权限与页面资源的映射,也比较清晰简单。但也有明显的不足:

  • 对于大型项目,权限管理往往是复杂的,比如一个系统有几十个不同类别的应用,用户量都比较大,不能简单的抽象成几个简单的角色来控制。

为了能够更加方便,更灵活的进行管理权限,往往会采用角色动态创建的方案,通过自定义不同类型的角色,以支持更加细粒度的权限管理。这里就不详细说明,可移步下面文章学习更多:

回见!!


文章作者: feico
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 feico !
评论
  目录