1、前端访问控制的常规处理方法

  前端访问控制,一般针对界面元素dom element进行可见属性或enable属性进行控制,有权限的,相关元素可见或使能;没权限的,相关元素不可见或失能。这样用户可以明确哪些是无权访问的。可见属性要比使能属性更广泛,这是每个dom元素都有的属性。

  当然前端控制仅仅是整体访问控制的一部分,后端还需要进一步针对接口访问进行鉴权。因为通过编辑浏览器的界面元素的属性,可以绕过前端控制。

  在Vue中,也有通过控制路由来实现访问控制的,但没有控制界面元素的情况下,用户体验不是很好。

  本文给出了Vue框架下前端访问控制的整体方案。

2、总体方案

  在用户登录时,或权限变更时,后端通过接口将权限树发给前端。为了减少不必要的数据传输,后端发出的权限树仅包括有权限的功能项,即前端收到的权限树的各个节点都是有权限的功能项。

  权限树节点的数据部分即为功能项的权限信息,包括两个关键字段:url和domKey。url是后端自己使用,在AOP鉴权切面类中,拦截非法的接口访问。domKey是给前端使用的,即dom element的id值,domKey的确定需要前后端协商一致,不能搞错。

  domKey在同一个路径上,不允许重复;不同路径,允许重复。所谓路径,是从根节点开始,到该节点的一系列节点组成的树杈。当然,没有必要的话,domKey最好不重复。同一个界面视图范围的各子节点的domKey也不允许重复。

  前端本地存储用户token和权限树JSON字符串,如果本地这个存储信息存在,重新打开浏览器,可以免登录。(仅本地token有效,不能完全保证token真的有效,如后端重启服务器、token过期等导致token失效,前端通过HTTP访问时,仍然会跳到登录页面)。

  登录成功后,将token和权限树JSON字符串保存到本地存储。

  权限发生变更时,通过response拦截器,检查有无附加信息,如有需要,更新token和权限树JSON字符串。

  前端开发一个权限树的管理的js文件,用于权限树JSON对象的访问,权限树JSON字符串被转换成权限树JSON对象。

  开发前端页面vue文件时,需要进行权限控制的dom element,使用下列属性:

  1. class="permissions" id="相关domKey"

  通过class来标识该界面元素是与访问控制相关的,目的是确定需要进行权限控制的组件范围,id即为该功能项对应的domKey。

  然后,使用一个公共权限设置方法,来统一处理权限相关的界面元素。

  由于Vue的组件style,可以有scoped属性设置,此时,在App.vue中,就不能访问到相关dom element的class,局部式样渲染后,在外部被改写,因此,在scoped限制的情况下,需要在scoped起作用的Vue组件中,也要调用公共权限设置方法。另外,scoped的限制,恰好使得相同domKey的节点,可以通过上级节点domKey来加以区分。这样,就用统一的方法,实现了前端页码的访问控制。

3、方案实现

3.1、功能项的表结构定义

  1. DROP TABLE IF EXISTS `function_tree`;
  2. CREATE TABLE `function_tree`
  3. (
  4. `func_id` INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID',
  5. `func_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名称',
  6. `parent_id` INT(11) NOT NULL DEFAULT 0 COMMENT '父功能ID',
  7. `level` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '功能所在层级',
  8. `order_no` INT(11) NOT NULL DEFAULT 0 COMMENT '显示顺序',
  9. `url` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '访问接口url',
  10. `dom_key` VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom对象的id',
  11. `remark` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注',
  12. -- 记录操作信息
  13. `operator_name` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人账号',
  14. `delete_flag` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '记录删除标记,1-已删除',
  15. `create_time` DATETIME(3) NOT NULL DEFAULT NOW(3) COMMENT '创建时间',
  16. `update_time` DATETIME(3) DEFAULT NULL ON UPDATE NOW(3) COMMENT '更新时间',
  17. PRIMARY KEY (`func_id`)
  18. ) ENGINE = InnoDB
  19. DEFAULT CHARSET = utf8 COMMENT ='功能表';

  如有需要,可以增加icon字段,用于前端树节点的显示。

3.2、后端权限树的输出

  后端在登录成功后,给前端发送token和权限树JSON字符串。

  关于树节点的生成,可参阅:Java通用树结构数据管理---https://www.cnblogs.com/alabo1999/p/14928380.html。里面有关于权限树的例子。

  为了方便前端管理,这里修改权限树的输出,将根节点也一并输出到前端。

  在管理员修改用户权限后,动态权限更新,可通过附加信息,给前端发送token和权限树JSON字符串。参阅:Spring Boot动态权限变更实现的整体方案---https://www.cnblogs.com/alabo1999/p/14948914.html。

3.3、前端本地缓存

  vue项目中,新建/src/store目录,创建inde.js文件。代码如下:

  1. import Vue from 'vue';
  2. import Vuex from 'vuex';
  3. Vue.use(Vuex);
  4. const store = new Vuex.Store({
  5. state: {
  6. // 存储token
  7. token: localStorage.getItem('token') ? localStorage.getItem('token') : '',
  8. // 存储权限树
  9. rights: localStorage.getItem('rights') ? localStorage.getItem('rights') : ''
  10. },
  11. mutations: {
  12. // 修改token,并将token存入localStorage
  13. changeLogin (state, user) {
  14. if(user.token){
  15. state.token = user.token;
  16. localStorage.setItem('token', user.token);
  17. }
  18. if (user.rights){
  19. state.rights = user.rights;
  20. localStorage.setItem('rights', user.rights);
  21. }
  22. }
  23. }
  24. });
  25. export default store;

3.4、创建权限管理模块

  vue项目中,新建/src/common目录,创建treeNode.js文件。代码如下:

  1. /**
  2. * 处理树结构数据,这里主要指功能权限树
  3. * 权限树的结构如下:
  4. * [
  5. * {
  6. * nodeData:{
  7. * funcId:1, //功能ID
  8. * funcName:"", //功能名称
  9. * parentId:0, //父节点ID
  10. * level:1, //功能所在层级
  11. * orderNo:2, //显示顺序
  12. * url:"", //访问接口url
  13. * domKey:"" //dom对象的id
  14. * },
  15. * children:[
  16. * nodeData:{...},
  17. * children:[...]
  18. * ]
  19. * },
  20. * {
  21. * nodeData:{...},
  22. * children:[...]
  23. * }
  24. * ]
  25. */
  26. var TreeNode = {
  27. //功能树
  28. rightsTree:null,
  29. /**
  30. * 将权限树的JSON字符串加载到树对象上
  31. * @param {权限树的JSON字符串} rights
  32. */
  33. loadData(rights){
  34. //将缓存的JSON字符串,转为JSON对象,为一级树节点的数组
  35. var treeNode = JSON.parse(rights);
  36. return treeNode;
  37. },
  38. /**
  39. * 在给定树上,找到上级domkey为superDomkey的给定domKey的树节点
  40. * 不同子树如果存在子节点domKey重复的情况,也可以区分
  41. * @param {给定树节点} rightsTree
  42. * @param {上级的domkey} superDomkey
  43. * @param {树节点的domkey} domKey
  44. */
  45. lookupNodeByDomkeys(rightsTree,superDomkey,domKey){
  46. var node = null;
  47. var superNode = null;
  48. //先寻找superDomkey
  49. if(superDomkey != ""){
  50. //如果上级对象的domkey非空
  51. superNode = this.lookupNodeByDomkey(rightsTree,superDomkey);
  52. }
  53. if (superNode != null){
  54. //如果上级节点非空,或已找到,则在子树上搜索,可加快搜索速度,并且可避免子节点domKey重复的情况
  55. node = this.lookupNodeByDomkey(superNode,domKey);
  56. }else{
  57. node = this.lookupNodeByDomkey(rightsTree,domKey);
  58. }
  59. return node;
  60. },
  61. /**
  62. * 在给定的子树中,搜索指定domKey的树节点
  63. * @param {子树} rightsTree
  64. * @param {domkey} domKey
  65. */
  66. lookupNodeByDomkey(rightsTree,domKey){
  67. var node = null;
  68. var functionInfo = rightsTree.nodeData;
  69. //先查找自身的数据
  70. if (functionInfo.domKey == domKey){
  71. //如果找到,则返回
  72. return rightsTree;
  73. }
  74. //搜索子节点
  75. for (var i = 0; i < rightsTree.children.length; i++){
  76. var item = rightsTree.children[i];
  77. node = this.lookupNodeByDomkey(item,domKey);
  78. if (node != null){
  79. break;
  80. }
  81. }
  82. return node;
  83. }
  84. }
  85. export default TreeNode;

  如果domKey确保唯一的话,使用Map可能是访问效率更高的方案。这里还是使用树型结构来管理权限树。

3.5、创建公共方法模块

  vue项目中,在/src/common目录下,创建commonFuncs.js文件。代码如下:

  1. import TreeNode from './treeNode.js'
  2. var commonFuncs = {
  3. checkRights(superDomkey){
  4. //先加载权限树
  5. if (TreeNode.rightsTree == null){
  6. let rights = localStorage.getItem('rights');
  7. if (rights === null || rights === ''){
  8. //没有权限树
  9. return;
  10. }
  11. //加载权限树
  12. TreeNode.rightsTree = TreeNode.loadData(rights);
  13. }
  14. //获取class包含permissions的所有dom对象
  15. var elements = document.getElementsByClassName('permissions');
  16. for(var i = 0; i < elements.length; i++){
  17. var element = elements[i];
  18. if (element.id != undefined)
  19. {
  20. var node = null;
  21. //如果对象有id,检查权限
  22. if (superDomkey == null || superDomkey == undefined){
  23. //如果未指定上级domkey,直接查找
  24. node = TreeNode.lookupNodeByDomkey(TreeNode.rightsTree,element.id);
  25. }else{
  26. //指定上级domkey
  27. node = TreeNode.lookupNodeByDomkeys(TreeNode.rightsTree,superDomkey,element.id)
  28. }
  29. if (node != null && node != undefined){
  30. //包含节点
  31. if (element.style.display == "none"){
  32. element.style.display = "";
  33. }
  34. console.log('has rights :'+element.id);
  35. }else{
  36. element.style.display="none";
  37. console.log('has not rights :'+element.id);
  38. }
  39. }
  40. }
  41. }
  42. };
  43. export default commonFuncs;

  checkRights方法,参数为superDomkey,即指定上级节点的domKey,允许为空或空串,相当于不指定。其查找当前页面或scoped范围的文档中,class名称包含permissions的所有dom元素。取得dom的id,即功能节点的domKey,如果在权限树中存在对应节点,则表示有权限;否则表示无权限。(注意:前端的权限树都是有权限的功能节点)。

3.6、修改main.js

  修改main.js文件,使得公共模块生效。代码如下:

  1. // The Vue build version to load with the `import` command
  2. // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
  3. import Vue from 'vue'
  4. import App from './App'
  5. import router from './router'
  6. import store from './store'
  7. import ElementUI from 'element-ui'
  8. import 'element-ui/lib/theme-chalk/index.css'
  9. import md5 from 'js-md5';
  10. import axios from 'axios'
  11. import VueAxios from 'vue-axios'
  12. import TreeNode_ from './common/treeNode.js'
  13. import CommonFuncs_ from './common/commonFuncs.js'
  14. import instance_ from './api/index.js'
  15. import global_ from '../config/global.js'
  16. Vue.use(VueAxios,axios)
  17. Vue.prototype.$md5 = md5
  18. Vue.prototype.TreeNode = TreeNode_
  19. Vue.prototype.$baseUrl = process.env.API_ROOT
  20. Vue.prototype.instance = instance_ //axios实例
  21. Vue.prototype.global = global_
  22. Vue.prototype.commonFuncs = CommonFuncs_
  23. Vue.use(ElementUI)
  24. Vue.config.productionTip = false
  25. /* eslint-disable no-new */
  26. var vue = new Vue({
  27. el: '#app',
  28. router,
  29. store,
  30. components: { App },
  31. template: '<App/>',
  32. render:h=>h(App)
  33. })
  34. export default vue

  引入了commonFuncs和TreeNode全局对象,可以在vue文件中使用。

3.7、组件示例

  侧边导航栏,与权限控制相关,可以作为示例。文件为Left.vue,代码如下:

  1. <template>
  2. <div class="left-sidebar">
  3. <el-menu :default-openeds="['1']" style="background:#F0F6F6;">
  4. <el-submenu index="1">
  5. <el-menu-item-group >
  6. <el-menu-item index="1-1">
  7. <router-link class="menu" tag="li" to="/home" exact-active-class="true"
  8. id="homeMenu" active-class="_active">
  9. <i class="el-icon-s-home"></i>首页
  10. </router-link>
  11. </el-menu-item>
  12. <el-submenu index="1-2" id="userManagementMain">
  13. <template slot="title" ><i class="el-icon-user-solid"></i>用户管理</template>
  14. <el-menu-item index="1-2-1" class="permissions" id="userManagementSub">
  15. <router-link class="menu" tag="li" to="/userManagement">
  16. <i class="el-icon-user"></i>用户管理
  17. </router-link>
  18. </el-menu-item>
  19. <el-menu-item index="1-2-2" class="permissions" id="changePassword">
  20. <router-link class="menu"tag="li" to="/changePassword">
  21. <i class="el-icon-key"></i>修改密码
  22. </router-link>
  23. </el-menu-item>
  24. </el-submenu>
  25. <el-menu-item index="1-3" class="permissions" id="questionnaireManagement">
  26. <router-link class="menu" tag="li" to="/questionnaireManagement">
  27. <i class="el-icon-document"></i>问卷内容管理
  28. </router-link>
  29. </el-menu-item>
  30. <el-submenu index="1-4" class="permissions" id="issueManagementMain">
  31. <template slot="title"><i class="el-icon-message"></i>问卷发布管理</template>
  32. <el-menu-item index="1-4-1" class="permissions" id="issueManagementSub">
  33. <router-link class="menu" tag="li" to="/issueManagement">
  34. <i class="el-icon-phone"></i>发布问卷查询
  35. </router-link>
  36. </el-menu-item>
  37. <el-menu-item index="1-4-2" class="permissions" id="issueTaskQuery">
  38. <router-link class="menu" tag="li" to="/issueTaskQuery">
  39. <i class="el-icon-tickets"></i>发布任务查询
  40. </router-link>
  41. </el-menu-item>
  42. </el-submenu>
  43. <el-menu-item index="1-5" class="permissions" id="answerSheetManagement">
  44. <router-link class="menu" tag="li" to="/answerSheetManagement">
  45. <i class="el-icon-receiving"></i>答卷管理
  46. </router-link>
  47. </el-menu-item>
  48. </el-menu-item-group>
  49. </el-submenu>
  50. </el-menu>
  51. </div>
  52. </template>
  53. <style>
  54. /* 去掉右边框 */
  55. .el-menu {
  56. border-right: none;
  57. }
  58. .el-submenu {
  59. background-color: rgb(231, 235, 220) ;
  60. }
  61. </style>

  注意那些:class="permissions" id=“XXX”的dom元素,基本都是el-menu-item。这里,将scoped去掉了,因为菜单项,目前只有侧边导航栏在使用。

3.7、修改App.vue

  App.vue,作为应用页面组件的总成,在里面进行总的权限控制。代码如下:

  1. <template>
  2. <div id="app">
  3. <!-- 其他页 -->
  4. <el-container style="min-height: calc(100% - 50px);" v-if="$route.meta.keepAlive">
  5. <!-- 无头部导航栏 -->
  6. <el-container>
  7. <el-aside :style="{width:collpaseWidth}">
  8. <!-- 侧边栏 -->
  9. <keep-alive>
  10. <left></left>
  11. </keep-alive>
  12. </el-aside>
  13. <el-main>
  14. <!-- Body -->
  15. <router-view></router-view>
  16. </el-main>
  17. </el-container>
  18. <!-- 无足部 -->
  19. </el-container>
  20. <!-- 登录页 -->
  21. <router-view v-if="!$route.meta.keepAlive"></router-view>
  22. </div>
  23. </template>
  24. <script>
  25. import left from './components/Left.vue'
  26. export default {
  27. name: 'App',
  28. components: {
  29. left: left
  30. },
  31. data(){
  32. return {
  33. collpaseWidth:200
  34. }
  35. },
  36. mounted:function(){
  37. this.commonFuncs.checkRights();
  38. },
  39. methods: {
  40. }
  41. }
  42. </script>
  43. <style>
  44. #app {
  45. font-family: 'Avenir', Helvetica, Arial, sans-serif;
  46. -webkit-font-smoothing: antialiased;
  47. -moz-osx-font-smoothing: grayscale;
  48. text-align: center;
  49. color: #2c3e50;
  50. margin-top: 60px;
  51. }
  52. </style>

  在页码加载时,调用commonFuncs.checkRights()方法,进行权限控制。

3.8、测试一下

3.8.1、获取权限树数据

  登录成功后,后端输出的权限树数据如下:

  1. {
  2. rights = {
  3. "nodeData": {
  4. "funcId": 0,
  5. "funcName": "root",
  6. "parentId": -1,
  7. "level": 0,
  8. "orderNo": 0,
  9. "url": "",
  10. "domKey": ""
  11. },
  12. "children": [{
  13. "nodeData": {
  14. "funcId": 1,
  15. "funcName": "用户管理一级菜单",
  16. "parentId": 0,
  17. "level": 1,
  18. "orderNo": 0,
  19. "url": "",
  20. "domKey": "userManagementMain"
  21. },
  22. "children": [{
  23. "nodeData": {
  24. "funcId": 3,
  25. "funcName": "修改密码",
  26. "parentId": 1,
  27. "level": 2,
  28. "orderNo": 1,
  29. "url": "/userMan/changePassword",
  30. "domKey": "changePassword"
  31. },
  32. "children": []
  33. }]
  34. }, {
  35. "nodeData": {
  36. "funcId": 10,
  37. "funcName": "问卷内容管理一级菜单",
  38. "parentId": 0,
  39. "level": 1,
  40. "orderNo": 1,
  41. "url": "",
  42. "domKey": "questionnaireManagement"
  43. },
  44. "children": [{
  45. "nodeData": {
  46. "funcId": 11,
  47. "funcName": "新增问卷",
  48. "parentId": 10,
  49. "level": 2,
  50. "orderNo": 0,
  51. "url": "/questionnaireMan/addQuestionnaire",
  52. "domKey": "addQuestionnaire"
  53. },
  54. "children": []
  55. }, {
  56. "nodeData": {
  57. "funcId": 12,
  58. "funcName": "编辑问卷",
  59. "parentId": 10,
  60. "level": 2,
  61. "orderNo": 1,
  62. "url": "/questionnaireMan/editQuestionnaire",
  63. "domKey": "editQuestionnaire"
  64. },
  65. "children": []
  66. }, {
  67. "nodeData": {
  68. "funcId": 13,
  69. "funcName": "查询问卷",
  70. "parentId": 10,
  71. "level": 2,
  72. "orderNo": 2,
  73. "url": "/questionnaireMan/queryQuestionnaires",
  74. "domKey": "queryQuestionnaire"
  75. },
  76. "children": []
  77. }, {
  78. "nodeData": {
  79. "funcId": 14,
  80. "funcName": "复制新建问卷",
  81. "parentId": 10,
  82. "level": 2,
  83. "orderNo": 3,
  84. "url": "",
  85. "domKey": "copyAddQuestionnaire"
  86. },
  87. "children": []
  88. }, {
  89. "nodeData": {
  90. "funcId": 15,
  91. "funcName": "浏览问卷",
  92. "parentId": 10,
  93. "level": 2,
  94. "orderNo": 4,
  95. "url": "/questionnaireMan/previewQuestionnaire",
  96. "domKey": "browseQuestionnaire"
  97. },
  98. "children": []
  99. }, {
  100. "nodeData": {
  101. "funcId": 16,
  102. "funcName": "提交审核",
  103. "parentId": 10,
  104. "level": 2,
  105. "orderNo": 5,
  106. "url": "/questionnaireMan/submitAduit",
  107. "domKey": "submitAudit"
  108. },
  109. "children": []
  110. }, {
  111. "nodeData": {
  112. "funcId": 18,
  113. "funcName": "作废问卷",
  114. "parentId": 10,
  115. "level": 2,
  116. "orderNo": 7,
  117. "url": "/questionnaireMan/cancelQuestionnaire",
  118. "domKey": "cancelQuestionnaire"
  119. },
  120. "children": []
  121. }]
  122. }, {
  123. "nodeData": {
  124. "funcId": 20,
  125. "funcName": "问卷发布管理一级菜单",
  126. "parentId": 0,
  127. "level": 1,
  128. "orderNo": 2,
  129. "url": "",
  130. "domKey": "issueManagementMain"
  131. },
  132. "children": [{
  133. "nodeData": {
  134. "funcId": 21,
  135. "funcName": "发布管理二级菜单",
  136. "parentId": 20,
  137. "level": 2,
  138. "orderNo": 0,
  139. "url": "",
  140. "domKey": "issueManagementSub"
  141. },
  142. "children": []
  143. }, {
  144. "nodeData": {
  145. "funcId": 22,
  146. "funcName": "发布任务查询",
  147. "parentId": 20,
  148. "level": 2,
  149. "orderNo": 1,
  150. "url": "",
  151. "domKey": "issueTaskQuery"
  152. },
  153. "children": []
  154. }]
  155. }, {
  156. "nodeData": {
  157. "funcId": 40,
  158. "funcName": "答卷管理一级菜单",
  159. "parentId": 0,
  160. "level": 1,
  161. "orderNo": 3,
  162. "url": "",
  163. "domKey": "answerSheetManagement"
  164. },
  165. "children": [{
  166. "nodeData": {
  167. "funcId": 41,
  168. "funcName": "查询答卷记录",
  169. "parentId": 40,
  170. "level": 2,
  171. "orderNo": 0,
  172. "url": "/answerSheetMan/queryAnswerTask",
  173. "domKey": "queryAnswerSheet"
  174. },
  175. "children": []
  176. }, {
  177. "nodeData": {
  178. "funcId": 42,
  179. "funcName": "回收记录明细",
  180. "parentId": 40,
  181. "level": 2,
  182. "orderNo": 1,
  183. "url": "/answerSheetMan/getAnswerSubmitDetail",
  184. "domKey": "recoveryDetail"
  185. },
  186. "children": []
  187. }, {
  188. "nodeData": {
  189. "funcId": 43,
  190. "funcName": "答卷统计",
  191. "parentId": 40,
  192. "level": 2,
  193. "orderNo": 2,
  194. "url": "/answerSheetMan/queryStatResult",
  195. "domKey": "answerSheetStat"
  196. },
  197. "children": []
  198. }, {
  199. "nodeData": {
  200. "funcId": 44,
  201. "funcName": "答卷原始记录",
  202. "parentId": 40,
  203. "level": 2,
  204. "orderNo": 3,
  205. "url": "/answerSheetMan/queryOriginalAnswer",
  206. "domKey": "queryOriginalAnswer"
  207. },
  208. "children": []
  209. }]
  210. }]
  211. }, token = 873820BA39E64005BCCE3E54A830AB2C
  212. }

  这些功能项中,有些与导航栏有关,还有一些是页面的按钮或链接,在示例中没有用到。

3.8.2、制作首页

  制作一个简单的首页Home.vue,代码如下:

  1. <template>
  2. <div id="home">
  3. <h4>欢迎使用</h4>
  4. <h3>XX系统</h3>
  5. </div>
  6. </template>

3.8.3、简单设置路由导航文件

  修改/src/router/index.js文件,代码如下:

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. import HelloWorld from '@/components/HelloWorld'
  4. import Home from '@/components/Home.vue'
  5. import Login from '@/components/login/Login.vue'
  6. Vue.use(Router)
  7. const router = new Router({
  8. routes: [
  9. {
  10. path: '/home',
  11. name: 'home',
  12. component: Home,
  13. meta: {
  14. keepAlive: true
  15. }
  16. },
  17. {
  18. path: '/login',
  19. name: 'login',
  20. component: Login,
  21. meta: {
  22. keepAlive: false
  23. }
  24. },
  25. ]
  26. })
  27. // 导航守卫
  28. // 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
  29. router.beforeEach((to, from, next) => {
  30. if (to.path === '/login') {
  31. next();
  32. } else {
  33. let token = localStorage.getItem('token');
  34. if (token === null || token === '') {
  35. next('/login');
  36. } else {
  37. if (to.path === '/'){
  38. next('/home');
  39. }else{
  40. next();
  41. }
  42. }
  43. }
  44. });
  45. export default router;

3.8.4、导航栏效果测试

  现在运行Vue,"npm run dev",然后显示首页,并用F12显示调式信息:

  侧边栏页面显示如下:

  浏览器的调试器的控制台输出信息为:

  说明,domKey为userMangementSub的dom元素没有操作权限,与侧边栏的效果一致。

3.8.5、scoped情况测试

  Login.vue,使用了scoped,作为示例,现在将登录按钮,进行权限控制,修改如下:

  1. <el-form-item>
  2. <el-button type="primary" class="permissions" id="login" style="width:160px" @click="submitForm('form')">登录</el-button>
  3. </el-form-item>

  在Login.vue的script的mounted方法中,增加权限控制代码:

  1. mounted:function(){
  2. //页面加载时,显示验证码
  3. this.getVerifyCode();
  4. this.commonFuncs.checkRights();
  5. },

  由于domKey为login的,没有在权限树中,故其加入权限控制集合,又没有被授权,则该按钮应该不可见。

  运行测试,显示登录页,效果图如下:

  登录按钮不可见了,与预期效果一致。

3.9、登录成功后保存信息

  登录成功后,将后端发生过来的token和权限树保存起来,并将JSON字符串转为JSON对象。

  代码如下:

  1. submitForm(formName) {
  2. let _this = this;
  3. this.$refs[formName].validate(valid => {
  4. // 验证通过为true,有一个不通过就是false
  5. if (valid) {
  6. // 通过的逻辑
  7. let passwd = this.$md5(this.form.password);
  8. this.instance.userLogin(this.$baseUrl,{
  9. loginName:_this.form.username,
  10. password:passwd,
  11. verifyCode:_this.form.verifyCode
  12. }).then(res => {
  13. console.log(res.data);
  14. if (res.data.code == this.global.SucessRequstCode){
  15. //如果登录成功
  16. _this.userToken = res.data.data.token;
  17. _this.rights = res.data.data.rights;
  18. //更新权限树
  19. this.TreeNode.rightsTee = this.TreeNode.loadData(_this.rights);
  20. console.log(this.TreeNode.rightsTee)
  21. // 将用户token和权限树保存到vuex中
  22. _this.changeLogin({ token: _this.userToken, rights: _this.rights});
  23. _this.$router.push('/home');
  24. //alert('登陆成功');
  25. }else{
  26. alert(res.data.message);
  27. }
  28. }).catch(error => {
  29. alert('账号或密码错误');
  30. console.log(error);
  31. });
  32. } else {
  33. console.log('验证失败');
  34. return false;
  35. }
  36. });
  37. },

3.10、权限动态更新的拦截处理

  根据权限动态更新方案,管理员修改用户权限后,该用户第一次访问后端接口,返回信息中可能会携带附加信息。这个可能在任何返回JSON格式数据的接口中发生。因此,可使用拦截器,来进行统一处理。

  1. import axios from 'axios';
  2. import router from '../router'
  3. import Vue from 'vue';
  4. import Vuex from 'vuex';
  5. import TreeNode from '../common/treeNode.js'
  6. const instance = axios.create({
  7. timeout: 60000,
  8. headers: {
  9. 'Content-Type': "application/json;charset=utf-8"
  10. }
  11. });
  12. //token相关的response拦截器
  13. instance.interceptors.response.use(response => {
  14. if (response) {
  15. switch (response.data.code) {
  16. case 3: //token为空
  17. case 4: //token过期
  18. case 5: //token不正确
  19. localStorage.clear(); //删除用户信息
  20. //要跳转登陆页
  21. alert('token失效,请重新登录!');
  22. router.replace({
  23. path: '/login',
  24. });
  25. break;
  26. default:
  27. break;
  28. }
  29. if(response.data.additional){
  30. //如果包含附加信息
  31. var data = {};
  32. if(response.data.additional.token){
  33. //如果包含token
  34. data.token = response.data.additional.token;
  35. localStorage.setItem('token', data.token);
  36. }
  37. if(response.data.additional.rights) {
  38. data.rights = response.data.additional.rights;
  39. localStorage.setItem('rights', data.rights);
  40. //刷新权限树
  41. TreeNode.rightsTree = TreeNode.loadData(data.rights);
  42. }
  43. }
  44. }
  45. return response;
  46. }, error => {
  47. return Promise.reject(error.response.data.message) //返回接口返回的错误信息
  48. })

Vue前端访问控制方案的更多相关文章

  1. Vue 前端权限控制的优化改进版

    1.前言   之前<Vue前端访问控制方案 >一文中提出,使用class="permissions"结合元素id来标识权限控制相关的dom元素,并通过公共方法check ...

  2. vue前端开发那些事——后端接口.net core web api

    红花还得绿叶陪衬.vue前端开发离不开数据,这数据正来源于请求web api.为什么采用.net core web api呢?因为考虑到跨平台部署的问题.即使眼下部署到window平台,那以后也可以部 ...

  3. Vue 前端配置多级目录实践(基于Nginx配置方式)

    前情提要 有阵子没更新博客了,因为快年结了工作比较多,这不,最近公司的对外演示环境出现问题这个活儿也落到了我的头上-- 事情是这样的,原来演示环境有很多服务,每个服务都是对外单独开一个端口,比如 ht ...

  4. vue 前端框架 (三)

    VUE 生命周期 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> < ...

  5. vue 前端框架 目录

    vue 前端框架 目录   vue-目录 ES6基础语法 vue基础语法 Vue.js的组件化思想 —上 Vue.js的组件化思想 —下 Vue + Vue-Router结合开发 SublimeSer ...

  6. vue前端+java后端 vue + vuex + koa2开发环境搭建及示例开发

    vue + vuex + koa2开发环境搭建及示例开发 https://segmentfault.com/a/1190000012918518 vue前端+java后端 https://blog.c ...

  7. 基于vue+springboot+docker网站搭建【五】部署vue前端项目

    部署vue前端项目  一.下载项目到本地   https://github.com/macrozheng/mall-admin-web 二.npm install 三.修改api配置,改为你接下来要部 ...

  8. vue前端实战注意事项

    1. vue前端实战注意事项 1.1. 预备 1.1.1. Eslint 这是个语法检查工具,我用webstorm作为开发的ide,这个语法检查还是太严格了,一个空格啥的都会报错,对新手来讲还是建议关 ...

  9. beego-vue URL重定向(beego和vue前后端分离开发,beego承载vue前端分离页面部署)

    具体过程就不说,是搞这个的自然会动,只把关键代码贴出来. beego和vue前后端分离开发,beego承载vue前端分离页面部署 // landv.cnblogs.com //没有授权转载我的内容,再 ...

随机推荐

  1. java并发编程:深入了解synchronized

    简介 synchronized是Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.同时它还保证了共享变量的内存可见性. ...

  2. C#·对于BOM头之完全解决方案

    阅文时长 | 0.46分钟 字数统计 | 798.4字符 主要内容 | 1.引言&背景 2.使用C#写入带有/不带有BOM头的文件? 3.对于读取文件时,避免BOM头造成的异常. 4.声明与参 ...

  3. CentOS 8 配置 VNC Server

    CentOS 8 配置 VNC Server 2020-12-31 | 标签: centos, vnc 前言 CentOS 8 配置 VNC Server, 使用户可以远程访问,本例介绍安装和配置流程 ...

  4. centos下查看网卡,主板,CPU,显卡,硬盘型号等硬件信息

    centos下查看网卡,主板,CPU,显卡,硬盘型号等硬件信息 rose_willow rose_willow 发布于 2016/06/16 11:32 字数 902 阅读 405 收藏 0 点赞 0 ...

  5. make: g77: Command not found 修改Makefile.in中的编译文件中的g77为gfortran

    make: g77: Command not found 编译cblas时报错,这时,修改Makefile.in中的编译文件中的g77为gfortran

  6. CentOS Linux搭建独立SVN Server全套流程(修改svn仓库地址、服务启动等)

    CentOS Linux搭建独立SVN Server全套流程(修改svn仓库地址.服务启动等) 原 一事能狂便少年 发布于 2016/12/27 11:16 字数 1113 阅读 1.3K  收藏 0 ...

  7. VulnHub系列(一)DC-1

    环境 kali linux 和 DC-1 都是搭建在VMware上的虚拟机,都是NAT模式. 主机发现 NAT模式下虚拟机没有被分配真实的ip地址,他们通过共享宿主机的ip地址访问互联网.我们可以通过 ...

  8. Linux_配置主DNS服务(基础)

    [RHEL8]-DNSserver:[Centos7.4]-DNSclient !!!测试环境我们首关闭防火墙和selinux(DNSserver和DNSclient都需要) [root@localh ...

  9. Windows 10 创建虚拟网卡

    想把虚拟机桥接到我的电脑,但我连的是无线网电脑没查网线,所以就创建个虚拟网卡吧. 1.win + x 打开设备管理器 2.网络适配器  + 操作 + 添加过时硬件  3.下一步  4.选-->安 ...

  10. 几年前,为什么我撸了一套RabbitMQ客户端?

    之前文章说过,如果使用 RabbitMQ,尽可能使用框架,而不要去使用 RabbitMQ 提供的 Java 版客户端. 细说起来,其实还是因为 RabbitMQ 客户端的使用有很多的注意事项,稍微不注 ...