记录两个项目中的权限校验实现方式
1.OKR(一个项目)权限校验
使用github上开源框架ant-design-pro
路由权限的实现方式是通过获取当前用户的权限去比对路由表,生成当前用户具有的权限可访问的路由表,通过 router.addRoutes
router
上。
OKR中每个页面的权限都是动态从后端配置的,可以在后台通过一个 tree 或者其它展现形式给每一个页面动态配置权限,之后将这份路由表存储到后端。该项目中是使用tree实现路由控制。
1.1后台返回数据结构
/me 接口,得到用户所有信息
1 //返回的JSON格式
2 {
3 data:{
4 ...//基本信息
5 permissions:[{
6 permissionId:"页面权限id/系统id",
7 title:'HolliOne',
8 children:[{
9 permissionId:'user', //页面及权限
10 title:'用户管理',
11 children:[{
12 action: "resetPassword" //资源按钮级权限
13 title:'重置密码',
14 ....
15 },...]
16 },...]
17 },
18 message:'请求成功',
19 success:true,
20 }
1.2 前端页面菜单级权限校验
当用户登录后得到 permissions,动态生成可访问页面
1 // 前端路由表
2 const constantRouterComponents = {
3 // 基础页面 layout 必须引入
4 BasicLayout: BasicLayout,
5 BlankLayout: BlankLayout,
6 RouteView: RouteView,
7 PageView: PageView,
8
9 // 需要动态引入的页面组件
10 analysis: () => import('@/views/dashboard/Analysis'),
11 workplace: () => import('@/views/dashboard/Workplace'),
12 monitor: () => import('@/views/dashboard/Monitor')
13 // ...more
14 }
View Code
1 getInfo().then(response => {
2 const result = response.data
3 result.role = {}
4 if (result.permissions[0].children.length > 0) {
5 let temp = []
6 const role = result.role
7 role.permissions = util.findActionArr(result.permissions[0].children,temp)
8 // const role = result.role
9 // role.permissions = result.permissions[0].children
10 role.permissions.map(per => {
11 if (per.children != null && per.children.length > 0) {
12 const action = per.children.map(action => { return action.action })
13 per.actionList = action
14 }
15 })
16 // role.permissionList = role.permissions.map(permission => { return permission.permissionId })
17 let permissionList = [];
18 role.permissionList = util.findArr(result.permissions[0].children, permissionList, 'permissionId')
19 commit('SET_ROLES', result.role)
20 commit('SET_INFO', result)
21 } else {
22 reject(new Error('getInfo: roles must be a non-null array !'))
23 }commit('SET_AVATAR', result.avatar)
24 resolve(response)
25 }).catch(error => {
26 reject(error)
27 })
View Code
1 //动态生成菜单
2 router.beforeEach((to, from, next) => {
3 NProgress.start() // start progress bar
4
5 if (Vue.ls.get(ACCESS_TOKEN)) {
6 /* has token */
7 if (to.path === '/user/login') {
8 next({ path: '/user/userList' })
9 NProgress.done()
10 } else {
11 if (store.getters.roles.length === 0) {
12 store
13 .dispatch('GetInfo')
14 .then(res => {
15 const roles = res.data && res.data.role
16 store.dispatch('GenerateRoutes', { roles }).then(() => {
17 // 根据roles权限生成可访问的路由表
18 // 动态添加可访问路由表
19 router.addRoutes(store.getters.addRouters)
20 const redirect = decodeURIComponent(from.query.redirect || to.path)
21 if (to.path === redirect) {
22 // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
23 next({ ...to, replace: true })
24 } else {
25 // 跳转到目的路由
26 next({ path: '/'+ roles.permissionList[0] })
27 }
28 })
29 })
30 .catch((err) => {
31 console.log(err);
32 notification.error({
33 message: '错误',
34 description: '您暂时没有权限,请联系管理员开启权限'
35 })
36 store.dispatch('Logout').then(() => {
37 next({ path: '/user/login', query: { redirect: to.fullPath } })
38 })
39 })
40 } else {
41 next()
42 }
43 }
44 } else {
45 if (whiteList.includes(to.name)) {
46 // 在免登录白名单,直接进入
47 next()
48 } else {
49 next({ path: '/user/login', query: { redirect: to.fullPath } })
50 NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
51 }
52 }
53 })
54
55 //vuex store modules
56 /**
57 * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除
58 *
59 * @param permission
60 * @param route
61 * @returns {boolean}
62 */
63 function hasPermission (permission, route) {
64 if (route.meta && route.meta.permission) {
65 let flag = false
66 for (let i = 0, len = permission.length; i < len; i++) {
67 flag = route.meta.permission.includes(permission[i])
68 if (flag) {
69 return true
70 }
71 }
72 return false
73 }
74 return true
75 }
76 //格式化 后端 结构信息并递归生成层级路由表
77 function filterAsyncRouter (routerMap, roles) {
78 const accessedRouters = routerMap.filter(route => {
79 if (hasPermission(roles.permissionList, route)) {
80 if (route.children && route.children.length) {
81 route.children = filterAsyncRouter(route.children, roles)
82 }
83 return true
84 }
85 return false
86 })
87 return accessedRouters
88 }
89 actions: {
90 GenerateRoutes ({ commit }, data) {
91 return new Promise(resolve => {
92 const { roles } = data
93 const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
94 commit('SET_ROUTERS', accessedRouters)
95 resolve()
96 })
97 }
98 }
View Code
1.3 资源/按钮级权限校验
封装了一个按钮级别权限的自定义指令, v-action
1 <!-- eg: 当前页面为 user -->
2
3 <template>
4 <!-- 校验是否有 user 权限下的 add 操作权限 -->
5 <a-button v-action:add >添加用户</a-button>
6
7 <!-- 校验是否有 user 权限下的 del 操作权限 -->
8 <a-button v-action:del>删除用户</a-button>
9
10 <!-- 校验是否有 user 权限下的 edit 操作权限 -->
11 <a v-action:edit @click="edit(record)">修改</a>
12 </template>
View Code
需要注意的是,指令权限默认从 store 中获取当前已经登陆的用户的角色和权限信息进行比对,所以也要对指令权限的获取和校验 Action 权限部分进行自定义。
在某些情况下,不适合使用 v-action
,例如 Tab 组件,只能通过手动设置 v-if 来实现。
1 <template>
2 <a-tabs>
3 <a-tab-pane v-if="$auth('user.add')" tab="Tab 1">
4 some context..
5 </a-tab-pane>
6 <a-tab-pane v-if="$auth('user.del')" tab="Tab 2">
7 some context..
8 </a-tab-pane>
9 <a-tab-pane v-if="$auth('user.edit')" tab="Tab 3">
10 some context..
11 </a-tab-pane>
12 </a-tabs>
13 </template>
View Code
在 Vue 初始化时,@/utils/helper/permission.js 作为插件注册到 Vue 原型链上,在 Vue 实例中就可以用 this.$auth() 方法进行权限判断。 这里也要对权限的获取和校验 Action 权限部分进行自定义。
2.另一个项目的权限校验
使用iview-admin开源框架
2.1后台返回的数据结构
/userInfo接口
1 //接口返回的JSON格式数据
2 {
3 data:{
4 btnSet:['按钮级权限集合(例如增删该查以及各种操作)'],
5 menuSet:['页面级权限集合,返回字段和前端路由配置一致的页面就有权限'],
6 soleSet:['角色集合,该用户是什么角色'],
7 user:{'登录用户的基本信息'},
8 },
9 msg:'操作成功',
10 state:1,
11 success:true
12 }
2.2前端页面级权限操作
路由权限的实现方式是通过获取当前用户的权限去比对路由表,生成当前用户具有的权限可访问的路由表
前端登录时调用userInfo
接口,将权限信息分开存入本地localStorage或者cookies/vuex-store,(就是便于拿到信息的地方)
第一步:过滤页面权限
1 /**
2 * 对一个对象或者数组进行深复制,保证不是以引用赋值
3 */
4 cloneObj(obj) {
5 var str = obj.constructor === Array ? [] : {};
6 var newobj = obj.constructor === Array ? [] : {};
7 if (typeof obj !== 'object') {
8 return;
9 } else if (window.JSON) {
10 str = JSON.stringify(obj); // 首先将对象序列化
11 newobj = JSON.parse(str); // 然后将对象还原
12 }
13 return newobj;
14 },
15 //过滤路由
16 updateMenulist (state) {
17 // 权限过滤 、
18 let permissions = Cookies.get('permissions');
19 if (permissions === undefined) {
20 return;
21 }
22 debugger
23 let menuList = [];
24 appRouter.forEach((child, index) => {
25 // 深复制对象
26 var item = Utils.cloneObj(child);
27 let len = menuList.push(item);
28 // 父类是否存在
29 let parent = JSON.parse(permissions).filter(p => {
30 if (p === item.name) {
31 return p;
32 }
33 });
34 if (parent.length > 0) {
35 let childrenArr = [];
36 childrenArr = item.children.filter(child => {
37 var p = JSON.parse(permissions);
38 for (var i = 0; i < p.length; ++i) {
39 // 放开测试页面
40 if (p[i] === child.name || child.name === 'hollsysTestPage') {
41 return child;
42 }
43 }
44 });
45 item.children = childrenArr;
46 // if (childrenArr === undefined || childrenArr.length === 0) {
47 // menuList.splice(len - 1, 1);
48 // }
49 } else {
50 menuList.splice(len - 1, 1);
51 }
52 });
53 let resut = [];
54 let pArr = JSON.parse(permissions);
55 /**循环遍历menuList*/
56 if (menuList != null && menuList.length>0) {
57 for (var i=0;i<menuList.length;i++) {
58 var temp = menuList[i];
59 let childarr = [];
60 childarr = temp.children;/**二级*/
61 for (var j=0;j<childarr.length;j++) {
62 var tempj = childarr[j];
63 /**遍历三级路由*/
64 var sanArr = [];
65 sanArr = tempj.children;
66 if (sanArr !== undefined) {
67
68 for (var k=0;k<sanArr.length;k++) {
69 var tempK = sanArr[k];
70 var flag = 0;
71 for (var q = 0; q < pArr.length; ++q) {
72 // 放开测试页面
73 if (pArr[q] === tempK.name) {
74 /**删除sanArr中这个权限*/
75 flag=1;
76 }
77 }
78 if (flag == 0) {
79 sanArr.splice(k,1);
80 }
81 }
82 }
83 }
84 }
85 }
86 state.menuList = menuList;
87 // 上面routers加所有的appRouters报错 改为过滤连接时动态添加
88 // state.routers.push(...appRouter);
89 state.routers.push(...menuList);
90 }
View Code
2.3前端按钮级权限过滤
页面路由生成,生成按钮及的权限
在每个页面中通过if/v-if判断按钮是否显示
1 //此角色有什么操作按钮权限功能
2 permissionsBtnSet (name) {
3 var permissionsBtnSet = Cookies.get('permissionsBtnSet');
4 var p = JSON.parse(permissionsBtnSet);
5 for (var i = 0; i < p.length; ++i) {
6 if (p[i] === name) {
7 return true;
8 }
9 }
10 return false;
11 }
View Code
哈哈哈哈,到此就结束了,可以生成菜单页面和操作按钮,自己记录一下。