Detailed explanation of RBAC permissions

1. Traditional permission design

First, let’s understand what traditional permission design is

Insert image description here

From the above figure, we find that the traditional permission design is to set individual permissions for each person, but this method is no longer suitable for the development needs of the current enterprise's efficient management and control of permissions, because everyone has to set permissions individually. 

2. Detailed explanation of RBAC permissions

Based on this, the RBAC permission model came into being, RBAC (Role-Based Access control), which is a role-based permission allocation solution. Compared with the traditional solution, RBAC provides an intermediate layer Role (role), and its permission model as follows

 RBAC realizes the separation of users and permission points. If you want to set permissions for a certain user, you only need to set the corresponding role for the user, and the role will have the corresponding permissions. In this way, the distribution and design of permissions are simple. It is extremely simple and efficient. When you want to revoke permissions from a user, you only need to revoke the role. Next, we will implement this idea in this project.

3. Permission settings

1.Permission point

Permissions: Do you have the right to perform a certain operation in a system?
Permissions are divided into two levels

            1. 菜单权限:是否有权限访问某个菜单
            2. 按钮权限:是否有权限操作 页面上的某个按钮功能

2. Business logic

For permission data, there are two levels of settings

1. Can I access the page?

2. Can you operate a certain button on the page?

3.RBAC permission design ideas

Goal: After logging into the system with different accounts, they will see different pages and can perform different functions.

The pattern is as follows

Three key points:

User : the person who uses the system

Permission points : How many functions are there in this system (example: there are 3 pages, each page has different operations)

Role : a collection of different authority points

 

Actually it is

  1. Assign roles to users
  2. Assign permission points to roles

In actual business:

  1. .Assign employees a specific role first
  2. Then assign specific permission points to the role (the operation button under the salary page on the salary page) and the employee will have the permission points.

4. Authorization specific business

Insert image description here

Insert image description here

4. Specific steps

1. Dynamically generate the left menu-addRoutes method

The dynamic routing table that is statically written directly in the router is transformed into addRoutes a form that is added through method calls.

// 引入所有的动态路由表(未经过筛选)
import router, { asyncRoutes } from '@/router'
const whiteList = ['/login', '/404']
router.beforeEach(async(to, from, next) => {
  NProgress.start() // 启动进度条
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      if (!store.getters.userId) {
        // 判断userInfo有没有id值,如果没有就进user/getUserInfo
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户可以访问的权限是', menus)
        // 根据用户的实际权限menus,可以在asyncRoutes筛选出用户可以访问的权限
        const filterRoute = asyncRoutes.filter(route => {
          return menus.includes(route.children[0].name)
        })
 
        // 因为404页面在路由的中间位置,要进去之前404路由后面的路由时,直接进404页面了
        // 把404路由添加到所有路由的末尾就可以解决这个问题
        filterRoute.push( // 404 page must be placed at the end !!!
          { path: '*', redirect: '/404', hidden: true })
        // 改写成动态添加路由
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充路由配置,才能去访问页面,如果没有设置的话,左边的菜单不显示的
        router.addRoutes(filterRoute)
 
        // 把他们保存到vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoute)
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      } else {
        next()
      }
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
})

We found that the menu on the left only had the static home page. Manually entering a certain dynamic routing address in the browser was still available, which proves that we have actually added dynamic routing to our routing system.

2. Dynamically generate the left menu - rewrite the menu save location

The data used for current menu rendering: this.router. options. routes. This data is fixed. The routing table added by add R outes only exists in memory and will not change this. router.options.routes. This data is fixed. AddRoutes adds The routing table only exists in memory and will not change this.router.options.routes. This data is fixed. The routing table added by addRoutes only exists in memory and will not change this.router.options.routes.

Goal: Call router.addRoutes(), want the data to be reflected on the view, and store the routing information in vuex

Insert image description here

2.1 Define vuex management menu data

Add menu.js under src/store/modules:

 

// 导入静态路由
import { constantRoutes } from '@/router'
export default {
  namespaced: true,
  state: {
    // 先以静态路由作为菜单数据的初始值
    menuList: [...constantRoutes]
  },
  mutations: {
    setMenuList(state, asyncRoutes) {
      // 将动态路由和静态路由组合起来
      state.menuList = [...constantRoutes, ...asyncRoutes]
    }
  }
}

Register this module in src/store/index.js

2.2 Submit setMenuList to generate complete menu data

Modify src/permission.js:

if (!store.getters.userId) {
    await store.dispatch('user/getUserInfo')
    // 把动态路由数据交给菜单
    store.commit('menu/setMenuList', asyncRoutes)
    // 把动态路由添加到应用的路由系统里
    router.addRoutes(asyncRoutes)
}

2.3 The menu generation part is rewritten to use data in vuex

routes() {
  // 拿到的是一个完整的包含了静态路由和动态路由的数据结构
  // return this.$router.options.routes
  return this.$store.state.routeMenu.menuList
}

3. Use permission data for filtering processing

  1. Through the permission data returned by the background, filter out the menu to be displayed, and filter using the name of the route as the identifier.
  2. Get the return value from action action
    is essentially a promise and its return result can be received through const res = await action name

Insert image description here

3.store/modules/user.js

 

// 用来获取用户信息的action
    async getUserInfo(context) {
      // 1. ajax获取基本信息,包含用户id
      const rs = await getUserInfoApi()
      console.log('用来获取用户信息的,', rs)
      // 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)
      const info = await getUserDetailById(rs.data.userId)
      console.log('获取详情', info.data)
      // 把上边获取的两份合并在一起,保存到vuex中
      context.commit('setUserInfo', { ...info.data, ...rs.data })
      return rs.data.roles.menus
    },

4. Filter in permission.js

if (!store.getters.userId) {
        // 有token,要去的不是login,就直接放行
        // 进一步获取用户信息
        // 发ajax---派发action来做
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户能访问的页面', menus)
        console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
        // 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
 
        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0].name
          return menus.includes(routeName)
        })
 
        // 一定要在进入主页之前去获取用户信息
 
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充了路由配置,才可能去访问页面
        // 它们不会出现左侧
        router.addRoutes(filterRoutes)
 
        // 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoutes)
 
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      }

Precautions

  • Solving the 404 problem
    Reason: Now the 404 page in our routing settings is in the middle instead of the end of all routes
    Solution: Just change the 404 page to the end of the routing configuration
    \1. From route/index.js Delete the path:'*' item in the static route
    \2. Add it at the end in permission.js

code example

    if (!store.getters.userId) {
        // 有token,要去的不是login,就直接放行
        // 进一步获取用户信息
        // 发ajax---派发action来做
        const menus = await store.dispatch('user/getUserInfo')
        console.log('当前用户能访问的页面', menus)
        console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
        // 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
 
        const filterRoutes = asyncRoutes.filter(route => {
          const routeName = route.children[0].name
          return menus.includes(routeName)
        })
 
        // 一定要在进入主页之前去获取用户信息
 
        // 把404加到最后一条
        filterRoutes.push( // 404 page must be placed at the end !!!
          { path: '*', redirect: '/404', hidden: true })
 
        // addRoutes用来动态添加路由配置
        // 只有在这里设置了补充了路由配置,才可能去访问页面
        // 它们不会出现左侧
        router.addRoutes(filterRoutes)
 
        // 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
        // 生成左侧菜单时,也应该去vuex中拿
        store.commit('menu/setMenuList', filterRoutes)
 
        // 解决刷新出现的白屏bug
        next({
          ...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
          replace: true // 重进一次, 不保留重复历史
        })
      } 
  • Reset routing on exit

in router/index.js

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

This method is to re-instantiate the route, which is equivalent to changing a new route. It 加的路由does not exist before. You need to call it when logging out.

import { resetRouter } from '@/router'
// 退出的action操作
logout(context) {
  // 1. 移除vuex个人信息
  context.commit('removeUserInfo')
  // 2. 移除token信息
  context.commit('removeToken')
  // 3. 重置路由
  resetRouter()
  // 4. 重置 vuex 中的路由信息 只保留每个用户都一样的静态路由数据
  //    在moudules中的一个module中去调用另一个modules中的mutation要加{root:true}
  context.commit('setMenuList', [], { root: true })
}

4. Control operation buttons

Insert image description here

How to define global detection:

Vue.prototype.$checkPoint = function(pointKey) {
  if (store.state.user.userInfo.roles.points) {
    // 进行权限点判断
    return store.state.user.userInfo.roles.points.includes(pointKey)
  }
  // 没有权限点POINTS信息, 说明用户没有身份, 没有任何权限
  return false

Use if to control button display in the template

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <el-card>
        <el-button v-if="$checkPoint('CKGZ')">查看工资</el-button>
      </el-card>
    </div>
  </div>
</template>

The parameters in $checkPoint are based on the identifier of the authority point in the system.

Or customize the command control button display

Define global directives in main.js

// 注册一个全局自定义指令 `v-allow`
Vue.directive('allow', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function(el, binding) {
    // v-focus="'abc'"  ===> binding.value = 'abc'
    if (store.state.user.userInfo.roles.points.includes(binding.value)) {
      // 元素是可见的
    } else {
      el.style.display = 'none'
    }
  }

use

<el-button
            v-allow="'import_employee'"
            type="warning"
            size="small"
            @click="$router.push('/import')"
          >导入excel</el-button>

 

Guess you like

Origin blog.csdn.net/xm1037782843/article/details/132005960