登录权限控制包含着这么几个方面的含义:

1)不同的权限对应不同的路由

2)侧边栏需要根据不同的权限,异步生成

登录:使用用户名和密码,登录成功后返回用户的token(防止XSS攻击),将此token存放在cookie中(保证刷新页面后依旧能够记住用户的登录状态)

之后,前端根据token去拉取一个user_info的接口,获取用户详细信息,包括role

根据用户的role,动态计算对应权限的路由,使用router.addRoutes动态挂载这些路由

涉及:vue-router,vuex,axios拦截等等

因为vue-router必须在vue实例化之前就挂载,所以不太方便动态改变,不过好在vue-router2.2之后新增了router.addRouters

基本思路如下:

  • 创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页面。
  • 当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路由表。
  • 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
  • 使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。

1)vue-router的构造配置,分为两部分,同步路由(包括首页、登录页、以及不实使用权限的公用页面),以及可能根据用户权限进行异步加载的路由

import Vue from 'vue'
import Router from 'vue-router'
const login = (resolve) => require(['../components/pages/login/login.vue'], resolve);
const home = (resolve) => require(['../components/common/home/home.vue'], resolve);
const basicInfo = (resolve) => require(['../components/pages/basicInfo/basicInfo.vue'], resolve);
const productDayForm = (resolve) => require(['../components/pages/productDayForm/productDayForm.vue'], resolve);
const provinceDayForm = (resolve) => require(['../components/pages/provinceDayForm/provinceDayForm.vue'], resolve);
const productMonthForm = (resolve) => require(['../components/pages/productMonthForm/productMonthForm.vue'], resolve);
const provinceMonthForm = (resolve) => require(['../components/pages/provinceMonthForm/provinceMonthForm.vue'], resolve);
const personCenter = (resolve) => require(['../components/pages/personCenter/personCenter.vue'], resolve);
const userManage = (resolve) => require(['../components/pages/userManage/userManage.vue'], resolve);
// 测试用
const dashboard = (resolve) => require(['../components/pages/dashboard/dashboard.vue'], resolve);
const errorPage = (resolve) => require(['../components/pages/404/404.vue'], resolve);
const passwordReset = (resolve) => require(['../components/pages/passwordReset/passwordReset.vue'], resolve);
const messageTemplate = (resolve) => require(['../components/pages/messageTemplate/messageTemplate.vue'], resolve);
const messageEdit = (resolve) => require(['../components/pages/messageEdit/messageEdit.vue'], resolve);
const messageDetail = (resolve) => require(['../components/pages/messageDetail/messageDetail.vue'], resolve);
const IntelHold = (resolve) => require(['../components/pages/IntelHold/IntelHold.vue'], resolve);
const Test = () => import('../components/pages/Test/Test.vue');
Vue.use(Router) export const constantRouterMap = [{
path: '/login',
component: login,
name: 'login',
meta: {
hidden: true
}
},
{
path: '/resetPassword',
component: passwordReset,
name: 'resetPassword',
meta: {
hidden: true
}
}
]; export const asyncRouterMap = [{
path: '/',
component: home,
name: 'root',
redirect: '/static',
meta: {
dropdown: false
},
children: [{
path: 'static',
component: dashboard,
name: 'dashboard',
meta: {
title: '数据统计',
icon: 'icon-attendance'
}
}]
}, {
path: '/dayForm',
component: home,
redirect: '/dayForm/productDayForm',
name: 'dayForm',
meta: {
title: '日报表',
icon: 'icon-calendar',
dropdown: true
},
children: [{
path: 'productDayForm',
component: productDayForm,
name: 'productDayForm',
meta: {
title: '产品日报表'
}
}, {
path: 'provinceDayForm',
component: provinceDayForm,
name: 'provinceDayForm',
meta: {
title: '区域日报表'
}
}]
}, {
path: '/monthForm',
component: home,
redirect: '/monthForm/productMonthForm',
name: 'monthForm',
meta: {
title: '月报表',
icon: 'icon-cangneishicao',
dropdown: true
},
children: [{
path: 'productMonthForm',
component: productMonthForm,
name: 'productMonthForm',
meta: {
title: '产品月报表'
}
}, {
path: 'provinceMonthForm',
component: provinceMonthForm,
name: 'provinceMonthForm',
meta: {
title: '区域月报表'
}
}]
}, {
path: '/detail',
component: home,
redirect: '/detail/basicInfo',
name: 'detail',
meta: {
title: '明细',
icon: 'icon-delivery',
dropdown: true
},
children: [{
path: 'basicInfo',
component: basicInfo,
name: 'basicInfo',
meta: {
title: '基本信息'
}
}]
}, {
path: '/message',
component: home,
redirect: '/message/messageTemplate',
name: 'message',
meta: {
title: '短信维系',
icon: 'icon-chat',
dropdown: true
},
children: [{
path: 'messageTemp',
component: messageTemplate,
name: 'messageTemplate',
meta: {
title: '经典模型'
},
children: [{
path: ':id',
component: messageDetail,
name: 'messageDetail',
meta: {
hidden: true
}
}]
}, {
path: 'messageEdit',
component: messageEdit,
name: 'messageEdit',
meta: {
title: '快速建模'
}
}]
}, {
path: '/',
component: home,
name: 'root',
redirect: '/static',
meta: {
icon: 'el-icon-star-on',
dropdown: false
},
children: [{
path: 'intelHold',
component: IntelHold,
name: 'IntelHold',
meta: {
title: '智慧维系',
icon: 'icon-bank-card'
}
}, {
path: 'userManage',
component: userManage,
name: 'userManage',
meta: {
role: ['admin'],
title: '用户管理',
icon: 'icon-power'
}
}, {
path: 'personCenter',
component: personCenter,
name: 'personCenter',
meta: {
title: '个人中心',
icon: 'icon-user'
}
}, {
path: 'test',
component: Test,
name: 'Test',
meta: {
title: '测试',
icon: 'icon-user'
}
}]
}, {
path: '*',
component: errorPage,
name: '',
meta: {
hidden: true
}
}];
export default new Router({
scrollBehavior: () => ({
y:
}),
routes: constantRouterMap
});

router.js

2)用户信息:包括role,name,token,avatar,token等都使用vuex来集中管理

import {
loginByEmail,
logout,
getInfo
} from 'api/login';
import {
getToken,
setToken,
removeToken
} from 'utils/auth'; const user = {
state: {
user: '',
status: '',
code: '',
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: [],
setting: {
articlePlatform: []
}
}, mutations: {
SET_CODE: (state, code) => {
state.code = code;
},
SET_TOKEN: (state, token) => {
state.token = token;
},
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction;
},
SET_SETTING: (state, setting) => {
state.setting = setting;
},
SET_STATUS: (state, status) => {
state.status = status;
},
SET_NAME: (state, name) => {
state.name = name;
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar;
},
SET_ROLES: (state, roles) => {
state.roles = roles;
},
LOGIN_SUCCESS: () => {
console.log('login success')
},
LOGOUT_USER: state => {
state.user = '';
}
}, actions: {
// 邮箱登录
LoginByEmail({
commit
}, userInfo) {
const email = userInfo.email.trim();
return new Promise((resolve, reject) => {
loginByEmail(email, userInfo.password).then(response => {
console.log('login response', response);
// debugger
const data = response.data;
setToken(response.data.token);
commit('SET_TOKEN', data.token);
resolve();
}).catch(error => {
reject(error);
});
});
}, // 获取用户信息
GetInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const data = response.data;
commit('SET_ROLES', data.role);
commit('SET_NAME', data.name);
commit('SET_AVATAR', data.avatar);
commit('SET_INTRODUCTION', data.introduction);
resolve(response);
}).catch(error => {
reject(error);
});
});
}, // 第三方验证登录
LoginByThirdparty({
commit,
state
}, code) {
return new Promise((resolve, reject) => {
commit('SET_CODE', code);
loginByThirdparty(state.status, state.email, state.code).then(response => {
commit('SET_TOKEN', response.data.token);
setToken(response.data.token);
resolve();
}).catch(error => {
reject(error);
});
});
}, // 登出
LogOut({
commit,
state
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '');
commit('SET_ROLES', []);
removeToken();
resolve();
}).catch(error => {
reject(error);
});
});
}, // 前端 登出
FedLogOut({
commit
}) {
return new Promise(resolve => {
commit('SET_TOKEN', '');
removeToken();
resolve();
});
}, // 动态修改权限
ChangeRole({
commit
}, role) {
return new Promise(resolve => {
commit('SET_ROLES', [role]);
commit('SET_TOKEN', role);
setToken(role);
resolve();
})
}
}
}; export default user;

store/user.js

3)  不同用户角色的路由权限信息,也使用vuex进行集中管理

import { asyncRouterMap, constantRouterMap } from 'src/router'

/**
* 通过meta.role判断是否与当前用户权限匹配
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.role) {
return roles.some(role => route.meta.role.indexOf(role) >= )
} else {
return true
}
} /**
* 递归过滤异步路由表,返回符合用户角色权限的路由表
* @param asyncRouterMap
* @param roles
*/
function filterAsyncRouter(asyncRouterMap, roles) {
const accessedRouters = asyncRouterMap.filter(route => {
if (hasPermission(roles, route)) {
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, roles)
}
return true
}
return false
})
return accessedRouters
} const permission = {
state: {
routers: constantRouterMap,
addRouters: []
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers
state.routers = constantRouterMap.concat(routers)
}
},
actions: {
GenerateRoutes({ commit }, data) {
return new Promise(resolve => {
const { roles } = data
let accessedRouters
if (roles.indexOf('admin') >= ) {
accessedRouters = asyncRouterMap
} else {
accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
}
commit('SET_ROUTERS', accessedRouters);
resolve();
})
}
}
}; export default permission;

store/permission.js

4) 使用router.beforeEach注册一个全局前置守卫

// permissiom judge
function hasPermission(roles, permissionRoles) {
if (roles.indexOf('admin') >= ) return true; // admin权限 直接通过
if (!permissionRoles) return true;
return roles.some(role => permissionRoles.indexOf(role) >= )
} // register global progress.
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start(); // 开启Progress
if (getToken()) { // 判断是否有token
if (to.path === '/login') {
next({ path: '/' });
} else {
if (store.getters.roles.length === ) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取user_info
const roles = res.data.role;
store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to }); // hack方法 addRoutes之后next()可能会失效,因为可能next()的时候add没有完成,可以通过next({...to})此时之前的导航会被放弃,重新发起新的导航,来避免这个问题
})
}).catch(() => {
store.dispatch('FedLogOut').then(() => {
next({ path: '/login' });
})
})
} else {
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
if (hasPermission(store.getters.roles, to.meta.role)) {
next();//
} else {
next({ path: '/401', query: { noGoBack: true } });
}
// 可删 ↑
}
}
} else {
if (whiteList.indexOf(to.path) !== -) { // 在免登录白名单,直接进入
next()
} else {
next('/login'); // 否则全部重定向到登录页
NProgress.done(); // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行!
}
}
}); router.afterEach(() => {
NProgress.done(); // 结束Progress
});

节选自main.js(路由守卫)

5)侧边栏渲染

侧边栏根据之前计算出的当前用户权限对应的路由(vuex管理),进行渲染,同时为了支持无限嵌套路由,使用了递归组件

const getters = {
sidebar: state => state.app.sidebar,
visitedViews: state => state.app.visitedViews,
token: state => state.user.token,
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
status: state => state.user.status,
roles: state => state.user.roles,
setting: state => state.user.setting,
permission_routers: state => state.permission.routers,
addRouters: state => state.permission.addRouters
};
export default getters

getters

<template>
<el-menu mode="vertical" theme="dark" unique-opened :default-active="$route.path" :collapse="isCollapse">
<sidebar-item :routes='permission_routers'></sidebar-item>
</el-menu>
</template> <script>
import { mapGetters } from 'vuex';
import SidebarItem from './SidebarItem';
export default {
components: { SidebarItem },
computed: {
...mapGetters([
'permission_routers',
'sidebar'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>

sidebar.vue

<template>
<div class='menu-wrapper'>
<template v-for="item in routes"> <router-link v-if="!item.hidden&&item.noDropdown&&item.children.length>0" :to="item.path+'/'+item.children[0].path">
<el-menu-item :index="item.path+'/'+item.children[0].path" class='submenu-title-noDropdown'>
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg><span slot="title">{{item.children[].name}}</span>
</el-menu-item>
</router-link> <el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
<template slot="title">
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg><span>{{item.name}}</span>
</template>
<template v-for="child in item.children" v-if='!child.hidden'> <sidebar-item class='nest-menu' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item> <router-link v-else :to="item.path+'/'+child.path">
<el-menu-item :index="item.path+'/'+child.path">
<icon-svg v-if='child.icon' :icon-class="child.icon"></icon-svg><span>{{child.name}}</span>
</el-menu-item>
</router-link> </template> </el-submenu> </template>
</div>
</template> <script>
export default {
name: 'SidebarItem',
props: {
routes: {
type: Array
}
}
}
</script> <style rel="stylesheet/scss" lang="scss" scoped> </style>

sidebarItem.vue

这里考虑一个问题:对于同时具有上侧和左侧导航的页面来说,当切换上侧导航时,左侧导航栏也对应不同(可参见element-ui的官方文档效果),这种是怎么实现的?

一种待验证的思路:在vuex中设置一个currentRoutes的state,在路由守卫(感觉选择路由独享的守卫,即在路由配置上直接定义beforeEnter守卫比全局守卫更合适)中切换currentRoutes的值,侧边栏根据currentRoutes的值进行渲染

6)axios封装

通过request拦截器在每个请求的头部塞入token,好让后台对请求进行权限验证;并在response拦截器中,判断服务端返回的特殊状态码,进行统一处理,如没有权限或者token失效(为了安全考虑,一般一个token的有效期都是session,就是当浏览器关闭就丢失了,重新打开浏览器需要重新验证,后台也会在比如每周一个固定时间点重新刷新token,强制所有用户重新登录一次)等

import axios from 'axios';
import {
Message
} from 'element-ui';
import store from '../store';
import {
getToken
} from 'utils/auth'; // 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api的base_url
timeout: // 请求超时时间
}); // request拦截器
service.interceptors.request.use(config => {
// Do something before request is sent
if (store.getters.token) {
config.headers['X-Token'] = getToken(); // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
}
return config;
}, error => {
// Do something with request error
console.log(error); // for debug
Promise.reject(error);
}) // respone拦截器
service.interceptors.response.use(
response => {
return response;
},
/**
* 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/
// const res = response.data;
// if (res.code !== 20000) {
// Message({
// message: res.message,
// type: 'error',
// duration: 5 * 1000
// });
// // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
// if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
// confirmButtonText: '重新登录',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload();// 为了重新实例化vue-router对象 避免bug
// });
// })
// }
// return Promise.reject(error);
// } else {
// return response.data;
// }
error => {
console.log('err' + error); // for debug
Message({
message: error.message,
type: 'error',
duration: *
});
return Promise.reject(error);
}
) export default service;

axios封装

vue后台_登录权限的更多相关文章

  1. vue 后台管理系统菜单权限管理

    来自:https://www.cnblogs.com/fqh123/p/11094296.html 侵删 login登录方法 login() { if (!this.username) { retur ...

  2. vue后台_实战篇

    一.一些常用组件效果的实现 1)面包屑导航 主要是使用$route.mathed:一个数组,包含当前路由的所有嵌套路径片段的路由记录 .路由记录就是 routes 配置数组中的对象副本 (还有在 ch ...

  3. vue后台_纯前端实现excel导出/csv导出

    之前的文件下载功能一般是由前后端配合实现,由于项目需要,纯前端实现了一把excel的导出功能: 一.excel导出 1.安装依赖库 xlsx:这是一个功能强大的excel处理库,但是上手难度也很大,还 ...

  4. 【转】手摸手,带你用vue撸后台 系列二(登录权限篇)

    前言 拖更有点严重,过了半个月才写了第二篇教程.无奈自己是一个业务猿,每天被我司的产品虐的死去活来,之前又病了一下休息了几天,大家见谅. 进入正题,做后台项目区别于做其它的项目,权限验证与安全性是非常 ...

  5. 一步步带你做vue后台管理框架(三)——登录功能

    系列教程<一步步带你做vue后台管理框架>第三课 github地址:vue-framework-wz 线上体验地址:立即体验 <一步步带你做vue后台管理框架>第一课:介绍框架 ...

  6. vue后台管理系统权限处理

    vue后台管理系统权限 1.权限问题:用户和管理员进入管理系统看到的模块是不一样的,管理员看的的要比用户看到的多.需要用到动态加载路由,router.addRouters()来动态的挂载路由 // 1 ...

  7. VUE 后台管理系统权限控制

    谈一谈VUE 后台管理系统权限控制 前端权限从本质上来讲, 就是控制视图层的展示,比如说是某个页面或者某个按钮,后端权限可以控制某个用户是否能够查询数据, 是否能够修改数据等操作,后端权限大部分是基于 ...

  8. vue路由登录拦截(vue router登录权限控制)

    实现原理: 哪些路由需要验证需要在路由文件router/index.js中指定: { path: '/', component: Home, name: 'Home', iconCls: 'fa fa ...

  9. vue路由元之进入路由需要用户登录权限功能

    为什么需要路由元呢??? 博猪最近开发刚刚好遇到一个情况,就是有个路由页面里面包含了客户的信息,客户想进这个路由页面的话, 就可以通过请求数据获取该信息,但是如果客户没有登录的话,是不能进到该页面的, ...

随机推荐

  1. .net core +gogs + jenkins +docker自动化发布、部署

    1.首先,安装docker,不多bb 2.我们采用docker的方式安装jenkins,同时将宿主机的docker挂载到docker安装的jenkins里面,可能有点拗口.说白了就是 就是要让jenk ...

  2. 0.a开始数据结构征程

    决定开始从mooc和ppt上学习数据结构,......书暂时不看.在进入数据结构之前,我首先将自己以一个还未进入大山但又向往山中美景的探险者身份对数据结构的几点疑问的答案的寻找和思考写在下面. 什么是 ...

  3. 34. Find First and Last Position of Element in Sorted Array + 二分

    题意懒得抄了,大概是:在升序数组中给定整数target,找到第一个和最后一个target的索引,找到返回{index1, index2},否则返回{-1, -1}: 时间复杂度要求:O(logn) 分 ...

  4. 1.NIO概述

    /*Java NIO 简介*/ java NIO (New IO)是从 java1.4版本开始引入的一个新的IO API,可以替代标准的 java IO API (jdk1.7又对其进行了改进, 称为 ...

  5. 【leetcode】575. Distribute Candies

    原题 Given an integer array with even length, where different numbers in this array represent differen ...

  6. 集成IDE anaconda

    Anaconda 下载安装完anaconda后,会生成如下工具: 安装Anaconda不需要使用管理员权限.安装完毕后,Anaconda附带一个图形启动器(Anaconda Prompt),可以使用他 ...

  7. zabbix low-level discovery 监控mysql

    当一台服务器上MySQL有多个实例的时候,MySQL占用多个不同端口.利用zabbix的low-level discovery可以轻松监控. 思路参考:http://dl528888.blog.51c ...

  8. 复杂sql书写方法

    给你一个复杂sql连接不同的表,多个嵌套查询条件等的语句时,你是非常的胆怯由于对语法的不熟悉以及没有经验和自信,现在我们来学习一下如何写复杂的sql,我们把它分解为很多小的步骤进行 一.集中最后的输出 ...

  9. Bert-util安装

    转载:https://blog.csdn.net/u013109501/article/details/91987180 https://blog.csdn.net/Vancl_Wang/articl ...

  10. java中的io流总结(一)

    知识点:基于抽象基类字节流(InputStream和OutputStream).字符流(Reader和Writer)的特性,处理纯文本文件,优先考虑使用字符流BufferedReader/Buffer ...