系统鉴权方式

前言

为什么会聊到这个话题呢,是因为这段时间公司局势动荡,让我有了想要看看更多机会的想法,因此在interview的时候被问到,你们公司的系统是怎么做鉴权的呢,当然被问到这个问题后,凭借着我的记忆瞎答了一通,事后去梳理了一下,发现公司的鉴权逻辑原来是这样。

项目架构

公司内系统的鉴权架构图如下:
在这里插入图片描述
根据上面的架构图进行从底向上的逐层剖析

两种鉴权模式

  1. RBAC(role-Based Access Control)基于角色的访问控制
    整体的设计思路就是创建多个角色(即权限所属的集合),然后将具体的功能权限点按角色定位分配给相应角色,然后赋予用户指定角色即可。
  2. ABAC(Attribute-Based Access Control)基于属性的权限控制
    整体的设计思路是通过实体的属性、操作类型、相关的环境来控制是否有对操作对象的权限

二者相对优势:

  • RBAC优势
    • 角色明确逻辑简单明了易于理解,适合角色功能简单化耦合关系较小的权限控
  • ABAC优势
    • 控制粒度更细
    • 可以解决复杂计算组合的权限场景

当前厂内采用的是RBAC的模式,逻辑简明易管理。

Node端

项目整体采用了BFF模式,因此在整个前端项目中会多出node端这个东西。Node端采用midway狂降,主要做的是将申请好的ACL key值,配置到config文件中,然后写好service和controller,来获取ACL那边配置好的角色信息。

前端

前端采用了umi框架,前端内主要做了两方面,一是全局鉴权,二是路由鉴权。

全局鉴权

全局鉴权函数的主要代码如下:主要会调用具体的函数,如checkAuth,来更新authList,再根据authList里面的角色权限去做具体的判断

import { getAuthsByUser, getRolesByUser } from '@/services/acl/api';
import { useCallback, useRef, useState } from 'react';

export default function useAuthority() {
  const [updateTime, setUpdateTime] = useState<number>(Date.now());

  const aclObj = useRef<any>({ authList: [], roleList: [] });
  const rigister = useRef<any>([]);
  const fetching = useRef<boolean>(false);

  const loop = useCallback(
    (list: any) => {
      if (list.length) {
        const current = list.pop();
        current(aclObj.current);
        loop(list);
      }
      return;
    },
    [aclObj],
  );

  const initData = useCallback(async () => {
    if (fetching.current) {
      return new Promise((resolve) => {
        rigister.current.push(resolve);
      });
    } else {
      fetching.current = true;
      const roles = await getRolesByUser();
      aclObj.current.roleList = roles;
      const auths = await getAuthsByUser();
      aclObj.current.authList = auths;
      fetching.current = false;
      if (rigister.current.length) {
        loop(rigister.current);
      }
      return Promise.resolve(aclObj.current);
    }
  }, [fetching, rigister]);

  const checkBlankData = useCallback(async () => {
    if (!aclObj.current.authList.length || !aclObj.current.authList.length) {
      setUpdateTime(Date.now());
      return initData();
    } else {
      if (Date.now() - updateTime < 60000) {
        return Promise.resolve(aclObj.current);
      } else {
        setUpdateTime(Date.now());
        return initData();
      }
    }
  }, [updateTime]);

  /**
   * 判断是否有对应权限或同时有一组权限
   */
  const checkAllAuth = useCallback(async (keys: string | string[]) => {
    if (!keys) {
      return false;
    }
    const res = await checkBlankData();
    if (typeof keys === 'string') {
      return res.authList.includes(keys);
    }
    return keys.filter((key) => res.authList.includes(key)).length === keys.length;
  }, []);

  /**
   * 判断是否是对应角色或同时属于多个角色
   */
  const checkAllRole = useCallback(async (keys: string | string[]) => {
    if (!keys) {
      return false;
    }
    const res = await checkBlankData();
    if (typeof keys === 'string') {
      return res.roleList.includes(keys);
    }
    return keys.filter((key) => res.roleList.includes(key)).length === keys.length;
  }, []);

  /**
   * 判断是否有对应权限或其中某个权限
   */
  const checkAuth = useCallback(async (keys: string | string[]) => {
    if (!keys) {
      return false;
    }
    const res = await checkBlankData();

    if (typeof keys === 'string') {
      return res.authList.includes(keys);
    }
    return !!keys.filter((key) => res.authList.includes(key)).length;
  }, []);

  /**
   * 判断是否是对应角色或属于其中某个角色
   */
  const checkRole = useCallback(async (keys: string | string[]) => {
    if (!keys) {
      return false;
    }
    const res = await checkBlankData();

    if (typeof keys === 'string') {
      return res.roleList.includes(keys);
    }
    return !!keys.filter((key) => res.roleList.includes(key)).length;
  }, []);

  return {
    authList: rigister.current.authList,
    roleList: rigister.current.roleList,
    checkAllAuth,
    checkAllRole,
    checkAuth,
    checkRole,
  };
}
路由权限

路由文件配置

let routes: MenuDataItem = [
	{
    name: '一级菜单',
    path: '/x',
    icon: 'AccountBookOutlined',
    component: '@/layouts/page-layout',
    auths: ['auth-flag'], // 在权限范围内可以访问(acl功能权限标识)
    routes: [
      {
        path: '/x/list',
        name: '二级菜单', 
        component: './x/list', // 具体的页面组件
        auths: ['auth-flag'], // 在权限范围内可以访问(acl功能权限标识)
        wrappers: ['@/components/auths/page-auths'], // 拓展的鉴权组件,用于验证当前页面权限
      }
    ],
  },
]

菜单渲染工具:patchRoutesumi 运行时的配置,可以拿到打平的路由表,感兴趣的可以去看下umi框架

import { checkRouteAuths } from './utils/utils';

export function patchRoutes({ routes = [] as any }) {
  routes = checkRouteAuths(auths, routes);
}
/**
 * 路由权限处理,控制菜单是否展示
 * @param auths
 * @param routes
 */
export function checkRouteAuths(auths: string[], routes: any[]) {
  routes.forEach((route) => {
    if (route.routes) {
      route.routes = checkRouteAuths(auths, route.routes);
    }
    if (route.auths != null) {
      const res = route.auths.find((auth: string) => auths.includes(auth));
      if (!res) {
        route.hideInMenu = true;
      }
    }
  });
  return routes;
}

综上依靠整体结构来支撑各个业务方;
分享到这里,希望看到的小伙伴能点赞支持一下,白菜会每周不定期分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值