Backstage 插件前端鉴权实战用 usePermission 与 RequirePermission 为 UI 组件加权限保护【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage前端鉴权是 Backstage 权限框架Permission Framework的最后一公里当后端路由已经通过权限策略得到保护后前端还需要在用户体验层面做出响应——禁用按钮、隐藏组件、拦截路由。本文以 todo-list 示例插件为线索讲解如何在 Backstage 插件前端使用backstage/plugin-permission-react提供的usePermissionHook 与RequirePermission组件并深入源码剖析其底层实现stale-while-revalidate 缓存、请求批处理、资源权限短路等帮助你写出既安全又友好的插件 UI。为什么前端也要做鉴权在权限框架系列教程的前几节中我们已经学会了如何用PermissionPolicy保护插件后端 API 路由。对于绝大多数返回数据用于展示的路由例如示例中的GET /todos前端无需任何改动——后端会直接返回空列表或404这本身已经足够安全。但问题出在触发变更操作mutative action的 UI 元素上。以 todo-list 应用的 Add 按钮为例用户点击后前端会向后端/todos路由发起POST请求。如果一个用户没有创建 todo 的权限他只能等到点下按钮、看到报错时才意识到自己被拒绝——这是非常糟糕的用户体验。更合理的做法是提前禁用这个按钮甚至在权限不足时直接隐藏整个添加区块。:::note 安全边界前端鉴权永远不能替代后端鉴权 前端鉴权只能作为后端鉴权的补充用于改善用户体验绝不能作为安全防线本身。前端代码对用户完全可见、可篡改即便你禁用了按钮或隐藏了组件恶意用户仍然可以直接向后端路由发送请求。因此务必先确保后端路由已置于鉴权保护之下再在前端做这些优化。 :::安装依赖在插件前端做鉴权需要两个包backstage/plugin-permission-react提供 Hook 与组件以及你插件自己的-common包导出已声明的权限对象。以 todo-list 插件为例$ yarn workspace internal/plugin-todo-list \ add backstage/plugin-permission-react internal/plugin-todo-list-commonbackstage/plugin-permission-react包的核心导出非常精简它从三个子模块统一对外暴露能力见 plugins/permission-react/src/index.tscomponentsRequirePermission组件hooksusePermissionHookapisPermissionApi接口与permissionApiRef前端鉴权请求的统一入口。用 usePermission 禁用按钮假设todoListCreatePermission是我们在internal/plugin-todo-list-common中声明的普通非资源型权限。在plugins/todo-list/src/components/TodoListPage/TodoListPage.tsx中引入 Hook 与权限对象import { alertApiRef, discoveryApiRef, fetchApiRef, useApi, } from backstage/core-plugin-api; import { usePermission } from backstage/plugin-permission-react; import { todoListCreatePermission } from internal/plugin-todo-list-common; function AddTodo({ onAdd }: { onAdd: (title: string) any }) { const title useRef(); const { loading: loadingPermission, allowed: canAddTodo } usePermission({ permission: todoListCreatePermission, }); return ( Typography variantbody1Add todo/Typography Box componentspan alignItemsflex-end displayflex flexDirectionrow TextField placeholderWrite something here... onChange{e (title.current e.target.value)} / {!loadingPermission ( Button disabled{!canAddTodo} variantcontained onClick{() onAdd(title.current)} Add /Button )} /Box / ); }关键点解读usePermission接收一个包含permission字段的参数对象返回AsyncPermissionResult其中loading表示鉴权请求是否仍在进行allowed表示当前用户是否被允许执行该操作plugins/permission-react/src/hooks/usePermission.ts。在loadingPermission为true时不要渲染按钮或渲染占位避免出现先可用、后禁用的闪烁加载完成后依据canAddTodo决定是否disabled。修改策略验证效果改完前端代码后把getting-started阶段创建的权限策略模块CustomPolicy类改为对创建 todo 的请求返回DENY即可验证按钮是否被正确禁用if (isPermission(request.permission, todoListCreatePermission)) { return { result: AuthorizeResult.DENY, }; }此时刷新页面你会看到 Add 按钮处于禁用状态用户不再被点下去才报错的糟糕交互所困扰。注意策略模块的搭建、permission.enabled: true的配置以及默认 allow-all 策略的移除步骤请参考 权限框架快速上手。用 RequirePermission 隐藏组件禁用是给用户的一个有用信号但在某些场景下你可能更倾向于直接隐藏无权限的元素。这时可以使用RequirePermission组件import { RequirePermission } from backstage/plugin-permission-react; import { todoListCreatePermission } from internal/plugin-todo-list-common; export const TodoListPage () { // ... return ( Grid container spacing{3} directioncolumn RequirePermission permission{todoListCreatePermission} errorPage{/} Grid item AddTodo onAdd{handleAdd} / /Grid /RequirePermission Grid item TodoList key{key} onEdit{setEdit} / /Grid /Grid ); }; // AddTodo 内部不再需要 usePermission恢复为普通按钮 function AddTodo({ onAdd }: { onAdd: (title: string) any }) { const title useRef(); return ( Typography variantbody1Add todo/Typography Box componentspan alignItemsflex-end displayflex flexDirectionrow TextField placeholderWrite something here... onChange{e (title.current e.target.value)} / Button variantcontained onClick{() onAdd(title.current)} Add /Button /Box / ); }改动之后没有权限的用户将完全看不到添加 todo的输入区。RequirePermission组件的errorPage属性可以自定义无权限时渲染什么如果不传则会回退到应用默认的NotFoundErrorPage组件plugins/permission-react/src/components/RequirePermission.tsx。上面示例中传了errorPage{/}即无权限时什么都不渲染实现纯隐藏。用 RequirePermission 保护路由RequirePermission同样可以包在路由元素外层用来拦截整个页面的访问。以packages/app/src/App.tsx为例import { RequirePermission } from backstage/plugin-permission-react; import { todoListCreatePermission } from internal/plugin-todo-list-common; const routes ( FlatRoutes Route path/search element{SearchPage /} {searchPage} /Route Route path/settings element{UserSettingsPage /} / Route path/todo-list element{ {/* 更严谨的做法是为页面单独声明一个 read 权限这里仅作示例 */} RequirePermission permission{todoListCreatePermission} TodoListPage / /RequirePermission } {/* ... */} /Route /FlatRoutes );现在直接访问https://localhost:3000/todo-list无权限用户会看到错误页面默认是 Not Found 页因为对无权用户隐藏路由的存在本身就是一种信息保护。源码级原理前端鉴权到底怎么跑了解上层 API 之后深入backstage/plugin-permission-react的源码可以看到几个值得注意的实现细节1. usePermissionSWR 缓存 资源权限短路usePermission内部通过useApi(permissionApiRef)拿到PermissionApi再用useSWR对鉴权结果做缓存plugins/permission-react/src/hooks/usePermission.ts。源码注释明确指出这里采用stale-while-revalidate策略目的是避免根据allowed结果做条件渲染时出现 UI 闪烁。另一个关键设计是资源权限的短路处理usePermission支持资源型权限ResourcePermission并接受可选的resourceRef。当传入的是资源权限但resourceRef尚未就绪例如实体还在异步加载中时Hook 会直接返回DENYplugins/permission-react/src/hooks/usePermission.ts而不是发起一次注定失败的请求——这是为了配合实体异步加载场景特意做的安全默认值。相应地此时allowed恒为falseUI 会表现为无权限状态直到resourceRef就绪后重新评估。2. IdentityPermissionApiDataLoader 请求批处理默认的PermissionApi实现是IdentityPermissionApi它使用DataLoader将同一微任务内发生的多次鉴权调用合并成一次 HTTP 请求plugins/permission-react/src/apis/IdentityPermissionApi.ts。这意味着一个页面上即使有十几个组件同时调用usePermission后端也只会收到一个批量的authorize请求性能开销极小。3. RequirePermission三态渲染RequirePermission内部其实就是对usePermission的一层封装plugins/permission-react/src/components/RequirePermission.tsxloading为true时返回null什么都不渲染allowed为true时渲染children否则渲染errorPage若提供否则回退到app.getComponents()中的NotFoundErrorPage。所以它天然适合作为路由级守卫或区块级守卫把鉴权逻辑从业务组件中剥离出去。测试验证前端鉴权组件的行为契约仓库为这两个前端鉴权 API 提供了完整的单元测试可以作为理解其行为的契约文档usePermission.test.tsx 验证了三件事鉴权请求未返回时状态为loading返回ALLOW时allowed为真并渲染内容返回DENY时不渲染内容。RequirePermission.test.tsx 验证了五种行为加载中不渲染子元素有权限时渲染子元素无权限时默认渲染 Not Found 页测试中通过 Reached NotFound Page 断言无权限时可用errorPage渲染自定义错误页以及资源权限 resourceRef的鉴权组合也能正常工作。如果你的插件需要为前端鉴权写测试可以参考这两个文件用TestApiProvider注入 mock 的permissionApiRef即可在测试环境中完全控制鉴权结果。小结给 Backstage 插件加前端鉴权核心就是两条路禁用用usePermission拿到loading/allowed在加载完成后禁用无权限的操作按钮保留可见性但明确不可用隐藏/拦截用RequirePermission包裹组件或路由无权限时不渲染可自定义errorPage或显示 Not Found 页。无论选择哪种方式都请牢记前端鉴权只是用户体验优化真正的安全边界永远在后端路由。完整的权限策略编写与资源权限鉴权可继续阅读 编写权限策略、添加基础权限检查 与 添加资源权限检查。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
