如何从服务端获取菜单数据及权限校验

简介: 如何从服务端获取菜单数据及权限校验

关于登录授权和安全性这部分的内容,这里不做讨论


用户访问权限,其实是一个中后台很常规的问题,说到中后台不得不提到最佳实践,ant-design-pro。我自己开始接触 pro 的时候,是 v1 要升 v2 的时候了。它是通过权限组件 Authorized 来进行权限校验的,通过比对现有权限与准入权限,决定相关元素的展示。然后通过路由数据生成菜单数据,再结合准入权限,来达到整个权限管理体系。这在很多的项目中实践,也得到了很好的践行。


用这个方案需要几个前置条件,前端需要知道各个页面的准入权限,菜单数据通过路由数据生成,到底是需要控制菜单数据还是控制路由数据,有点分不清楚。


但是对于我自己来说,总觉得很难达到“舒适”的使用体验。也在 pro 的 issues 区看到了很多相关的问题和需求,但基本上都没有得到很好的答复。比如当涉及到“如何从服务端获取菜单数据进行权限校验”时,就很难处理。到 pro v2 结合 Umi ,主要有两种方式处理。


pro v2 的实现

方案一,结合 Umi 的运行时,在 patchRoutes 中动态修改了路由数据,从官网的例子来看,似乎处理了这个问题,但是,其实这里是一个错误的引导。

let extraRoutes;
export function patchRoutes({ routes }) {
  merge(routes, extraRoutes);
}
export function render() {
  fetch('/api/routes').then((res) => { extraRoutes = res.routes })
}

配置式中,需要将项目所有的可用路由提前配置,这里编辑的只是运行时的路由,简而言之,修改的不是编译时的路由,很多人把这个搞混了。比如在 routes  中配置了页面 A、B,然后在这里增加了页面C,在 dev 的时候,发现可用,效果也符合预期。但是当执行 umi build 之后,发现没有页面 C。


约定式,会默认编译所有的页面,所以不存在上述的问题,但是约定式生成的路由,与实际可用菜单差异较大,在这里需要做很大的操作成本,并且需要时刻注意,只能修改 routes ,不能返回新的对象。


方案二,也是我比较认可的方案,通过把路由和菜单数据分割开,我的理解是路由数据是传递给 Umi 做页面编译需要的数据,菜单数据是根据业务需要进行编辑和维护的。两份数据确实有冗余部分,但是将两份数据统一维护,要耗费的成本太高。所以我干脆将菜单数据,放到 models 中维护,通过在 models 里面发起请求,在 layout 中消费的方式实现动态菜单功能。


Umi 3 中的实现

同样有从服务端获取菜单数据的问题,参考上面的方案。在最新的 Umi 3 中,通过结合 @umijs/plugin-plugin-initial-state 、  @umijs/plugin-access 和 @umijs/plugin-layout 一起使用,进行权限校验。通过 @umijs/plugin-initial-state 获取初始化数据, 在 @umijs/plugin-access 中通过 src/access 定义的方法,对路由数据进行标记 unaccessible,最后在 @umijs/plugin-layout 中对标记了 unaccessible 的菜单进行过滤。


先不说分三个插件库有多难维护,在 access 插件中使用 splice 修改路由数据,在 layout 插件中通过 const _routes = require('@@/core/routes').routes; 引用被修改后的数据,这种同事无法维护的代码。仅仅不支持约定式使用这一点,我就无法接受。


我自己项目中的尝试

对我来说权限校验,无非就是有什么菜单,能不能访问?上面提到的三个插件库,只用到一个方法类 runtimeUtil 和 一个过滤组件 WithExceptionOpChildren 保留 access 插件的 src/access 文件。

// src/access.ts
export default function (initialState: { currentUser?: API.CurrentUser | undefined }) {
  const { currentUser } = initialState;
  return {
    canAdmin: currentUser && currentUser.access === 'admin',
  };
}

在 layout 中,发起请求服务端的菜单,或者本地的菜单,然后通过 runtimeUtil 和 transformRoute 对菜单数据进行操作。

import React, { FC } from 'react';
import ProLayout from '@ant-design/pro-layout';
import { useIntl, } from 'umi';
import { transformRoute } from '@umijs/route-utils';
import accessFactory from '@/access';
// 以下两个引用是伪代码
import { WithExceptionOpChildren } from '@umijs/plugin-layout/component/Exception/index.tsx';
import { traverseModifyRoutes } from '@umijs/plugin-access/utils/runtimeUtil';
const BasicLayout: FC = ({ children, location }) => {
  const pathName = location.pathname;
  const intl = useIntl();
  // 这里传入初始化数据,如果有使用 plugin-initial-state 可以直接传入 initialState
  const access = accessFactory({
    currentUser: {
      access: 'admin'
    }
  })
  // 这个数据可以是本地写死,或者来自服务端接口,如果是配置式的还可以直接使用route.routes
  const serveMenuData = [
    {
      path: '/',
      name: 'index',
      icon: 'smile',
    },
    {
      path: '/ListTableList',
      name: 'list',
      icon: 'smile',
      hideInMenu: true,
      access: 'canAdmin'
    },
  ]
  const accrssMenu = traverseModifyRoutes(serveMenuData, access);
  const { menuData, breadcrumb } = transformRoute(accrssMenu, true, intl.formatMessage);
  const currentPathConfig = breadcrumb.get(pathName)
  return (
    <ProLayout
      menuDataRender={() => menuData}
    >
      <WithExceptionOpChildren currentPathConfig={currentPathConfig}>
        {children}
      </WithExceptionOpChildren>
    </ProLayout>
  );
};
export default BasicLayout;

上面的使用主要做了几件事情

1、向 accessFactory 中传入 initState 数据,得到我们需要的权限对象 access 2、通过 traverseModifyRoutes 方法修改我们的初始菜单数据

3、将修改后的菜单数据,通过 transformRoute 方法转换,过滤掉不展示的菜单

4、使用 breadcrumb.get(pathname) 获取当前路由的菜单数据

5、通过 WithExceptionOpChildren 拦截匹配 currentPathConfig


通过上面的方法,可以处理几个常见问题

1、动态菜单

2、路由守卫(当菜单不现实,但是直接通过url访问,会被拦截)

3、关键的几个数据可以随意传入,方便结合其他插件使用。


通过翻阅 Umi@3 插件库的源码和文档,最终整理了上述的方案,在我们项目中实践,不知道有没有其他未知的问题,如果经过一段时间的实践,没有其他问题的话,应该会直接整理成一个新的 Umi 插件。

目录
相关文章
|
10月前
|
数据安全/隐私保护
fastadmin是如何设置没有权限的用户不能访问某些页面的?
fastadmin是如何设置没有权限的用户不能访问某些页面的?
259 0
|
5月前
|
存储 前端开发 Java
①实现基于session的登录流程:发送验证码、登录注册、校验登陆状态
①实现基于session的登录流程:发送验证码、登录注册、校验登陆状态
73 0
|
7月前
|
UED
路由权限登录后还保留上一个登录角色的权限,刷新一下就好了的解决方案
路由权限登录后还保留上一个登录角色的权限,刷新一下就好了的解决方案
33 0
|
11月前
|
SQL XML 缓存
修改若依的数据权限功能
修改若依的数据权限功能
963 0
|
Linux 数据安全/隐私保护
设计并实现请求路径权限认证
设计并实现请求路径权限认证
设计并实现请求路径权限认证
|
NoSQL 前端开发 Redis
简单的token用户登录实现,接口权限校验
简单的token用户登录实现,接口权限校验
|
前端开发 JavaScript Java
两个小例子登录和显示全部用户信息的模块(四)
两个小例子登录和显示全部用户信息的模块(四)
两个小例子登录和显示全部用户信息的模块(四)
|
数据安全/隐私保护
需求: 需要完成如下基本功能: 1:添加用户 (注册) 2:删除用户 (注销用户) 3:修改用户 4:查询单个用户 5:查询所有用户 6:退出系统 程序运行后,
需求: 需要完成如下基本功能: 1:添加用户 (注册) 2:删除用户 (注销用户) 3:修改用户 4:查询单个用户 5:查询所有用户 6:退出系统 程序运行后,除
DRF修改权限、用户认证方式
修改权限、用户认证方式 在settings中全局配置 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.