聊聊前端鉴权方案

1. 楔子

我开始接触 Web 开发那会,正是 MVC 框架大行其道的时期。怎么做前端鉴权呢?大致有两种思路:

1、页面在服务端生成,模版引擎拿到当前用户的角色/授权信息(来自 session 或其他渠道)配合权限规则生成最终的 HTML 代码返回给浏览器。此时用户看到的就是已被授权的内容。

2、网页从后端获取用户的角色/授权信息,在本地判断元素的显隐。

今时今日如火如荼的前后端分离做鉴权,大多采用的是类似第二种的做法,也是今天我要聊的话题。

2. 鉴权方案

前端鉴权主要分权限(角色/授权信息)获取、权限判断这两块,简单来说就是拿到规则跟应用规则。

在这里插入图片描述

2.1 获取用户权限

我们通常会选择在应用初始化首次需要鉴权这两个时间点获取用户的权限信息,这里采用的是前者。由于从远程获取数据是个存在延迟的动作,所以要注意同步控制(只有拿到权限数据才进行后续的作业,否则可能出现越权),还有就是存在白屏的风险,一旦出现可以通过显示 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 查看)、支持响应式,相对地代码也更复杂。

2.2 权限判断

用户权限信息已经拿到手了,如何使用呢?

首先我们定义一个通用方法 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)
}

通常会在下面的场景需要判断权限。

2.2.1 页面级

路由跳转时进行鉴权,若权限不匹配则重定向到指定页面

借助 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()
})

2.2.2 组件级

页面内某个组件(如按钮、菜单)只有具备相应权限才显示

  • 组件内判断
<!--需要赋值 isAdmin,如 let isAdmin = window.User.roles.includes("ADMIN")-->
<div v-if="isAdmin"></div>

  • 封装为组件
<template>
    <template v-if="show"> <slot></slot> </template>
</template>

<script setup>
    import { checkRole } from "@S/Auth"

    const props = defineProps({
        need: { type: [String, Function], default: "" }
    })

    let show = checkRole(props.need)
</script>

<!--如何使用-->
<WithRole need="ADMIN"> <AdminMenu /> </WithRole>

  • 自定义指令
/**
 * 权限判断指令,如组件标记了 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 等)

<div v-role="'ADMIN'"><AdminMenu /></div>

上述几种方式都能实现效果,可根据实际情况或者个人口味选择食用。我的话,偏向于自定义指令(显得逼格更高 😎),但是在同一组件内多次鉴权就会用方式一,可以节约判断的次数,绿色计算,为实现碳中和实现一份绵力哈哈。

3. 结尾

随着前端体系跟计算量日渐增大,权限控制的重要性会愈加突出,方式也会更多样,甚是期待 😁

以上是对于前端鉴权的个人理解,如果有不对或者更合适的方案可留言哈。

我把相关代码放到仓库里:https://github.com/0604hx/vue3-naive-starter

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

集成显卡

码字不易,需要您的鼓励😄

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值