RBAC的权限设计思想

权限设计

首先,我们先了解下什么是传统的权限设计

image-20200901150655343.png

从上面的图中,我们发现,传统的权限设计是对每个人进行单独的权限设置,但这种方式已经不适合目前企业的高效管控权限的发展需求,因为每个人都要单独去设置权限

基于此,RBAC的权限模型就应运而生了,RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案,相对于传统方案,RBAC提供了中间层Role(角色),其权限模式如下

image-20200901151025021.png

RBAC实现了用户和权限点的分离,想对某个用户设置权限,只需要对该用户设置相应的角色即可,而该角色就拥有了对应的权限,这样一来,权限的分配和设计就做到了极简,高效,当想对用户收回权限时,只需要收回角色即可,接下来,我们就在项目中实施这一设想

image-20200901151151286.png

从上图中,可以看出,用户和角色是 1对多 的关系,即一个用户可以拥有多个角色,比如公司的董事长可以拥有总经理和系统管理员一样的角色

权限点管理

人已经有了角色, 那么权限是什么

在企业服务中,权限一般分割为 页面访问权限按钮操作权限API访问权限

API权限多见于在后端进行拦截,前端只做 页面访问按钮操作授权

由此,我们可以根据业务需求设计权限管理页面

image-20200901160508382.png

前端权限应用-页面访问和菜单

权限受控的主体思路

到了最关键的环节,我们设置的权限如何应用?

给用户分配了角色, 给角色分配了权限,那么在用户登录获取资料的时候,会自动查出该用户拥有哪些权限,这个权限需要和我们的菜单还有路由有效结合起来

动态权限其实就是根据用户的实际权限来访问的,接下来我们操作一下

image-20200730002842243.png

在权限管理页面中,设置一个标识, 这个标识可以和我们的路由模块进行关联,也就是说,如果用户拥有这个标识,那么用户就可以拥有这个路由模块,如果没有这个标识,就不能访问路由模块

用什么来实现?

vue-router提供了一个叫做addRoutes的API方法,这个方法的含义是动态添加路由规则

思路如下

image-20200901164312005.png

新建Vuex中管理权限的模块

主页模块章节中,我们将用户的资料设置到vuex中,其中便有权限数据,我们可以就此进行操作

我们可以在vuex中新增一个permission模块

src/store/modules/permission.js

// vuex的权限模块
import { constantRoutes } from '@/router'
// vuex中的permission模块用来存放当前的 静态路由 + 当前用户的 权限路由
const state = {
  routes: constantRoutes // 所有人默认拥有静态路由
}
const mutations = {
  // newRoutes可以认为是 用户登录 通过权限所得到的动态路由的部分
  setRoutes(state, newRoutes) {
    // 每次更新 都应该在静态路由的基础上进行追加动态路由
    state.routes = [...constantRoutes, ...newRoutes]
  }
}
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

复制代码

Vuex筛选权限路由

OK, 那么我们在哪将用户的标识和权限进行关联呢 ?

image-20200815184203715.png

我们可以在这张图中,进一步的进行操作

image-20200815184407204.png

访问权限的数据在用户属性 menus 中,menus 中的标识该怎么和路由对应呢?

image.png

可以将路由模块的根节点 name 属性命名和权限标识一致,这样只要标识能对上,就说明用户拥有了该权限

这一步,在我们命名路由的时候已经操作过了

image.png

接下来, vuex的permission中提供一个action,进行关联

import { asyncRoutes, constantRoutes } from '@/router'

const actions = {
  // menus是当前用户的所拥有的菜单权限
  // asyncRoutes是所有的动态路由
  filterRoutes(context, menus) {
    const routes = []
    //   筛选出 动态路由中和menus中能够对上的路由
    menus.forEach(key => {
      // key就是标识
      // asyncRoutes 找 有没有对象中的name属性等于 key的 如果找不到就没权限 如果找到了 要筛选出来
      routes.push(...asyncRoutes.filter(item => item.name === key)) // 得到一个数组 有可能 有元素 也有可能是空数组
    })
    // 得到的routes是所有模块中满足权限要求的路由数组
    // routes就是当前用户所拥有的 动态路由的权限
    context.commit('setRoutes', routes) // 将动态路由提交给mutations
    return routes // 这里为什么还要return  state数据 是用来 显示左侧菜单用的  return  是给路由addRoutes用的
  }
复制代码

权限拦截出调用筛选权限Action

在拦截的位置,调用关联action, 获取新增routes,并且使用 addRoutes 合并

// 权限拦截在路由跳转  导航守卫

import router from '@/router'
import store from '@/store' // 引入store实例 和组件中的this.$store是一回事
// 前置守卫
const whiteList = ['/login', '/404'] // 定义白名单
router.beforeEach(async(to, from, next) => {
  // 判断是否有token
  if (store.getters.token) {
  // 判断访问的是否登录页
    if (to.path === '/login') {
      next('/') // 跳到主页  
    } else {
      // 如果当前vuex中有用户的资料的id 表示 已经有资料了 不需要获取了 如果没有id才需要获取
      if (!store.getters.userId) {
        // 如果没有id才表示当前用户资料没有获取过
        // async 函数所return的内容 用 await就可以接收到
        const { roles } = await store.dispatch('user/getUserInfo')
        // 如果说后续 需要根据用户资料获取数据的话 这里必须改成 同步
        // actions是做异步操作的
        const routes = await store.dispatch('permission/filterRoutes', roles.menus)
        // routes就是筛选得到的动态路由
        // 动态路由 添加到 路由表中 默认的路由表 只有静态路由 没有动态路由
        // addRoutes  必须 用 next(地址) 不能用next()
        router.addRoutes(routes) // 添加动态路由到路由表  铺路
        // 添加完动态路由之后
        next(to.path) // 相当于跳到对应的地址  相当于多做一次跳转 为什么要多做一次跳转
        // 进门了,但是进门之后我要去的地方的路还没有铺好,直接走,掉坑里,多做一次跳转,再从门外往里进一次,跳转之前 把路铺好,再次进来的时候,路就铺好了
      } else {
        next()
      }
    }
  } else {
    //   没有token的情况下
    if (whiteList.indexOf(to.path) > -1) {
      //  表示要去的地址在白名单
      next()
    } else {
      next('/login')
    }
  }
})


复制代码

静态路由动态路由解除合并

注意: 这里有个非常容易出问题的位置,当我们判断用户是否已经添加路由的前后,不能都是用next()

在添加路由之后应该使用 next(to.path), 否则会使刷新页面之后 权限消失,这属于一个vue-router的已知缺陷

同时,不要忘记,我们将原来的静态路由 + 动态路由合体的模式 改成 只有静态路由 src/router/index.js

image-20200730012805239.png

此时,我们已经完成了权限设置的一半, 此时我们发现菜单失去了内容,这是因为菜单读取的是固定的路由,我们要把它换成实时的最新路由,把vuex中获取到的routes直接用来渲染菜单就可以了

登出时,重置路由权限和 404问题

目标: 处理当登出页面时,路由不正确的问题

当我们登出操作之后,虽然看不到菜单,但是用户实际上可以访问页面,直接在地址栏输入地址就能访问

这是怎么回事?

这是因为我们前面在 addRoutes 的时候,一直都是在 ,登出的时候,我们并没有删,也没有重置,也就是说,我们之前加的路由在登出之后一直在,这怎么处理?

router/index.js文件,封装一个重置路由方法

// 重置路由
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}
复制代码

没错,这个方法就是将路由重新实例化,相当于换了一个新的路由,之前 加的路由 自然不存在了,只需要在登出的时候, 处理一下即可

  // 登出的action
  lgout(context) {
    // 删除token
    context.commit('removeToken') // 不仅仅删除了vuex中的 还删除了缓存中的
    // 删除用户资料
    context.commit('removeUserInfo') // 删除用户信息
    // 重置路由
    resetRouter()
  }
复制代码

除此之外,我们发现在页面刷新的时候,本来应该拥有权限的页面出现了404,这是因为404的匹配权限放在了静态路由,而动态路由在没有addRoutes之前,找不到对应的地址,就会显示404,所以我们需要将404放置到动态路由的最后

src/permission.js

- router.addRoutes(routes)
+ router.addRoutes([...routes, { path: '*', redirect: '/404', hidden: true }]) // 添加到路由表
复制代码

功能权限应用

功能权限的受控思路

当我们拥有了一个模块,一个页面的访问权限之后,页面中的某些功能,用户可能有,也可能没有,这就是功能权限

比如,我们想对员工管理的删除功能做个权限怎么做?

在员工管理的权限点下, 新增一个删除权限点,启用

image-20200730020732919.png

我们要做的就是看看用户,是否拥有point-user-delete这个point,有就可以让删除能用,没有就隐藏或者禁用

使用Mixin技术将检查方法注入

所以,我们可以采用一个新的技术 mixin(混入)来让所有的组件可以拥有一个公共的方法

src/mixin/checkPermission.js

import store from '@/store'
export default {
  methods: {
    checkPermission(key) {
    // 拿到保存的用户资料--> 判断传入的值与权限里面有没有,有的话显示,没有就隐藏
      const { userInfo } = store.state.user
      if (userInfo.roles.points && userInfo.roles.points.length) {
        return userInfo.roles.points.some(item => item === key)
      }
      return false
    }
  }
}

复制代码

猜你喜欢

转载自juejin.im/post/7108390722071429128