权限控制不管前后端都可以简单分为:
身份认证权限控制
RBAC 权限控制
...
而前端我和团队, 检索了很多地方都没有很成熟或者说可行的关于 RBAC 基于角色的访问控制相关的前端权限控制方案, 可能是我们检索的方法不对, 亦或是大家都忙于其他, 没有时间把自己的方法整理公布出来, 故我们在原定计划中, 自己实践了一把, 下面和大家分享一下, 有不对或者错误的地方, 望指正.
如果大家点进来, 应该都知道何为 RBAC, 为什么需要使用了, 故这里对此不做过多解释, RBAC 基于角色的访问控制, 一般只会在管理端应用使用, 故这个模块不作为默认模块.
下面介绍一下 vue-viewplus 一个简化 Vue 应用开发的工具库 https://github.com/Jiiiiiin/vue-viewplus 中的 rabc.JS 自定义 RBAC 权限控制模块. http://jiiiiiin.cn/vue-viewplus/#/rabc
该模块, 意在为前端应用提供 rbac 权限控制帮助.
其和 login-state-check.JS 身份认证权限控制模块不同之处在于, 该模块提供了一下两种权限控制手段:
实现前端页面可访问性控制, 即通过路由拦截, 判断用户待访问页面是否已经授权
实现可见页面的局部 UI 组件的可使用性或可见性控制, 即基于自定义 v-access 指令, 对比声明的接口或资源别是否已经授权
而 login-state-check.JS 身份认证权限控制模块, 则提供的是对非公共页面的身份认证校验检查, 其中维护了用户的身份认证即登录状态, 这种权限控制, 更适合大多数应用, 即给用户使用的客户端应用.
而当前模块也依赖了登录状态, 故可以一起复用;
实际案例:
名称 | 渠道 | 简介 |
---|---|---|
jiiiiiin 权限系统 https://github.com/Jiiiiiin/jiiiiiin-security | PC 端 | 一个前后端分离的内管基础项目,并基于当前插件完成了 < a target="_blank" href="https://link.juejin.im?target=https://github.com/Jiiiiiin/jiiiiiin-security/blob/master/jiiiiiin-client-manager/src/plugin/vue-viewplus/rbac.js#L124" rel="nofollow noopener noreferrer" ztid="135" ow="356" oh="42">RBAC 前端权限控制 |
效果如下:
使用方法:
其基于 vue-viewplus, 实现了一个自定义模块 , 非标准模块, 需要手动配置:
main.JS 入口文件:
- import router from './router'
- import ViewPlus from 'vue-viewplus'
- import rbacModule from '@/plugin/vue-viewplus/rbac.js'
- import viewPlusOptions from '@/plugin/vue-viewplus'
- Vue.use(ViewPlus, viewPlusOptions)
- ViewPlus.mixin(Vue, rbacModule, {
- debug: true,
- errorHandler(err) {
- console.error(err)
- },
- moduleName: '自定义 RBAC',
- router,
- publicPaths: ['/login'],
- onLoginStateCheckFail(to, from, next) {
- this.dialog(` 您无权访问 [${to.path}] 页面 `)
- .then(() => {
- // 防止用户被踢出之后, 被权限拦截导致访问不了任何页面, 故这里进行登录状态监测
- if (this.isLogin()) {
- next(false);
- } else {
- next('/login');
- }
- })
- }
- })
在登录成功之后, 需要设置插件的登录状态, 和 rabc 模块相应权限集合, 即后端返回的当前登录用户拥有的:
[*] 登录用户拥有访问权限的路由 path 路径集合
完成该配置, 则页面可访问性控制就可以正常工作
[*] 登录用户拥有访问权限的后台接口集合
[可选] 登录用户拥有访问权限的资源别名集合
完成以上配置, 则自定义 v-access 指令就可以支持对应模式的配置
[可选] 是否是超级用户
有些系统存在一个超级用户角色, 其可以访问任何资源, 页面, 故如果设置, 针对这个登录用户将不会做任何权限校验, 以便节省前端资源
- // 开始请求登录接口
- AccountLogin(vm.$vp, {
- username,
- password,
- imageCode
- })
- .then(async res => {
- // 修改用户登录状态
- vm.$vp.modifyLoginState(true)
- const menus = _delEmptyChildren(res.principal.admin.menus);
- const authorizeResources = _parseAuthorizePaths(res.principal.admin.authorizeResources);
- vm.$vp.rabcUpdateAuthorizedPaths(authorizeResources)
- const authorizeInterfaces = _parseAuthorizeInterfaces(res.principal.admin.authorizeInterfaces);
- vm.$vp.rabcUpdateAuthorizeInterfaces(authorizeInterfaces)
- const isSuperAdminStatus = _parseUserRoleIsSuperAdminStatus(res.principal.admin.roles);
- vm.$vp.rabcUpdateSuperAdminStatus(isSuperAdminStatus)
针对需要设置的权限集合, 其都是扁平化的一维数组, 格式类似:
authorizedPaths 和 publicPaths:
["/mngauth/admin", "/index", "/mngauth"]
- authorizeInterfaces:
- ["admin/dels/*", "admin/search/*/*/*", "admin/*/*/*", "role/list/*", "admin/*"]
- authorizeResourceAlias:
- ["MNG_USERMNG", "MNG_ROLEMNG"]
注意以上数组的值除了可以配置为字符串还可以配置为正则表达式:
[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]
实现可见页面的局部 UI 组件的可使用性或可见性配置示例:
- <el-form v-access="['admin/search/*/*/*']" slot="search-inner-box" :inline="true" :model="searchForm" :rules="searchRules" ref="ruleSearchForm" class="demo-form-inline">
- ...
- <el-form-item class="search-inner-btn-box">
- <el-button size="small" type="primary" icon="el-icon-search" @click="onSearch"> 查询 </el-button>
- <el-button size="small" icon="el-icon-refresh" @click="onCancelSubmit"> 重置 </el-button>
- </el-form-item>
- </el-form>
完成以上配置即可让正常使用当前模块提供的权限控制服务, 当然如 $vp.modifyLoginState|$vp#isLogin 涉及到 login-state-check.JS 身份认证权限控制模块
计划
针对 authorizeInterfaces, 后期将会用于在发送 Ajax 请求之前, 对待请求的接口和当前集合进行匹配, 如果匹配失败说明用户就没有请求权限, 则直接不发送后台请求, 减少后端不必要的资源浪费, 在完成这个权限匹配, 前端基础的权限规则就完整了.
其实实现下来没有想象的那么复杂, 可以点击查看源码
相较于理解这一块, 我觉得理解 RBAC 原则和表结构, 才能更好的理解为什么要这么控制, 更多的关于后端关于这一块的实践, 可以参考 jiiiiiin 权限系统 https://github.com/Jiiiiiin/jiiiiiin-security 这个内管项目针对表结构的设计, 其后台使用的是 spring security 来完成后端的 RBAC 权限控制, 并针对当前前端权限需要和 vue router path 进行了细微变化, 相较于传统的 RBAC 金典 5 张表的设计.
也请大家多多支持 :)
下面是改模块的 API 描述:
配置
debug|errorHandler|router|installed 配置, 可以查看全局通用配置
- publicPaths
- /**
- * [*] 系统公共路由 path 路径集合, 即可以让任何人访问的页面路径
- * {Array<Object>}
- * <p>
- * 比如登录页面的 path, 因为登录之前我们是无法判断用户是否可以访问某个页面的, 故需要这个配置, 当然如果需要这个配置也可以在初始化插件之前从服务器端获取, 这样前后端动态性就更高, 但是一般没有这种需求:)
- * <p>
- * 数组中的 item, 可以是一个 ** 正则表达式字面量 **, 如 `[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`, 也可以是一个字符串
- * <p>
- * 匹配规则: 如果在 `LoginStateCheck#publicPaths`** 系统公共路由 path 路径集合 ** 中, 那么就直接跳过权限校验
- */
- publicPaths = []
- authorizedPaths
- /**
- * [*] 登录用户拥有访问权限的路由 path 路径集合
- * {Array<Object>}
- * <p>
- * 数组中的 item, 可以是一个 ** 正则表达式字面量 **, 如 `[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`, 也可以是一个字符串
- * <p>
- * 匹配规则: 如果在 `LoginStateCheck#authorizedPaths`** 需要身份认证规则集 ** 中, 那么就需要查看用户是否登录, 如果没有登录就拒绝访问
- */
- authorizedPaths = []
- authorizeInterfaces
- /**
- * [*] 登录用户拥有访问权限的后台接口集合
- * {Array<Object>}
- * <p>
- * 1. 在 `v-access` 指令配置为 url(默认) 校验格式时, 将会使用该集合和指令声明的待审查授权接口列表进行匹配, 如果匹配成功, 则指令校验通过, 否则校验不通过, 会将对应 dom 元素进行处理
- * 2.TODO 将会用于在发送 Ajax 请求之前, 对待请求的接口和当前集合进行匹配, 如果匹配失败说明用户就没有请求权限, 则直接不发送后台请求, 减少后端不必要的资源浪费
- * <p>
- * 数组中的 item, 可以是一个 ** 正则表达式字面量 **, 如 `[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`, 也可以是一个字符串
- * <p>
- * 匹配规则: 将会用于在发送 Ajax 请求之前, 对待请求的接口和当前集合进行匹配, 如果匹配失败说明用户就没有请求权限, 则直接不发送后台请求, 减少后端不必要的资源浪费
- */
- authorizeInterfaces = []
- authorizeResourceAlias
- /**
- * [可选] 登录用户拥有访问权限的资源别名集合
- * {Array<Object>}
- * <p>
- * 数组中的 item, 可以是一个 ** 正则表达式字面量 **, 如 `[/^((\/Interbus)(?!\/SubMenu)\/.+)$/]`, 也可以是一个字符串
- * <p>
- * 匹配规则: 因为如果都用 `LoginStateCheck#authorizeInterfaces` 接口进行匹配, 可能有一种情况, 访问一个资源, 其需要 n 个接口, 那么我们在配置配置权限指令: v-access="[n, n....]" 的时候就需要声明所有需要的接口, 就会需要对比多次,
- * 当我们系统的接口集合很大的时候, 势必会成为一个瓶颈, 故我们可以为资源声明一个别名, 这个别名则可以代表这 n 个接口, 这样的话就从 n + 减少到 n 次匹配;
- */
- authorizeResourceAlias = []
- onLoginStateCheckFail
- /**
- * [*] `$vp::onLoginStateCheckFail(to, from, next)`
- * <p>
- * 权限检查失败时被回调
- */
- onLoginStateCheckFail = null
API 接口
- modifyLoginState
- /**
- * 代理 `$vp#login-state-check` 模块的同名方法, 以实现在登出, 会话超时踢出的时候清理本模块维护的登录之后设置的状态
- * @param status
- */
- modifyLoginState(status = false)
- rabcUpdateSuperAdminStatus
- /**
- * [可选] 有些系统存在一个超级用户角色, 其可以访问任何资源, 页面, 故如果设置, 针对这个登录用户将不会做任何权限校验, 以便节省前端资源
- * @param status
- */
- rabcUpdateSuperAdminStatus(status)
- rabcAddAuthorizedPaths
- /**
- * 添加授权路径集合
- * 如: 登录完成之后, 将用户被授权可以访问的页面 `paths` 添加到 `LoginStateCheck#authorizedPaths` 中
- * @param paths
- */
- rabcAddAuthorizedPaths(paths)
- rabcUpdateAuthorizedPaths
- /**
- * 更新授权路径集合
- * @param paths
- */
- rabcUpdateAuthorizedPaths(paths)
- rabcUpdateAuthorizeResourceAlias
- /**
- * 更新资源别名集合
- * @param alias
- */
- rabcUpdateAuthorizeResourceAlias(alias)
rabcAddAuthorizeResourceAlias /** * 添加资源别名集合 * @param alias */ rabcAddAuthorizeResourceAlias(alias)
rabcUpdatePublicPaths /** * 更新公共路径集合 * @param paths */ rabcUpdatePublicPaths(paths)
rabcAddPublicPaths /** * 添加公共路径集合 * @param paths */ rabcAddPublicPaths(paths)
如果大家觉得有用, 请大家多多支持, 更多的模块请点击查看 https://github.com/Jiiiiiin/vue-viewplus
来源: https://juejin.im/post/5c19a282f265da61137f372c