Antd Pro的权限组件

7,662 阅读5分钟

前端其实是很难有权限验证的,因为从安全的角度来说,前端没有绝对的安全,攻击者总是可以修改前端的代码。对于 API 的权限可以由服务端保证,但是对于页面的权限可能就比较麻烦了。最好的方法当然还是后端控制——也就是 NodeJs 的后端。如果不能达到这个安全级别的话(不过许多应用也没必要),那么剩下的方法都没有什么太大区别,不过如果需要支持动态配置,就需要服务端路由。

Antd Pro 权限路由

这一块算是 Antd Pro 的模板代码,因为 React Router 和 umi 并没有对于权限流(workflow)的封装(虽然实现起来很简单)。umi 可以在配置中增加Routes字段来进入鉴权组件,Antd Pro 的Routes使用了自己实现的Authorized组件。

Antd Pro 增加了另一个authority字段,用来判断每个路由的权限。Antd Pro 团队应该准备把这里设置的很灵活,因为在checkPermissions里是可以接受 promise 的,但是在src/pages/Authorized里最终看到的getRouteAuthority的 ts 定义只接受stringstring[]。这也是有原因的,我猜测是 umi 对配置里的 routeData 做了序列化,导致里面的函数丢失。如果需要写自定义的路由鉴权方式,可能需要从这个地方改起。

Antd Pro 的权限组件在src/components/Authorized模块下。这个模块默认导出一个RenderAuthorize的高阶函数,分别接受Authorized组件和currentAuthority作为参数。这个高阶函数的作用在于根据传入的currentAuthority来解析(parse)当前用户的权限,其内部使用了一个CURRENT变量来缓存解析的结果,并暴露给模块外部使用,这样可以避免重复解析造成效率损失。

我们也可以不使用这个高阶函数,那样就会每次调用传入的权限获取方法(在 Antd Pro 里是getAuthority)。

感觉这里有一点乱的地方是把最终配置了getAuthorityAuthorized放在了utils里。我觉得作为一个默认配置的实例,应当还是放在原先的组件文件夹下比较好,比较有条理性。

最后看一下在Pages里实际使用的Authorized组件。我们可以看到它的 TypeScript 接口类型(话说有了 TypeScript 以后,看源代码真的方便许多):

type IAuthorizedType = React.FunctionComponent<AuthorizedProps> & {
  Secured: typeof Secured;
  check: typeof check;
  AuthorizedRoute: typeof AuthorizedRoute;
};

这个组件的内容很简单,就是调用check来检查权限。这个check是对抽象出来的checkPermissions方法的再封装,本质上就是为了实现一个包裹当前权限的部分应用函数。前面也提到过,checkPermissions可以对各种类型的authority做支持(比如说函数),具体细节比较简单就不细说了。

最终,这就是我们在src/pages/Authorized里看到的封装:

<Authorized
  authority={getRouteAuthority(location.pathname, routes) || ''}
  noMatch={isLogin ? <Redirect to="/exception/403" /> : <Redirect to="/user/login" />}>
  {children}
</Authorized>

所有的判断逻辑都在一个入口处实现。

研究 Antd Pro 权限组件的初衷主要还是一些特殊需求和中间偶尔出现的 bug。比如,一开始我遇到了登录后重定向到登录页面的 bug,分析了源代码之后,我发现主要原因是因为CURRENT变量缓存了未登录时的权限。解决方法也很简单,只要调用reloadAuthorized即可。其实文档里对此也提到了,不过没有非常明确的提出这个约定,所以我以为 Antd Pro 会隐式的做这一步,结果是我想多了。

还有当我在 config 里设置路由时,对于和Routes同层的authority配置是无效的。不过这个属于 umi 读取配置的逻辑:

{
  path: '/',
  component: '../layouts/BasicLayout',
  Routes: ['src/pages/Authorized'],
  authority: ['admin', 'user'],
  routes: [
    // forms
    {
      path: '/form',
      icon: 'form',
      name: 'form'
    },
  ],
}

总之,Antd Pro 就是对权限判断逻辑做了一层简单的封装,弥补 React Router 的不足。

改写 Antd Pro 权限存储

Antd Pro 默认将权限全部存储在前端(页面权限和用户权限)。Antd Pro 曾经有对后端路由的支持,它是通过一种插件模式,来修改 app 的 render 函数。但后来这部分被移除了,因为它的数据是基于 mock 的,一般服务器没有实现,就会报错。

api/auth_routes 这个已经严重影响到发布

这部分的过程都被我考古出来了……其实我觉得保留挺好的,但是可能这就是社区对开源项目的影响吧……

在基于 token 的认证中,我们可以从 jwt token 里提取当前的身份信息(注意到这并不安全),但没有必要。我们可以直接根据当前用户的 token 来得到后端路由。我所理解的后端路由,前端仍然会有 config.ts 里的配置,因为像 component 不可能在后端做管理。

社区里有不少人想要实现在登录后获取路由的模式,这主要是基于角色的路由实在比较简单。感觉这是Antd Pro 目前还不成熟的体现。

目前 umi 提供的异步挂载路由的方式是通过插件机制来解决,感觉并不能说是非常正式的(能用)。而且这个在 app.js 里,不能在登录后处理。

最后,我自己的实现是在登录的异步请求之后挂载路由,但是这只能通过g_routes全局变量来操作,感觉也是不太正式,只能期待 umi 团队再接再厉了。