前端其实是很难有权限验证的,因为从安全的角度来说,前端没有绝对的安全,攻击者总是可以修改前端的代码。对于 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 定义只接受string和string[]。这也是有原因的,我猜测是 umi 对配置里的 routeData 做了序列化,导致里面的函数丢失。如果需要写自定义的路由鉴权方式,可能需要从这个地方改起。
Antd Pro 的权限组件在src/components/Authorized模块下。这个模块默认导出一个RenderAuthorize的高阶函数,分别接受Authorized组件和currentAuthority作为参数。这个高阶函数的作用在于根据传入的currentAuthority来解析(parse)当前用户的权限,其内部使用了一个CURRENT变量来缓存解析的结果,并暴露给模块外部使用,这样可以避免重复解析造成效率损失。
我们也可以不使用这个高阶函数,那样就会每次调用传入的权限获取方法(在 Antd Pro 里是getAuthority)。
感觉这里有一点乱的地方是把最终配置了getAuthority的Authorized放在了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 的,一般服务器没有实现,就会报错。
这部分的过程都被我考古出来了……其实我觉得保留挺好的,但是可能这就是社区对开源项目的影响吧……
在基于 token 的认证中,我们可以从 jwt token 里提取当前的身份信息(注意到这并不安全),但没有必要。我们可以直接根据当前用户的 token 来得到后端路由。我所理解的后端路由,前端仍然会有 config.ts 里的配置,因为像 component 不可能在后端做管理。
社区里有不少人想要实现在登录后获取路由的模式,这主要是基于角色的路由实在比较简单。感觉这是Antd Pro 目前还不成熟的体现。
目前 umi 提供的异步挂载路由的方式是通过插件机制来解决,感觉并不能说是非常正式的(能用)。而且这个在 app.js 里,不能在登录后处理。
最后,我自己的实现是在登录的异步请求之后挂载路由,但是这只能通过g_routes全局变量来操作,感觉也是不太正式,只能期待 umi 团队再接再厉了。