Vue权限控制

Vue权限控制

1.权限相关概念

1.1.权限的分类

后端权限

前端权限

1.2.前端权限的意义

降低⾮法操作的可能性

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

提⾼⽤户体验

2.前端权限控制思路

2.1.菜单的控制

2.2.界⾯的控制

2.3.按钮的控制

2.4.请求和响应的控制

3. Vue的权限控制实现

3.1.菜单的控制

3.2.界⾯的控制

3.3.按钮的控制

3.4.请求和响应的控制

4.⼩结

4.1.菜单控制

4.2.界⾯控制

4.3.按钮控制

4.4.请求和响应控制

在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": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjUwMCwicmlkIjowLCJpYXQiOjE1MTI1NDQyOTksImV4cCI6MTUxMjYzMDY5OX0.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

}

}

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

token,⽤于前端⽤户的状态保持

rights:该⽤户具备的权限数据,⼀级权限就对应⼀级菜单,⼆级权限就对应⼆级菜单

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

vuex中的代码

export default new Vuex.Store({

state: {

rightList: []

},

mutations: {

setRightList(state, data) {

state.rightList = data

}

},

actions: {},

getters: {}

})

Login.vue的代码

login() {

this.$refs.loginFormRef.validate(async valid => {

        ......

        this.$store.commit('setRightList', res.rights)

        this.$message.success('登录成功')

        this.$router.push('/home')

})

}

Home.vue的代码

import { mapState } from 'vuex'

computed: { ...mapState(['rightList'])

}

created() {

this.activePath = window.sessionStorage.getItem('activePath')

    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.clear()

    this.$router.push('/login')

    window.location.reload()

},

3.2.界⾯的控制

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

如何判断是否登录

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

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)

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, 这样服务器才可以鉴别你的身份

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

————————————————

版权声明:本文为CSDN博主「前端路啊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/m0_62118859/article/details/124275448

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容