后台管理系统——权限管理

245 阅读5分钟

权限验证:不同的权限对应着不同的路由,同时侧边栏也需根据不同的权限,异步生成

登录和权限验证的思路:

  • 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie/session中,保证刷新页面后能记住用户登录状态),前端会根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)
  • 权限验证:通过token获取用户对应的 role,动态根据用户的 role 算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。
  • 上述所有的数据和操作都是通过 vuex 全局管理控制的。

1. 点击登录 :

// 登录请求的方法一般写在vuex中,在登录页面去调用
this.$store.dispatch('login', this.loginForm).then(() => {
  this.$router.push({ path: '/' })  // 登录成功之后重定向到首页
}).catch(err => {
  this.$message.error(err); // 登录失败提示错误
  return false
});
​

登录请求的方法 :

在 @/store/modules/user.js 文件中:

// @/utils 下面的 auth.js 文件一般是用来导出 token 相关的方法,例如保存token到浏览器等
import { setToken } from '@/utils/auth'
import { login } from '@/api/user'login({ commit }, userInfo) {
  const { username, password } = userInfo
  return new Promise((resolve, reject) => {
    login({ username: username.trim(), password: password }).then(response => {
      const { data } = response
      setToken(data.token) // 将token存储在cookie之中
      commit('SET_TOKEN', data.token) // 将token存储到vuex中
      resolve()
    }).catch(error => {
      reject(error)
    });
  });
}

在 @/utils/auth.js 文件中:

import Cookies from 'js-cookie'const TokenKey = 'Admin-Token'
export function getToken() {
  return Cookies.get(TokenKey)
}
export function setToken(token) {
  return Cookies.set(TokenKey, token)
}
export function removeToken() {
  return Cookies.remove(TokenKey)
}

登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不用再去登录页面重新登录了。

2. 登录成功之后,获取用户信息

用户登录成功之后,我们会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后我们就要去获取用户的基本信息了 。

前端会有一份路由表,它表示了每一个路由可访问的权限,也可以通过接口请求的方式来获得路由表。当用户登录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过router.addRoutes动态挂载路由 。

在 @/permission.js 文件中:

import router from "./router";
import store from "./store";
​
router.beforeEach((to, from, next) => {
        // 判断是否有token,如果有token
        if (store.getters.token) {
          // 如果要跳转到登录页
          if (to.path === '/login') {
            next({ path: '/' })
          } else {
            // 是否是登录后第一次跳转
            if (store.getters.roles.length === 0) {
              // 获取到用户的权限信息
              // 获取用户信息的请求方法跟登录请求的方法一样,一般写在vuex中
              const { roles } = await store.dispatch('user/getInfo')
              // 生成可访问的路由表
              const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
              // 动态添加可访问路由表
              router.addRoutes(accessRoutes)
              // hack方法 确保addRoutes已完成  
              next({ ...to, replace: true }) 
            } else {
              // 当有用户权限的时候,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面
              next() 
            }
          }
        } else {
          // 如果没有token
          // 在免登录白名单,直接进入
          if (whiteList.indexOf(to.path) !== -1) {
            next()
          } else {
            // 否则全部重定向到登录页
            next('/login')
          }
        }
      })

在 @/store/getters.js 文件中:

const getters = {
  sidebar: state => state.app.sidebar,
  size: state => state.app.size,
  device: state => state.app.device,
  visitedViews: state => state.tagsView.visitedViews,
  cachedViews: state => state.tagsView.cachedViews,
  token: state => state.user.token,
  avatar: state => state.user.avatar,
  name: state => state.user.name,
  introduction: state => state.user.introduction,
  roles: state => state.user.roles,
  permission_routes: state => state.permission.routes,
  errorLogs: state => state.errorLog.logs
}
export default getters

在 @/store/modules/user.js 文件中:####

import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'
 
const state = {
  token: getToken(),
  name: '',
  avatar: '',
  introduction: '',
  roles: []
}
​
const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  SET_INTRODUCTION: (state, introduction) => {
    state.introduction = introduction
  },
  SET_NAME: (state, name) => {
    state.name = name
  },
  SET_AVATAR: (state, avatar) => {
    state.avatar = avatar
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}
​
const actions = {
    getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        if (!data) {
          reject('验证失败,请重新登录')
        }
        const { roles, name, avatar, introduction } = data
        if (!roles || roles.length <= 0) {
          reject('getInfo: roles 必须是非空数组')
        }
        commit('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },
}

在 @/store/modules/permission.js 文件中:####

// 导入公共路由和权限路由表
// 如果路由表由后端返回,则只导入公共路由,路由表在 generateRoutes 方法中获取
import { constantRoutes, asyncRoutes } from '@/router'// function hasPermission(roles, route) {
//  if (route.meta && route.meta.roles) {
//    return roles.some(role => route.meta.roles.includes(role))
//  } else {
//    return true
//  }
// }
// 根据 roles 筛选对应的 routes
// export function filterAsyncRoutes(routes, roles) {
//  const res = []
//  routes.forEach(route => {
//    const tmp = { ...route }
//    if (hasPermission(roles, tmp)) {
//      if (tmp.children) {
//        tmp.children = filterAsyncRoutes(tmp.children, roles)
//      }
//      res.push(tmp)
//    }
//  })
//  return res
// }const state = {
  routes: [],
  addRoutes: []
}
​
const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}
​
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      // 将对应权限生成的路由表存储到vuex中
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

代码解释:

在进行路由跳转时先判断是否有token,如果有,则表示已登录成功。如果没有,判断将要跳转的页面是否在免登录白名单内,是则跳转,不是则跳转到登录页。

如果有token,表示这时是已登录的状态。

如果要跳转到登录页,就让它直接跳转到首页,因为这时已经登录,所以不用跳转到登录页重新登录。

如果跳转到其他页面的话,先判断是否有用户的权限数据,如果没有,说明是登录成功后第一次跳转,就调接口获取用户的权限信息,然后动态生成可访问的路由表,并将路由表添加到路由中。

如果有用户权限的时候,说明所有可访问路由已生成,可直接跳转,如果访问没权限的全面会自动进入404页面 。

在动态生成可访问的路由表,并将路由表添加到路由中的时候要注意,一定要把404页面放在最后,如果后端在路由表中没有返回404页面的路由数据,那就要在前端单独再处理一下。