我开始接触 Web 开发那会,正是 MVC 框架大行其道的时期。怎么做前端鉴权呢?大致有两种思路:
1、页面在服务端生成,模版引擎拿到当前用户的角色/授权信息(来自 session 或其他渠道)配合权限规则生成最终的 HTML 代码返回给浏览器。此时用户看到的就是已被授权的内容。
2、网页从后端获取用户的角色/授权信息,在本地判断元素的显隐。
今时今日如火如荼的前后端分离做鉴权,大多采用的是类似第二种的做法,也是今天我要聊的话题。
前端鉴权主要分权限(角色/授权信息)获取、权限判断这两块,简单来说就是拿到规则跟应用规则。
我们通常会选择在应用初始化、首次需要鉴权这两个时间点获取用户的权限信息,这里采用的是前者。由于从远程获取数据是个存在延迟的动作,所以要注意同步控制(只有拿到权限数据才进行后续的作业,否则可能出现越权),还有就是存在白屏的风险,一旦出现可以通过显示 loading 动画缓解。
拿到权限数据后,如何存放呢?
这里采用的方案是直接写入到 window 全局对象(不可修改,见下方代码),纯粹是因为这样读写方便、代码简单 。
// 假设 remoteData 为 {roles:["INPUT"], id: "001", name:"集成显卡"}
let account = Object.assign({ roles: [] }, remoteData)
//锁定用户对象,不支持修改
Object.keys(account).forEach(k => {
Object.defineProperty(account, k, { value: account[k], writable: false, enumerable: true, configurable: true })
})
window.User = account
如果是将数据放入状态管理库(如 Pinia),可增加隐蔽性(无法通过控制台 window.User 查看)、支持响应式,相对地代码也更复杂。
用户权限信息已经拿到手了,如何使用呢?
首先我们定义一个通用方法 checkRole,用来判断当前登录用户是否具备指定权限
/**
* 所在文件 Auth.js
*
* @param {*} requireRole 角色名称或者函数(自行实现判断逻辑)
* @returns
*/
export function checkRole(requireRole) {
let roles = window.User ? (User.roles || []) : []
return typeof (requireRole) === 'string' ? roles.includes(requireRole) : requireRole(roles)
}
通常会在下面的场景需要判断权限。
路由跳转时进行鉴权,若权限不匹配则重定向到指定页面
借助 vue-router 的钩子函数进行拦截:
router.beforeEach((to, from, next) => {
/*
判断权限
注意:meta 是路由定义被保留的属性
*/
if (to.meta.role && !checkRole(to.meta.role)) {
console.error(`☹ ${to.name} (${to.fullPath}) 需要权限 ${to.meta.role},请联系管理员授权 ☹`)
return next({ name: P403 })
}
next()
})
页面内某个组件(如按钮、菜单)只有具备相应权限才显示
/**
* 权限判断指令,如组件标记了 v-role="ADMIN" 需要 ADMIN 权限方可显示
*/
export const Role = {
mounted(el, binding) {
const { value } = binding
/**
* 由于自定义指令无法作用于自定义组件上(即智能用于 div、span 等标准元素)
* 所以移除 dom 元素时,直接将父元素移除
*/
if (!checkRole(value)) el.parentNode && el.parentNode.remove()
}
}
使用自定义指令(注意:自定义指令仅能用于原生 dom 元素,如 div、span 等)
上述几种方式都能实现效果,可根据实际情况或者个人口味选择食用。我的话,偏向于自定义指令(显得逼格更高 ),但是在同一组件内多次鉴权就会用方式一,可以节约判断的次数,绿色计算,为实现碳中和实现一份绵力哈哈。
随着前端体系跟计算量日渐增大,权限控制的重要性会愈加突出,方式也会更多样,甚是期待
以上是对于前端鉴权的个人理解,如果有不对或者更合适的方案可留言哈。
我把相关代码放到仓库里:https://github.com/0604hx/vue3-naive-starter