背景

我司有很多需要进行权限管理的产品。其中有一个产品,需要给多个客户部署前中后台。在开发第一个版本时,代码全部分离。前端三套,后端三套。加上kafka,redis,算法,数据库等服务器,每有一个新的客户就需要部署一次,需要花费很长的时间且代码难以维护。

后决定重构代码,产品分为前,中,后三个平台。前后端分别一套代码,支持权限管理,可拓展。前端使用路由前缀判断平台,登录时会返回不同的token和用户信息。不同的token只能访问对应平台的接口,根据用户角色生成可访问的菜单,进入不同的系统

前言

权限模块对于一个项目来说是比较麻烦的部分,通常一个项目的权限管理,需要做的是下面三种级别的鉴权。

  1. 平台级别
  2. 页面级别(菜单)
  3. 控件级别(如按钮,表格展示字段等)

本篇文章站在前端的角度,实现前两种级别的权限管理(控件级别可以通过条件渲染实现)。用vue从零搭建一个前中后台权限管理模板。供大家参考。

演示地址:http://auth.percywang.top

项目地址:https://github.com/pppercyWang/vue-authentication

其实大部分项目都会分离前后台,因为整合在一套代码,确实对打包优化,代码分割需要做的更多。且项目架构上会复杂一些,安全性方面需要考虑的更全面。这里也提供了一个纯后台的权限管理模板。

项目地址:https://github.com/pppercyWang/vue-authentication2

项目结构

技术栈:vue vue-router vuex element

assets  静态资源
pluginselement-style.scss  element样式element.js   按需引入
routerindex.js 静态路由及createRouter方法
serviceapi.js  前中后台接口管理
store  vuex
utilshttp.js axios封装
viewsforeground  前台页面midground   中台页面background  后台页面layout    前中后台布局文件404.vue   404页面Login.vue   前台登录AgentLogin.vue   中台登录AdminLogin.vue   后台登录
permission.js   动态路由 前中后台鉴权 菜单数据生成
main.js  应用入口

一. 路由初始化——staticRoutes

三个平台登录是三个不一样的页面。/开头的是前台的路由,/agent是中台,/admin是后台。这里的重定向也可以跳转到具体的页面,但这里因为权限角色不同的原因,不能写死,就直接重定向到登录页。

注意:404页需要放在路由的最后面,所以放在动态路由部分

router/index.js

const staticRoutes = [{path: '/login',name: '用户登录',component: () => import('@/views/Login.vue'),},{path: '/agent/login',name: '中台登录',component: () => import('@/views/AgentLogin.vue'),},{path: '/admin/login',name: '后台登录',component: () => import('@/views/AdminLogin.vue'),},{path: '/',redirect: '/login',},{path: '/agent',redirect: '/agent/login',},{path: '/admin',redirect: '/admin/login',},
]

二. 动态路由——dynamicRoutes

本例只有中台和后台进行鉴权,一级栏目需要icon字段,用于菜单项图标。children为一级栏目的子栏目,meta中的roles数组代表可访问该route的角色。

permission.js

const dynamicRoutes = {// 前台路由'user': [{path: '/',component: () => import('@/views/layout/Layout.vue'),name: '首页',redirect: '/home',children: [{path: 'home',component: () => import('@/views/foreground/Home.vue'),}]}, ],// 中台路由'agent': [{path: '/agent/member',component: () => import('@/views/layout/AgentLayout.vue'),name: '会员管理',redirect: '/agent/member/index',icon: 'el-icon-star-on',children: [{path: 'index',component: () => import('@/views/midground/member/Index.vue'),name: '会员列表',meta: {roles: ['super_agent', 'second_agent'] // 超级代理和二级都可访问},},{path: 'scheme',component: () => import('@/views/midground/member/Scheme.vue'),name: '优惠方案',meta: {roles: ['super_agent']  // 只有超级代理可访问},},]},],// 后台路由'admin': [{path: '/admin/user',component: () => import('@/views/layout/AdminLayout.vue'),name: '用户管理',redirect: '/admin/user/index',icon: 'el-icon-user-solid',children: [{path: 'index',component: () => import('@/views/background/user/Index.vue'),name: '用户列表',meta: {roles: ['super_admin', 'admin']},},{path: 'detail',component: () => import('@/views/background/user/UserDetail.vue'),name: '用户详情',meta: {roles: ['super_admin']},},]},],'404': {path: "*",component: () => import('@/views/404.vue'),}
}

三. 登录页

通常在登录成功之后,后端会返回token跟用户信息,我们需要对token跟用户信息进行持久化,方便使用,这里我直接存在了sessionStorage。再根据用户角色的不同进入不同的路由

views/adminLogin.vue

try {const res = await this.$http.post(`${this.$api.ADMIN.login}`, this.form.loginModel)sessionStorage.setItem("adminToken", res.Data.Token);const user = res.Data.UsersessionStorage.setItem("user",JSON.stringify({username: user.username,role: user.role,ground: user.ground // 前中后台的标识  如 fore mid back}));switch (user.role) {case "ip_admin": // ip管理员this.$router.push("/admin/ip/index");break;case "admin": // 普通管理员this.$router.push("/admin/user/index");break;case "super_admin": // 超级管理员this.$router.push("/admin/user/index");break;}
} catch (e) {this.$message.error(e.Message)
}

四. 路由守卫——router.beforeEach()

只要是进入登录页,我们需要做两个事。

  1. 清除存储在sessionStorage的token信息和用户信息
  2. 使用permission.js提供的createRouter()创建一个新的router实例,替换matcher。

我们这里是使用addRoutes在静态路由的基础上添加新路由,但是文档中没有提供删除路由的api。可以试想一下,如果登录后台再登录中台,则会出现中台可以访问后台路由的情况。为什么替换matcher可以删除addRoutes添加的路由?

注:router.beforeEach一定要放在vue实例创建之前,不然当页面刷新时的路由不会进beforeEach钩子

main.js

router.beforeEach((to, from, next) => {if (to.path === '/login' || to.path === '/agent/login' || to.path === '/admin/login') {sessionStorage.clear();router.matcher = createRouter().matcher // 初始化routes,移除所有dynamicRoutesnext()return}authentication(to, from, next, store, router); //路由鉴权
})
new Vue({router,store,render: h => h(App)
}).$mount('#app')

五. 前中后台鉴权——authentication()

这里的switch函数根据to.path.split("/")[1]判定平台。在登录时成功后我们sessionStorage.setItem()保存token。
为什么要使用token agentToken adminToken三个不同的key来储存呢?而不是只将token作为key呢。这样在axios.interceptors.request.use拦截器中设置token头也不需要通过switch去获取不同的token了。

因为假设我们当前的页面路由是agent/member/index,我们手动修改为admin/xxx/xxx。我们希望它跳转到admin的登录页,而不是404页面。

isAuthentication标识是否完成鉴权,没有鉴权则调用generateRoutes获取有效路由,再通过addRoutes添加新路由

permission.js

export function authentication(to, from, next, store, router) {let token;switch (to.path.split("/")[1]) {case 'agent':token = sessionStorage.getItem('agentToken');if (!token && to.path !== '/agent/login') {next({path: '/agent/login'})return}break;case 'admin':token = sessionStorage.getItem('adminToken');if (!token && to.path !== '/admin/login') {next({path: '/admin/login'})return}break;default:token = sessionStorage.getItem('token');if (!token && to.path !== '/login') {next({path: '/login'})return}break;}const isAuth = sessionStorage.getItem('isAuthentication')if (!isAuth || isAuth === '0') {store.dispatch('getValidRoutes', JSON.parse(sessionStorage.getItem('user')).role).then(validRoutes => {router.addRoutes(validRoutes)sessionStorage.setItem('isAuthentication', '1')})}next();
}

通过user.ground判定平台

store/index.js

   getValidRoutes({commit}, role) {return new Promise(resolve => {let validRoutesswitch (JSON.parse(sessionStorage.getItem('user')).ground) {case 'fore':validRoutes = generateRoutes('user', role, commit)resolve(validRoutes);breakcase 'mid':validRoutes = generateRoutes('agent', role, commit)resolve(validRoutes);breakcase 'back':validRoutes = generateRoutes('admin', role, commit)resolve(validRoutes);break}})},

六. 角色筛选——generateRoutes()

这里干了两件最重要的事

  1. 生成el-menu的菜单数据
  2. 生成当前角色有效的路由

permission.js

export function generateRoutes(target, role, commit) {let targetRoutes = _.cloneDeep(dynamicRoutes[target]);targetRoutes.forEach(route => {if (route.children && route.children.length !== 0) {route.children = route.children.filter(each => {if (!each.meta || !each.meta.roles) {return true}return each.meta.roles.includes(role) === true})}});switch (target) {case 'admin':commit('SET_BACKGROUD_MENU_DATA', targetRoutes.filter(route => route.children && route.children.length !== 0)) // 菜单数据是不需要404的breakcase 'agent':commit('SET_MIDGROUD_MENU_DATA', targetRoutes.filter(route => route.children && route.children.length !== 0))break}return new Array(...targetRoutes, dynamicRoutes['404'])
}

七.页面刷新后数据丢失

在登录后isAuthentication为1,刷新时不会重新生成路由,导致数据丢失,在main.js监听window.onbeforeunload即可

main.js

window.onbeforeunload = function () {if (sessionStorage.getItem('user')) {sessionStorage.setItem('isAuthentication', '0') // 在某个系统登录后,页面刷新,需重新生成路由}
}
new Vue({router,store,render: h => h(App)
}).$mount('#app')

拓展

这时候差不多就大功告成了,只需将数据渲染到el-menu即可。

1.后台控制权限

当前的路由鉴权基本上由前端控制,后端只需返回平台标识和角色。但实际开发时,肯定都是通过后台控制,菜单角色等信息需要建表入库。来修改栏目名称,一级栏目icon,菜单权限等
我们可以在getValidRoutes时获取一张权限表,将这些数据插入到dynamicRoutes中。后端返回的数据大致如下:

[{id: 1,name: '用户管理',icon: 'el-icon-user-solid',children: [{id: 3,name: '用户列表',meta: {roles: [1, 2]},},{id: 4,path: 'detail',name: '用户详情',meta: {roles: [1]},},]},{id: 2,name: 'IP管理',icon: 'el-icon-s-promotion',children: [{id: 5,name: 'IP列表',meta: {roles: [1, 2, 3]},}, ]},
]

2.安全性方面

前端:

  1. 跨平台进入路由,直接跳到该平台登录页。
  2. 当前平台访问没有权限的页面报404错误。

后端:

  1. 一定要保证相应平台的token只能调对应接口,否则报错。
  2. 如果能做到角色接口鉴权就更好了,从接口层面拒绝请求

3.axios封装

在请求拦截器中根据用户信息拿不同的token,设置头部信息
在响应拦截器中,如果token过期,再根据用户信息跳转到不同的登录页

4.api管理

如果后端也是一套代码。那api也可以这样进行管理,但如果没有一个统一的前缀。可以在axios设置一个统一的前缀例如proxy,这样就解决了跨域的问题。

const USER = 'api'
const AGENT = 'agent'
const ADMIN = 'admin'
export default {USER: {login: `${USER}/User/login`,},AGENT: {login: `${AGENT}/User/login`,uploadFile: `${AGENT}/Utils/uploadFile`,},ADMIN: {login: `${ADMIN}/User/login`,},
}
devServer: {proxy: {'/proxy': {target: 'http://localhost:8848',changeOrigin: true,pathRewrite: {'^proxy': ''  //将url中的proxy子串去掉}}}},

vue从零搭建一个前中后台权限管理模板相关推荐

  1. IT:前端进阶技术路线图(初级→中级→高级)之初级(研发工具/HTML/CSS/JS/浏览器)/中级(研发链路/工程化/库/框架/性能优化/工作原理)/高级(搭建/中后台/体验管理等)之详细攻略

    IT:前端进阶技术路线图(初级→中级→高级)之初级(研发工具/HTML/CSS/JS/浏览器)/中级(研发链路/工程化/库/框架/性能优化/工作原理)/高级(搭建/Node/IDE/中后台/体验管理/ ...

  2. go html vue,用Go+Vue.js快速搭建一个Web应用(初级demo)

    Vue.js做为目前前端最热门的库之一,为快速构建并开发前端项目多了一种思维模式.本文给大家介绍用Go+Vue.js快速搭建一个Web应用(初级demo). 环境准备: 1. 安装go语言,配置go开 ...

  3. 深度学习笔记:利用numpy从零搭建一个神经网络

    很多人说深度学习就是个黑箱子,把图像预处理之后丢进 tensorflow 就能出来预测结果,简单有效又省时省力.但正如我在上一篇推送中所说,如果你已是一名功力纯厚的深度学习工程师,这么做当然没问题.但 ...

  4. 【华为云技术分享】从零搭建一个灰度发布环境

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师. 官方网站:devui.design Ng组件库:ng-devui(欢 ...

  5. 从零搭建一个 Spring Boot 开发环境!Spring Boot+Mybatis+Swagger2 环境搭建

    从零搭建一个 Spring Boot 开发环境!Spring Boot+Mybatis+Swagger2 环境搭建 本文简介 为什么使用Spring Boot 搭建怎样一个环境 开发环境 导入快速启动 ...

  6. [渝粤教育] 宁波城市职业技术学院 Web服务器运维(从零搭建一个企业网站) 参考 资料

    教育 -Web服务器运维(从零搭建一个企业网站)-章节资料考试资料-宁波城市职业技术学院[] 作业:购买阿里云ECS 作业:在万网注册域名 作业:ICP备案 微测验:准备主机 1.[单选题]ECS是阿 ...

  7. 自己搭服务器 做购物网站成本,从零搭建一个购物网站,实操经验

    对于很多不懂代码不懂技术的人来说,想要搭建自己的购物网站非常的困难.难道不懂计算机基础,不会写代码就真的不能进行购物网站开发了吗?事实上并非如此.接下来HiShop小编就跟大家分享一下,如何从零搭建一 ...

  8. 对中国商业银行前中后台分离的反思

    前中后台分离是建设流程银行的核心问题之一.前中后台分离固然给中国商业银行带来很多思想.组织.流程.行为和文化上的保障和转变,但是,在实现这一目标的过程中也产生一些问题. 为适应向零售银行转型,我国商业 ...

  9. 如何从零搭建一个hexo博客网站01

    title: 如何从零搭建一个hexo博客网站01 #文章標題 categories: "Hexo教程" #文章分類目錄 可以省略 categories: "Hexo教程 ...

  10. 从零搭建一个基于React+Nextjs的SSR网站(四):如何搭建服务器并部署Nextjs项目

    个人博客源码:https://github.com/shaotianyu/blog-front PS: 如果你有疑惑,可以给我留言,咱们一起解决它. 从零搭建一个基于React+Nextjs的SSR网 ...

最新文章

  1. 2022-2028年中国文化产业园投资分析及前景预测报告(全卷)
  2. 套接字选项SO_KEEPALIVE
  3. 笔记-高项案例题-2013年下-计算题
  4. zblog php版调用代码,zblog PHP分类列表调用方法
  5. java 处理表单_java – 处理multipart / form-data输入
  6. CSS绘制放大缩小关闭按钮
  7. 学习Numpy,看这篇文章就够啦
  8. python io操作有什么_Python文件IO操作
  9. Mosquito的优化——订阅树优化(八)
  10. 许家印大笔押注新能源:恒大集团1200亿沈阳投资建厂造车
  11. 阿里云蒋江伟:什么是真正的云原生?
  12. OC基础--OC中类的定义
  13. 邓白氏编码申请经验分享
  14. Java、某天是星期几
  15. 空间和时间 ----节选《时间简史》 霍金
  16. 浮窗---创建Activity浮窗(可拖动)
  17. 狄拉克函数- dirac 分布
  18. JACK——TeamsMaual6 Team Formation
  19. Keychron Q1:客制化机械键盘|体验
  20. 2022.08.12 第三组 高小涵

热门文章

  1. 0x00F749F6 处(位于 基于多态实现职工管理系统.exe 中)引发的异常: 0xC0000005: 读取位置 0x00000004 时发生访问冲突。
  2. ntdll.dll处引发的异常: 0xC0000005: 写入位置 0x00000004 时发生访问冲突
  3. 输入下载页面链接自动获取ipa下载地址,支持本地下载,支持蒲公英和fir及绝大多数自定义下载页
  4. Pycharm使用技巧:Split Vertically/Horizontally(垂直/水平拆分窗口)
  5. java 主动释放_java的手动释放资源
  6. 解读神经网络十大误解,再也不会弄错它的工作原理
  7. 计算机提示资源管理器停止,电脑开机黑屏并弹出Windows 资源管理器已停止工作该怎么办?...
  8. 手机卫星定位系统_如何判断自己的手机是否支持北斗卫星定位功能,如何使用?...
  9. 为何quot;矮矬穷quot;出身的成功…
  10. C -- OC with RunTime