vue权限管理之详解(附代码)

879 阅读2分钟

登录页 login.jpg 主页 home.jpg 权限数据 rights_data.jpg

权限相关概念

权限的分类

后端权限

从根本上讲前端仅仅只是视图层的展示, 权限的核心是在于服务器中的数据变,所以后端才是权限的关键,后端权限可以控制某个用户是否能够查询数据, 是否能够修改数据等操作

  1. 后端如何知道该请求是哪个用户发过来的

    cookie session token

  2. 后端的权限设计RBAC

    用户 角色 权限

前端权限的意义

如果仅从能够修改服务器中数据库中的数据层面上讲, 确实只在后端做控制就足够了, 那为什么越来越多的项目也进行了前端权限的控制, 主要有这几方面的好处

  1. 降低非法操作的可能性,不怕赃偷就怕贼惦记,在页面中展示出一个就算点击了也最终会失败的按钮,势必会增加有心者非法操作的可能性 尽可能排除不必要清求,减轻服务器压力

  2. 没必要的请求 ,操作失败的清求, 不具备权限的清求, 应该压根就不需要发送, 请求少了, 自然也会减轻服务器的压力

  3. 提高用户体验,根据用户具备的权限为该用户展现自己权限范围内的内容, 避免在界面上给用户带来困扰, 让用户专注于分内之事

前端权限控制思路

菜单的控制

在登录请求中, 会得到权限数据, 当然, 这个需要后端返回数据的支持. 前端根据权限数据, 展示对应的菜单. 点击菜单, 才能查看相关的界面

界面的控制

如果用户没有登录, 手动在地址栏敲入管理界面的地址, 则需要跳转到登录界面 如果用户已经登录, 如果手动敲入非权限内的地址, 则需要跳转404 界面

按钮的控制

在某个菜单的界面中, 还得根据权限数据, 展示出可进行操作的按钮,比如删除, 修改, 增加

请求和响应的控制

如果用户通过非常规操作, 比如通过浏览器调试工具将某些禁用的按钮变成启用状态, 此时发的请求, 也应该被前端所拦截

实现步骤

权限菜单栏控制

用户登录成功后返回一个数据,这个数据有菜单列表(后端返回的权限列表)和token,我们把这个数据放入到vuex中,然后主页根据vuex中的数据进行菜单列表的渲染

login.vue
         //console.log(res.data.rights);
          this.$store.commit('setRightList',res.data.rights)
          this.$store.commit('setUsername',res.data.username)
          this.$message.success('登录成功!')
           // 缓存token
          window.sessionStorage.setItem('token', res.data.token)
store/index.js

const store = new Vuex.Store({
    state: {
        rightList: JSON.parse(sessionStorage.getItem("rightList") || '[]'),
        username: sessionStorage.getItem("username")
    },
    mutations: {
        setRightList(state, data) {
            state.rightList = data //这里是重点
            sessionStorage.setItem("rightList", JSON.stringify(data))
        },
        setUsername(state, data) {
            state.username = data 
            sessionStorage.setItem("username", data)
        }
    },
    actions: {},
    getters: {}
})

问题: 刷新界面vuex数据消失,菜单栏消失

解决: 将数据存储在sessionStorage中,并让其和vuex中的数据保持同步

界面的控制

登录成功后,将token数据存储在sessionStorage中,判断是否登录

1. 路由导航守卫

router/index.js

    //挂载路由守卫,验证token是否已存在
router.beforeEach((to, from, next) => {
    if (to.path === '/login') return next()
    const tokenStr = window.sessionStorage.getItem('token')
    if (!tokenStr) return next('/login')
    next()
})

问题: 这样用户在登录之后就可以访问其他界面了,但如果用户A登录之后他只有访问a页面的权限,不能访问b页面,但因为路由配置上已有b页面的路由,所以这时候他还是可以通过地址栏输入进入到b页面

解决: 这个时候我们就用到了动态路由了,向路由配置里动态添加用户允许的权限路由

2. 动态路由

router/index.js
//先定义好所有的路由规则
const usersRule = {
    path: '/users',
    component: () =>
        import ('@/components/UserInfo')
}
const rolesRule = {
    path: '/roles',
    component: () =>
        import ('@/components/Roles')
}
const goodsRule = {
    path: '/goods',
    component: () =>
        import ('@/components/Goods')
}
const categoriesRule = {
    path: '/categories',
    component: () =>
        import ('@/components/Categories')
}

const ruleMapping = {
        'users': usersRule,
        'roles': rolesRule,
        'goods': goodsRule,
        'categories': categoriesRule,
    }
//登录成功之后动态添加路由,注意这个initDynamicRoutes的方法需要暴露出去在登录页面调用
export function initDynamicRoutes() {
    //根据二级权限 对路由进行动态条件
    const currentRoutes = router.options.routes
    const rightList = store.state.rightList
    rightList.forEach((item) => {
        item.children.forEach((item_sub) => {
            const temp = ruleMapping[item_sub.path]
            temp.meta = item_sub.rights
            currentRoutes[2].children.push(temp)
        })
    })
    router.addRoutes(currentRoutes)

}

这样当用户A在地址栏输入自己不能访问的路由时,则不会跳转到该页面

问题: 如果我们重新刷新的话,动态路由就会消失,而动态路由是在登录成功之后才会调用的,刷新的时候并没有调用,所以动态路由没有添加上

解决: 在app.vue中的created中调用添加动态路由的方法initDynamicRoutes()

App.vue

import {initDynamicRoutes} from '@/router'
export default {
  name: 'App',
  created(){
    initDynamicRoutes()
  }
}

3. 按钮的控制

虽然用户可以看到某些界面了, 但是这个界面的一些按钮该用户可能是没有权限的(添加、修改、删除)。 因此, 我们需要对组件中的一些按钮进行控制, 用户不具备权限的按钮就隐藏或者禁用, 而在这块的实现中, 可以把该逻辑放到自定义指令

<template slot-scope="scope">
        <el-button v-permission="{action:'edit',effect:'disabled'}"
          size="mini"
          @click="handleEdit(scope.$index, scope.row)">修改</el-button>
        <el-button v-permission="{action:'delete',effect:'disabled'}"
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
permission.js

import Vue from "vue"
import router from "@/router"
Vue.directive('permission', {
    inserted(el, binding) {
        const action = binding.value.action
        const effect = binding.value.effect
        if (router.currentRoute.meta.indexOf(action) == -1) {
            if (effect === 'disabled') {
                el.disabled = true
                el.classList.add('is-disabled')
            } else {
                el.parentNode.removeChild(el)
            }

        }
        //判定 当前路由所对应的组件中,用户是否具备action的权限

    }
})

4. 请求和相应的控制

//main.js
//请求拦截器设置
Vue.prototype.$http = axios
axios.defaults.baseURL = 'http://www.vueadmin.com/admin/'
axios.interceptors.request.use((req) => {
    console.log(req.url);
    console.log(req.method);
    if (req.url !== 'login') {
        // req.headers.Authorization = sessionStorage.getItem('token')
        //逻辑代码 此次省略
    }
    return req
})

此文章参考了 blog.csdn.net/weixin_4415…