实现目标
- 路由层面:用户登录校验成功后只能看到自己有权访问的导航菜单、路由
- 视图层面:只能看到自己有权访问的控件
- 异步请求层面:拦截器处理请求,拦截越权请求
前端页面权限主要包括
- 接口(资源)权限
- 菜单权限
- 路由权限
- 按钮权限
接口权限
使用jwt鉴权,配合拦截器处理拦截越权请求;401返回登录页面
axios.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
config.headers.authorization = token;
return config;
});
axios.interceptors.response.use(...);
路由权限
基本思路:基础路由(login、404等)、用户路由(admin路由...)
实现:以下两种方式均需要配置错误页,以保证用户对权限不足的感知
(1) 请求服务端获取当前用户路由配置, 使用addRoute注入路由;
缺点是之只能注入路由,无法剔除不相关路由(当切换用户进行登录时存在问题)
解决方案: 通过新建一个全新的 Router,然后将新的路由记录赋给当前页面的管理 Router,以达到更新路由配置的目的
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const createRouter = () => new Router({
mode: 'history',
routes: []
})
const router = createRouter()
// 切换登录用户,执行路由重置
export function resetRouter () {
const newRouter = createRouter()
router.matcher = newRouter.xxxx // 获取的当前用户的路由配置
}
export default router
(2) 通过请求服务端获取当前用户路由配置,通过 router.beforeEach 钩子对路由的每次跳转进行管理,每次跳转都进行检查
菜单权限
方案一:菜单与路由分离
前端定义路由,后端返回菜单标识
实现:
1. 前端路由信息设定某字段(name/id等)跟后端返回的菜单标识相关联(在权限系统中配置角色权限时将菜单标识设置为与前端路由字段相关联),便于进行唯一性校验
2. 用户登录校验成功后获取菜单权限信息(定义在jwt声明中或者后端单独定义一个接口用于查询)
3. 前端注册自定义指令,遍历导航菜单做权限判断,根据权限控制菜单显隐藏处理
缺点:
菜单与路由保证一一对应,前端添加了新功能,则菜单管理(后台权限系统)需要及时重新配置
方案二:后端返回菜单和路由
前端定义组件,后端返回路由
// 前端定义路由组件
const Home = () => import("../pages/Home.vue");
const UserInfo = () => import("../pages/UserInfo.vue");
export default {
home: Home,
userInfo: UserInfo
};
// 后端返回路由
[
{
name: "home",
path: "/",
component: "home"
},
{
name: "home",
path: "/userinfo",
component: "userInfo"
}
]
实现:后端返回路由,前端进行匹配(替换component字段为真实的路由组件);匹配完成后动态添加路由(addRoute)
缺点:前后端高度配合
按钮权限
实现:自定义指令
// 前端配置路由,指定权限
{
path: '/permission',
component: Layout,
name: 'xxx',
// 权限
meta: {
btnPermissions: ['admin', 'supper', 'normal']
},
// ....
}
// 自定义指令鉴定权限
Vue.directive('has', { ... });
// 引用指令
<button @click='editClick' v-has>编辑</button>
路由拦截
主要思路:页面跳转之前进行token校验
// 免登白名单
const whiteList = ['login', 'index']
router.beforeEach((to, from, next) => {
const token = .....;
if (hasToken) { // 登录
if (to.path === '/login') {
// 如果已登录,请重定向到主页
next({ path: '/index' })
return
}
next()
} else {
if (whiteList.indexOf(to.name) !== -1) {
next()
} else {
// 没有访问权限的其他页将重定向到登录页。
next(`/login`)
}
}
})
vue项目可以选择实例挂载之前进行校验,避免每次导航时都触发校验
// verifyToken....
// mount