• 聊聊前端鉴权方案


    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果是将数据放入状态管理库(如 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)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    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()
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2.2 组件级

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

    • 组件内判断
    
    
    • 1
    • 2
    • 3
    • 封装为组件
    
    
    
    
    
      
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 自定义指令
    /**
     * 权限判断指令,如组件标记了 v-role="ADMIN"  需要 ADMIN 权限方可显示
     */
    export const Role = {
        mounted(el, binding) {
            const { value } = binding
            /**
             * 由于自定义指令无法作用于自定义组件上(即智能用于 div、span 等标准元素)
             * 所以移除 dom 元素时,直接将父元素移除
             */
            if (!checkRole(value)) el.parentNode && el.parentNode.remove()
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    使用自定义指令(注意:自定义指令仅能用于原生 dom 元素,如 div、span 等)

    • 1
    • 2

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

    3. 结尾

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

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

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

  • 相关阅读:
    主定理(Master Theorem)推导和理解(2)
    salesforce零基础学习(一百一十七)salesforce部署方式及适用场景
    uboot源码——根目录下的mkconfig文件分析
    详解BFS,Dijkstra算法,Floyd算法是如何解决最短路径问题的
    手撕排序之堆排序
    笔记本电脑蓝牙怎么用来连接耳机
    Windows自带硬盘测试工具使用教程
    grafana的前端二次开发初体验
    淘宝一键上货API:自动化上架商品、批量获取商品详情信息
    Python两个序列的相关性
  • 原文地址:https://blog.csdn.net/ssrc0604hx/article/details/127728671