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

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. C#的@标志的使用情况—本篇blog采用Markdown编写

    @(C# 参考--出自官方文档入口) 1.使 C# 关键字用作标识符. @ 字符可作为代码元素的前缀,编译器将把此代码元素解释为标识符而非 C# 关键字. 下面的示例使用 @ 字符定义其在 for 循 ...

  2. .NET Core使用swagger遇到的坑

    今天突然点开写好的接口,突然发现展开时同时展开了两个接口.如图 我点这两个接口任意一个,这两个都会同时展开或折叠. 原因是他们actinName相同,虽然在vs里面只要http方法不同,action是 ...

  3. stm32 printf重定向

    printf函数调用fputc int fputc(int ch, FILE *p) { USART_SendData(USART1, ch); //重定向到串口 while(USART_GetFla ...

  4. 服务器去掉IE浏览器网络安全限制

  5. ifeq ifneq ifdef ifndef

    条件语句中使用到了三个关键字:“ifeq”.“else”和“endif”.其中: 1.        “ifeq”表示条件语句的开始,并指定了一个比较条件(相等).之后是用圆括号括包围的.使用逗号“, ...

  6. 对WAF的一些认知

    WAF分为非嵌入型与嵌入型, 非嵌入型指的是硬WAF.云WAF.虚拟机WAF之类的:嵌入型指的是web容器模块类型WAF.代码层WAF.非嵌入型对WEB流量的解析完全是靠自身的,而嵌入型的WAF拿到的 ...

  7. Flutter——Wrap组件(流式布局)

    Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表现几乎一致.但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainA ...

  8. CentOS7.X 搭建LAMP

    第一部分搭建LAMP基础环境 1.检查CentOS是否为7.x版本 2.安装LAMP中的apache,采用yum源方法安装 yum  install httpd httpd-devel       A ...

  9. C# Winfrom 窗体上动态生成控件慢处理

    处理方式:布局挂起 panelContent.SuspendLayout(); panelContent.ResumeLayout(); private void button1_Click(obje ...

  10. 三星Q470c Logo界面无限掉电重启,变砖后的挽救过程

    背景 三星笔记本的部分型号如:NP530 Q470等 安装win8后再次重装系统(我弄了个Ubuntu18)会导致无法进入BIOS菜单页面的问题.启动显示logo页面后,能够听到明显啪的一声(硬盘掉电 ...