vue权限控制

在SPA(单页面应用)中,前端需要根据用户的权限来控制用户菜单以及路由表,vue-router提供了几个路由生命周期钩子,叫做路由守卫,我们可以利用路由守卫在路由以及路由元信息进行权限控制,同时搭配vuex将会更美味,文末有完整示例地址
<!-- more -->

登录权限

登录验证是最常见的一种路由权限验证,使用vuex + 路由守卫可以实现比较清晰流畅的鉴权流,能轻松应对页面刷新、清除缓存等场景。

路由元信息

vue-router在构建路由时提供了元信息meta配置接口,我们可以在元信息中添加路由对应的权限,然后在路由守卫中检查相关权限,控制其路由跳转。

  1. // router.js
  2. // 路由表元信息
  3. [
  4. {
  5. path: '',
  6. redirect: '/home'
  7. },
  8. {
  9. path: '/home',
  10. meta: {
  11. title: 'Home',
  12. icon: 'home'
  13. }
  14. },
  15. {
  16. path: '/userCenter',
  17. meta: {
  18. title: '个人中心',
  19. requireAuth: true // 在需要登录的路由的meta中添加响应的权限标识
  20. }
  21. }
  22. ]
  23. // 在守卫中访问元信息
  24. function gaurd (to, from, next) {
  25. // to.matched.some(record => record.meta.requireAuth)
  26. // 可在此处
  27. }

vuex

一般的,用户登录后会在本地持久化存储用户的认证信息,本文以JWTtoken为例。将用户的token持久化到localStorage里,而用户信息则存在内存(store)中。这样可以在vuex中存储一个标记用户登录状态的属性auth,方便用语权限控制。

  1. // store.js
  2. {
  3. state: {
  4. token: window.localStorage.getItem('token'),
  5. auth: false,
  6. userInfo: {}
  7. },
  8. mutations: {
  9. setToken (state, token) {
  10. state.token = token
  11. window.localStorage.setItem('token', token)
  12. },
  13. clearToken (state) {
  14. state.token = ''
  15. window.localStorage.setItem('token', '')
  16. },
  17. setUserInfo (state, userInfo) {
  18. state.userInfo = userInfo
  19. state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
  20. }
  21. },
  22. actions: {
  23. async getUserInfo (ctx, token) {
  24. return fetchUserInfo(token).then(response => {
  25. if (response.code === 200) {
  26. ctx.commit('setUserInfo', response.data)
  27. }
  28. return response
  29. })
  30. },
  31. async login (ctx, account) {
  32. return login(account).then(response => {
  33. if (response.code === 200) {
  34. ctx.commit('setUserInfo', response.data.userInfo)
  35. ctx.commit('setToken', response.data.token)
  36. }
  37. })
  38. }
  39. }
  40. }

路由守卫

写好路由表和vuex之后,给所有路由设置一个全局守卫,在进入路由之前进行权限检查,并导航到对应的路由。

  1. // router.js
  2. router.beforeEach(async (to, from, next) => {
  3. if (to.matched.some(record => record.meta.requireAuth)) { // 检查是否需要登录权限
  4. if (!store.state.auth) { // 检查是否已登录
  5. if (store.state.token) { // 未登录,但是有token,获取用户信息
  6. try {
  7. const data = await store.dispatch('getUserInfo', store.state.token)
  8. if (data.code === 200) {
  9. next()
  10. } else {
  11. window.alert('请登录')
  12. store.commit('clearToken')
  13. next({ name: 'Login' })
  14. }
  15. } catch (err) {
  16. window.alert('请登录')
  17. store.commit('clearToken')
  18. next({ name: 'Login' })
  19. }
  20. } else {
  21. window.alert('请登录')
  22. next({ name: 'Login' })
  23. }
  24. } else {
  25. next()
  26. }
  27. } else {
  28. next()
  29. }
  30. })

后记

上述的方法是基于jwt认证方式,本地不持久化用户信息,只保存token,当用户刷新或者重新打开网页时,进入需要登录的页面都会尝试去请求用户信息,该操作在整个访问过程中只进行一次,直到刷新或者重新打开,对于应用后期的开发维护和扩展支持都很好。

动态加载菜单和路由

有时候为了安全,我们需要根据用户权限或者是用户属性去动态的添加菜单和路由表,可以实现对用户的功能进行定制。vue-router提供了addRoutes()方法,可以动态注册路由,需要注意的是,动态添加路由是在路由表中push路由,由于路由是按顺序匹配的,因此需要将诸如404页面这样的路由放在动态添加的最后

路由元信息

  1. // store.js
  2. // 将需要动态注册的路由提取到vuex中
  3. const dynamicRoutes = [
  4. {
  5. path: '/manage',
  6. name: 'Manage',
  7. meta: {
  8. requireAuth: true
  9. },
  10. component: () => import('./views/Manage')
  11. },
  12. {
  13. path: '/userCenter',
  14. name: 'UserCenter',
  15. meta: {
  16. requireAuth: true
  17. },
  18. component: () => import('./views/UserCenter')
  19. }
  20. ]

vuex

vuex中添加userRoutes数组用于存储用户的定制菜单。在setUserInfo中根据后端返回的菜单生成用户的路由表。

  1. // store.js
  2. setUserInfo (state, userInfo) {
  3. state.userInfo = userInfo
  4. state.auth = true // 获取到用户信息的同时将auth标记为true,当然也可以直接判断userInfo
  5. // 生成用户路由表
  6. state.userRoutes = dynamicRoutes.filter(route => {
  7. return userInfo.menus.some(menu => menu.name === route.name)
  8. })
  9. router.addRoutes(state.userRoutes) // 注册路由
  10. }

修改菜单渲染

  1. // App.vue
  2. <div id="nav">
  3. <router-link to="/">主页</router-link>|
  4. <router-link to="/login">登录</router-link>
  5. <template v-for="(menu, index) of $store.state.userInfo.menus">
  6. |<router-link :to="{ name: menu.name }" :key="index">{{menu.title}}</router-link>
  7. </template>
  8. </div>

结束语

上述为前端控制菜单和路由权限的两种实现思路,经过本人实践效果令人满意,如果有更好的想法欢迎交流,附上完整示例地址:https://github.com/YES-Lee/vue-permission-demo