前后端权限控制实现案例
前后端权限控制实现案例
前置阅读推荐:
业务背景:
商户管理系统中,存在不同的商户模式接入该管理系统,如直连商户、二级商户、服务商、特约商户。 真正登录系统管理商户的是操作员,操作员存在不同的角色,不同角色可以访问或操作的页面不尽相同。 在商户层面上,商户还存在若干业务属性,可以决定某些敏感页面功能的开放与否。
整体实现思路:
用户权限最终由两个维度控制——商户模式和操作员角色。 对于商户模式,不同模式下的商户的整体能力是有差异的。有些能力,是专为特定模式下的商户提供的。此类页面能力,在业务规划时便是确定的,没法在不改动代码的情况下,将此类能力开放给其他模式商户。因此,对于不同模型的商户,对应的权限映射基本是不变的,可以将此类配置下放给开放或运维,随版本更新。
对于角色,需要开放给运营或商户进行管理。允许商户新增财经、开发等角色的操作员。更进一步,也可以开放自定义角色给商户,允许自定义角色,并为操作员授权。(数据管理难度上升)
权限管理,从IT层面来讲,便是在用户和权限(点)之间建立联系。在本业务场景下,同时存在两个维度的管理——商户模式、操作员角色。前者需要在内部开发运维层面可控,后者需要在外部用户维度可控。
最灵活的方式,是根据商户模式和操作员角色组成“群组”的概念, 为“群组”计算或实例化出权限点范围,用户再关联到群组,从而得到权限点范围。
不过在实际业务中,考虑历史数据和改造难度,并没有做过度设计,直接跳过群组,通过角色——权限、商户——权限的交集,来圈定出权限点范围。 不过这需要业务保证不同商户模式下的角色,所定义的权限集合是一致的。
关联了用户与权限点之后,前后台又如何根据权限点做控制呢?此时就需要权限点能关联到前台的页面/能力,以及后台的接口API。本业务场景中,权限点直接根据前台页面能力定义,这天然具备可读性的优势,更便于和产品理解沟通,也利于今后自定义角色能力的扩展。
相关数据表
业务实例信息表:
- 商户实例表 (可查询出商户模式)
- 操作员实例表 (可关联到商户及角色)
权限配置映射表:
角色配置表
模式配置表
角色权限映射表
商户权限映射表
接口权限映射表
后端实现思路:
a. 选定商户/角色登入系统:
- 登录的操作员根据操作员实例表查询出其在哪些商户下具有哪些角色。 (操作员实例表)
- 用户选择对应的商户及角色进行管理
- 查询出对应的商户实例信息 (商户实例表)
- 根据其商户模式关联出全量权限点集合 (商户权限映射表)
- 根据商户内角色过滤出最终权限点集合 (角色权限映射表)
- 缓存相关用户信息到会话session中
b. 各API接口检验调用权限:
- 服务端缓存"接口权限映射表"
- 如果API接口存在权限限制,可进行下一步校验。(白名单模式)
- 从会话中获取用户权限点集合。
- 校验API接口所需的权限,是否在用户权限点集合内,如果在,则验证OK。
因为权限点是根据前台页面能力定义(一一对应),而前台的页面能力和接口则可能存在多对多的负责关系。在控制逻辑较为复杂的情况下,“接口权限映射表”可以优化为接口一对多甚至带上逻辑与或的关系。
前台实现思路:
以Vue单页面应用为案例
前端路由配置信息:
menuRoutes = [
{
title: '账号中心',
path: '/account-center',
component: PageLayout,
children:[
{
title: '权限管理',
cooperativeMercFlag: true,
children: [
title: '员工管理',
path: '/account-center/staff-mgmt',
component: StaffMgmt,
permissoin: 'accountCenter_StaffMgmtPage'
]
}
]
}
]
otherRoutes = [
{
name: 'privacy',
path: '/privacy',
compoment: Privacy,
},
{
name: 'merc-apply',
path: '/merc/apply',
component: MercApply,
isLogin: true,
}
]
用户状态信息维护:
userControlStore = {
isLogin,
permissionList,
cooperativeMercFlag,
...otherControlFlag
}
a. 页面菜单显示控制:
对于一个web管理系统,在登录之后往往会提供菜单能力。菜单会根据商户模式和角色针对性的展示
- 页面页面访问、刷新时,主页面文件App.vue内刷新页面状态。检测到用户登录时,会调用后端接口更新全局状态里是userControlStore。
- 根据用户状态信息,过滤可展示的菜单页面,生成accessMenus信息。
- PageLayout.vue文件中,根据页面url路径,定位到一级、二级菜单的显示。
// App.vue
watch: {
userControlStore: {
handler: function(val, oldVal) {/* refresh accessRoutes/accessMenus */},
deep: true
}
}
created: function() {
checkUserStateFromUrlOrCache();
getUserInfo().then(res => {
let userControlInfo = extractUserControlInfo(res);
let accessMenus = filterMenus(menuRoutes, userControlInfo);
let accessRoutes = filterRoutes(allRoutes, userControlInfo);
this.commit('setUserControlStore', userControlInfo);
this.commit('setAccessMenus', accessMenus);
this.commit('setAccessRoutes', accessRoutes);
resetRoutes(accessRoutes);
})
}
// PageLayout.vue
data: {
activePath: '',
subMenus: [],
}
b. 页面访问拦截控制;
当用户直接通过url链接访问页面时,我们需要根据用户状态和权限来判断访问是否合法。
- 我们在页面访问、刷新时,前端页面状态会初始化,生成全量的路由信息。
- 用户直接通过url链接访问页面时,在路由导航守卫里获取路由元信息。
- 通过全局状态管理或cookie或localstorage获取到用户控制信息。
- 针对各控制字段判断路由是否合法。
// router/index.js
const routes = genAllRoutes(menuRoutes, otherRoutes); // 从基础配置数据中生成路由信息
const router = new VueRouter({ routes })
router.beforeEach((to, from, next) => {
let controlMeta = to.meta; // 将基础配置数据里路由控制的字段抽取到路由元信息中
let userControlInfo = getUserControlFromStoreOrCache(); // 通过全局状态管理或cookie或localstorage获取到用户控制信息
if (controlMeta.isAccess(userControlInfo) == false) { // 针对各控制字段判断路由是否合法。
next(homePath);
return;
}
next();
})
c. 页面功能按钮控制:
页面功能按钮控制可通过vue的指令能力实现,通过自定义v-permission="permissionId"的指令,对操作按钮进行移除。
function permissionCheck(el, binding) {
let permissionList = store.getters?.userControlStore?.permissionList;
let permissionId = binding.value;
if(!permissionList.contain(permissionId)) {
el.parentNode?.removeChild(el);
}
}
Vue.directive('permission', {
insert: permissionCheck,
update: permissionCheck
})
不过如上的代码在实际使用中,会存在一个问题: 用户直接通过url访问或刷新页面时,因为store对象是异步更新的,我们没法保证在通过v-permission指令决定是否移除时,store对象里的permissionList是终态有效的。
于是,做了两个补救措施。
- permissionCheck(el, binding) 方法里,不对节点进行移除,而是可以通过 el.style.visibility的方式重复修改。
- 在之前router/index.js文件里getUserControlFromStoreOrCache中,从缓存中获取permissionList作为初始化数据。
缓存信息在这里是个有效的解决方式,即使权限列表作为用户的附属信息不准确了,在异步的获取用户信息接口里,也能及时的更新回来。对应的按钮显示或隐藏,也将在update阶段纠正回来。
附:端到端实现权限控制需梳理维护的项目数据
汇总大表
权限点 | 权限点描述 | 权限点类型 | 页面路径 | 页面截图 | 接口路径 | 角色 | 商户类型 | 商户其他属性 |
---|---|---|---|---|---|---|---|---|
mercInfoPage | 下商户信息菜单查看权限 | 菜单 | /account-center/merc-info | ---- | /api/merc/info/query | 超级管理员/运营 | 所有类型商户 | 无 |
mercInfoPage_operFunc | 商户信息修改权限 | 功能 | /account-center/merc-info | ---- | /api/merc/info/modify | 超级管理员 | 所有类型商户 | 已入网 |