vue RBAC权限控制管理

本文详细介绍了Vue.js中前端权限控制的概念、意义和实现方式,包括菜单、界面、按钮和请求响应的控制。强调了前端权限控制可以降低非法操作可能性,减轻服务器压力,提升用户体验。并具体阐述了如何利用Vuex进行菜单控制,使用路由导航守卫进行界面控制,通过自定义指令管理按钮,以及在请求和响应层面进行权限拦截。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue权限控制

在Web系统中, 权限很久以来⼀直都只是后端程序所控制的.为什么呢? 因为Web系统的本质围绕的是数
据, ⽽和数据库最紧密接触的是后端程序.所以在很⻓的⼀段时间内, 权限⼀直都只是后端程序需要考虑的
话题.但是随着前后端分离架构的流⾏, 越来越多的项⽬也在前端进⾏权限控制

1.权限相关概念

1.1权限相关概念

后端权限

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

  • 后端如何知道该请求是哪个⽤户发过来的
cookie
session
token
  • 后端的权限设计RBAC
用户
权限
角色

前端权限

前端权限的控制本质上来说, 就是控制前端的 视图层的展示和前端所发送的请求. 但是只有前端权
限控制没有后端权限控制是万万不可的. 前端权限控制只可以说是达到锦上添花的效果.

1.2.前端权限的意义

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

降低⾮法操作的可能性

不怕贼偷就怕贼惦记, 在⻚⾯中展示出⼀个 就算点击了也最终会失败 的按钮, 势必会增加有⼼者
⾮法操作的可能性

尽可能排除不必要请求,减轻服务器压⼒

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

提⾼⽤户体验

根据⽤户具备的权限为该⽤户展现⾃⼰权限范围内的内容,避免在界⾯上给⽤户带来困扰, 让⽤户
专注于分内之事

2.前端权限控制思路

2.1.菜单的控制

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

2.2.界⾯的控制

如果⽤户没有登录,⼿动在地址栏敲⼊管理界⾯的地址, 则需要跳转到登录界⾯
如果⽤户已经登录, 可是⼿动敲⼊⾮权限内的地址, 则需要跳转404界⾯

2.3.按钮的控制

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

2.4.请求和响应的控制

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

3. Vue的权限控制实现

3.1.菜单的控制

  • 查看登录之后获取到的数据
{
    "data": {
        "id": 500,
            "rid": 0,
                "username": "admin",
                    "mobile": "13999999999",
                        "email": "123999@qq.com",
                            "token": "BeareeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1M
        TI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.eGrsrvwHmtPsO9r_pxHIQ5i5L1kX9RX444uwnRGaIM"
    },
    "rights": [{
        "id": 125,
        "authName": "⽤户管理",
        "icon": "icon-user",
        "children": [{
            "id": 110,
            "authName": "⽤户列表",
            "path": "users",
            "rights": ["view", "edit", "add", "delete"]
        }]
    }, {
        "id": 103,
        "authName": "⻆⾊管理",
        "icon": "icon-tijikongjian",
        "children": [{
            "id": 111,
            "authName": "⻆⾊列表",
            "path": "roles",
            "rights": ["view", "edit", "add", "delete"]
        }]
    }, {
        "id": 101,
        "authName": "商品管理",
        "icon": "icon-shangpin",
        "children": [{
            "id": 104,
            "authName": "商品列表",
            "path": "goods",
            "rights": ["view", "edit", "add", "delete"]
        }, {
            "id": 121,
            "authName": "商品分类",
            "path": "categories",
            "rights": ["view", "edit", "add", "delete"]
        }]
    }],
        "meta": {
        "msg": "登录成功",
            "status": 200
    }
}

在这部分数据中, 除了该⽤户的基本信息之外, 还有两个字段很关键

1、token,⽤于前端⽤户的状态保持
2、rights:该⽤户具备的权限数据,⼀级权限就对应⼀级菜单,⼆级权限就对应⼆级菜单

  • 根据rights中的数据, 动态渲染左侧菜单栏, 数据在Login.vue得到, 但是在Home.vue才使⽤, 所以可
    以把数据⽤vuex进⾏维护

vuex中的代码

export default new Vuex.Store({
    state: {
        rightList: []
    },
    mutations: {
        setRightList(state, data) {
            state.rightList = data
        }
    },
    actions: {
    },
    getters: {
    }
})

上下两端代码设计vuex mutation使用 ⭐⭐⭐⭐⭐

Login.vue的代码

login() {
    this.$refs.loginFormRef.validate(async valid => {
        ......
        //数据存将res.rights存vuex中的state  rights中
        this.$store.commit('setRightList', res.rights)
        this.$message.success('登录成功')
        this.$router.push('/home')
    })
 }

将vuex中state值映射计算属性

Home.vue的代码

import { mapState } from 'vuex'
computed: {
 ...mapState(['rightList'])
}
created() {
    this.activePath = window.sessionStorage.getItem('activePath')
    // 初始化menulist菜单栏数据⭐使用
    this.menulist = this.rightList
},
  • 刷新界⾯菜单消失⭐⭐⭐

原因分析

因为菜单数据是登录之后才获取到的, 获取菜单数据之后,就存放在Vuex中⼀旦刷新界⾯, Vuex中的数据会重新初始化,所以会变成空的数组.因此, 需要将权限数据存储在sessionStorage中, 并让其和Vuex中的数据保持同步

代码解决

export default new Vuex.Store({
    state: {
        //改变方式 专为数组对象形式  防止为空报错 空
        rightList: JSON.parse(sessionStorage.getItem('rightList') || '[]')
    },
    mutations: {
        setRightList(state, data) {
            state.rightList = data
            // ⭐⭐⭐数组转字符串存进去
            sessionStorage.setItem('rightList', JSON.stringify(data))
        }
    },
    actions: {
    },
    getters: {
    }
})
  • 标识⽤户名, ⽅便查看当前⽤户

vuex的代码

export default 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: {
    }
})

Login.vue的代码

login() {
    this.$refs.loginFormRef.validate(async valid => {
        .......
        this.$store.commit('setRightList', res.rights)
        //⭐⭐⭐
        this.$store.commit('setUsername', res.data.username)
        this.$message.success('登录成功')
        this.$router.push('/home')
    })
}

Home.vue的代码

computed: {
//⭐⭐⭐
 ...mapState(['rightList','username'])
}
<el-button type="info" @click="logout">{{username}}退出</el-button>
  • 退出按钮的逻辑
logout() {
    // 清除所有sessionstorage
    sessionStorage.clear()
    this.$router.push('/login')
    // 清除vuex数据
    window.location.reload()
},

3.2.界⾯的控制

1、正常的逻辑是通过登录界⾯, 登录成功之后跳转到管理平台界⾯, 但是如果⽤户直接敲⼊管理平台的地
址, 也是可以跳过登录的步骤.所以应该在某个时机判断⽤户是否登录

解释:地址栏输入里面的router地址回校验选择是否强跳login,如果随意输入地址栏 router路由配置跳404页面

  • 如何判断是否登录
// 登录成功将token存进去
sessionStorage.setItem('token', res.data.token)
  • 什么时机

路由导航守卫

router.beforeEach((to, from, next) => {
    if (to.path === '/login') {
        next()
    } else {
        const token = sessionStorage.getItem('token')
        if (!token) {
            next('/login')
        } else {
            next()
        }
    }
})

2、虽然菜单项已经被控制住了, 但是路由信息还是完整的存在于浏览器,正⽐如zhangsan这个⽤户并不具
备⻆⾊这个菜单, 但是他如果⾃⼰在地址栏中敲⼊/roles的地址, 依然也可以访问⻆⾊界⾯

  • 路由导航守卫
    路由导航守卫固然可以在每次路由地址发⽣变化的时候, 从vuex中取出rightList判断⽤户将要访问
    的界⾯, 这个⽤户到底有没有权限.不过从另外⼀个⻆度来说,这个⽤户不具备权限的路由, 是否也应
    该压根就不存在呢?
  • 动态路由⭐⭐⭐

登录成功之后动态添加

App.vue中添加

代码如下:

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login.vue'
import Home from '@/components/Home.vue'
import Welcome from '@/components/Welcome.vue'
import Users from '@/components/user/Users.vue'
import Roles from '@/components/role/Roles.vue'
import GoodsCate from '@/components/goods/GoodsCate.vue'
import GoodsList from '@/components/goods/GoodsList.vue'
import NotFound from '@/components/NotFound.vue'
import store from '@/store'
Vue.use(Router)
// ⭐⭐⭐
const userRule = { path: '/users', component: Users }
const roleRule = { path: '/roles', component: Roles }
const goodsRule = { path: '/goods', component: GoodsList }
const categoryRule = { path: '/categories', component: GoodsCate }
//⭐路由规则和字符传映射关系
const ruleMapping = {
    'users': userRule,
    'roles': roleRule,
    'goods': goodsRule,
    'categories': categoryRule
}
const router = new Router({
    routes: [
        {
            path: '/',
            redirect: '/home'
        },
        {
            path: '/login',
            component: Login
        },
        {
            path: '/home',
            component: Home,
            redirect: '/welcome',
            children: [
                { path: '/welcome', component: Welcome },
                // { path: '/users', component: Users },
                // { path: '/roles', component: Roles },
                // { path: '/goods', component: GoodsList },
                // { path: '/categories', component: GoodsCate }
            ]
        },
        {
            path: '*',
            component: NotFound
        }
    ]
})
router.beforeEach((to, from, next) => {
    if (to.path === '/login') {
        next()
    } else {
        const token = sessionStorage.getItem('token')
        if (!token) {
            next('/login')
        } else {
            next()
        }
    }
})
//⭐⭐⭐⭐⭐⭐⭐⭐⭐最难理解地方
export function initDynamicRoutes() {
    //根据二级权限,对路由规则进行动态添加
    const currentRoutes = router.options.routes
    //currentRoutes[2].children.push()
    const rightList = store.state.rightList
    rightList.forEach(item => {
        item.children.forEach(item => {
            currentRoutes[2].children.push(ruleMapping[item.path])
        })
    })
    router.addRoutes(currentRoutes)
}
export default router

Login.vue

// ⭐引入
import { initDynamicRoutes } from '@/router.js'
login() {
    this.$refs.loginFormRef.validate(async valid => {
        if (!valid) return
        const { data: res } = await this.$http.post('login',
            this.loginForm)
        if (res.meta.status !== 200) return
        this.$message.error('登录失败!')
        this.$store.commit('setRightList', res.rights)
        this.$store.commit('setUsername', res.data.username)
        sessionStorage.setItem('token', res.data.token)
        //⭐根据用户具备的权限,动态添加路由规则  router.js到处的方法
        initDynamicRoutes()
        this.$message.success('登录成功')
        this.$router.push('/home')
    })
}

App.vue

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

3.3.按钮的控制

按钮控制
虽然⽤户可以看到某些界⾯了, 但是这个界⾯的⼀些按钮,该⽤户可能是没有权限的.因此, 我们需要对组
件中的⼀些按钮进⾏控制. ⽤户不具备权限的按钮就隐藏或者禁⽤, ⽽在这块中, 可以把该逻辑放到⾃定
义指令中

  • permission.js
import Vue from 'vue'
import router from '@/router.js'
Vue.directive('permission', {
    inserted: function (el, binding) {
        const action = binding.value.action
        const currentRight = router.currentRoute.meta
        if (currentRight) {
            if (currentRight.indexOf(action) == -1) {
                // 不具备权限
                const type = binding.value.effect
                if (type === 'disabled') {
                    el.disabled = true
                    el.classList.add('is-disabled')
                } else {
                    el.parentNode.removeChild(el)
                }
            }
        }
    }
})
  • main.js
import './utils/permission.js'
  • router.js
export function initDynamicRoutes() {
    const currentRoutes = router.options.routes
    const rightList = store.state.rightList
    rightList.forEach(item => {
        item.children.forEach(item => {
            const itemRule = ruleMapping[item.path]
            //⭐⭐⭐
            itemRule.meta = item.rights
            currentRoutes[2].children.push(itemRule)
        })
    })
    router.addRoutes(currentRoutes)
}
  • 使⽤指令
v-permission="{action:'add'}"
v-permission="{action:'delete', effect:'disabled'}"

3.4.请求和响应的控制

请求控制
  • 除了登录请求都得要带上token, 这样服务器才可以鉴别你的身份
    http,js 封装axios处
axios.interceptors.request.use(function (req) {
    const currentUrl = req.url
    if (currentUrl !== 'login') {
        req.headers.Authorization = sessionStorage.getItem('token')
    }
    return req
})
  • 如果发出了⾮权限内的请求, 应该直接在前端访问内阻止,虽然这个请求发到服务器也会被拒绝
import axios from 'axios'
import Vue from 'vue'
import router from '../router'
// 配置请求的跟路径, ⽬前⽤mock模拟数据, 所以暂时把这⼀项注释起来
// axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/'
const actionMapping = {
    get: 'view',
    post: 'add',
    put: 'edit',
    delete: 'delete'
}
axios.interceptors.request.use(function (req) {
    const currentUrl = req.url
    if (currentUrl !== 'login') {
        req.headers.Authorization = sessionStorage.getItem('token')
        // 当前模块中具备的权限
        // 查看 get请求 
        // 增加 post请求 
        // 修改 put请求 
        // 删除 delete请求
        const method = req.method
        // 根据请求, 得到是哪种操作
        const action = actionMapping[method]
        // 判断action是否存在当前路由的权限中
        const rights = router.currentRoute.meta
        if (rights && rights.indexOf(action) == -1) {
            // 没有权限
            alert('没有权限')
            return Promise.reject(new Error('没有权限'))
        }
    }
    return req
})
axios.interceptors.response.use(function (res) {
    return res
})
Vue.prototype.$http = axios
响应控制
  • 得到了服务器返回的状态码401, 代表token超时或者被篡改了, 此时应该强制跳转到登录界⾯
axios.interceptors.response.use(function (res) {
    if (res.data.meta.status === 401) {
        router.push('/login')
        sessionStorage.clear()
        window.location.reload()
    }
    return res
})

4.⼩结

前端权限的实现必须要后端提供数据⽀持, 否则⽆法实现.
返回的权限数据的结构,前后端需要沟通协商, 怎样的数据使⽤起来才最⽅便.

4.1.菜单控制

权限的数据需要在多组件之间共享, 因此采⽤vuex
防⽌刷新界⾯,权限数据丢失, 所以需要存储在sessionStorage, 并且要保证两者的同步

4.2.界⾯控制

路由的导航守卫可以防⽌跳过登录界⾯
动态路由可以让不具备权限的界⾯的路由规则压根就不存在

4.3.按钮控制

路由规则中可以增加路由元数据meta
通过路由对象可以得到当前的路由规则,以及存储在此规则中的meta数据
⾃定义指令可以很⽅便的实现按钮控制

4.4.请求和响应控制

请求拦截器和响应拦截器的使⽤
请求⽅式的约定restful

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值