权限验证:不同的权限对应着不同的路由,同时侧边栏也需根据不同的权限,异步生成
登录和权限验证的思路:
- 登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个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页面的路由数据,那就要在前端单独再处理一下。