Refine 访问控制 Provider 完整指南:用 RBAC/ABAC 构建安全的 React 管理后台
Refine 访问控制 Provider 完整指南用 RBAC/ABAC 构建安全的 React 管理后台【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine本文围绕 Refine 的accessControlProvider展开系统讲解如何在管理后台、内部工具等 React 应用中接入统一、与 UI 框架无关的访问控制层覆盖can方法接口、CanAccess /组件、useCanHook、按钮与菜单的默认检测点、缓存性能优化以及 Casbin/CASL/Cerbos/AccessControl.js 等生态集成方式。读完本文你将能够为 Refine 应用实现 RBAC基于角色的访问控制与 ABAC基于属性的访问控制并让菜单、操作按钮与页面级路由自动遵循权限规则。背景为什么需要 Provider 化的访问控制访问控制是一个宽泛的主题业界存在大量功能各异的成熟方案。Refine 的做法是不绑定任何一种具体实现而是通过accessControlProvider暴露一套与 UI 无关的通用 API。无论你选用基于角色的 RBAC、基于属性的 ABAC还是 ACL 等模型也无论底层是 [Casbin]、[CASL]、[Cerbos] 还是 [AccessControl.js]都可以通过同一个 Provider 接入 Refine。这样设计带来的直接收益是业务组件按钮、菜单、路由只关心能否执行某动作不关心权限策略如何计算权限策略可以集中在一处Provider 的can方法管理便于审计与替换切换底层授权库时UI 层代码无需改动。accessControlProvider的最小接口要让访问控制生效accessControlProvider至少要提供一个异步方法can其签名如下export interface IAccessControlContext { can?: ({ resource, action, params }: CanParams) PromiseCanResponse; options?: { buttons?: { enableAccessControl?: boolean; hideIfUnauthorized?: boolean; }; queryOptions?: UseQueryOptionsCanReturnType; }; }一个最简实现长这样const accessControlProvider: IAccessControlContext { can: async ({ resource, action, params, }: CanParams): PromiseCanResponse { return { can: true }; }, options: { buttons: { enableAccessControl: true, hideIfUnauthorized: false, }, queryOptions: { // ... default global query options }, }, };关于options的三项默认值文档与源码是一致的enableAccessControl默认为true按钮默认开启访问控制检测hideIfUnauthorized默认为false未授权时按钮默认被禁用而不是隐藏queryOptions默认为undefined不额外配置全局查询选项。你可以在 Provider 层面全局配置按钮行为也可以在每个按钮上单独覆盖当按钮自身未配置时会回退到options.buttons中定义的全局配置。接口类型的源码依据在 packages/core/src/contexts/accessControl/types.ts 中可以看到这些类型的真实定义CanResponse{ can: boolean; reason?: string; [key: string]: unknown }即必须返回can布尔值reason用于说明拒绝原因CanParams{ resource?: string; action: string; params?: {...} }resource为资源名action为要执行的动作params中可携带id、resource对象及其他自定义字段CanFunction({ resource, action, params }) PromiseCanReturnType其中CanReturnType为{ can: boolean; reason?: string }。此外AccessControlContext的默认值也在 packages/core/src/contexts/accessControl/index.tsx 中给出了印证默认enableAccessControl: true、hideIfUnauthorized: false。在Refine /中接入 Provider将 Provider 传给Refine /组件即可全局生效const App: React.FC () { return ( Refine // other providers and props accessControlProvider{{ can: async ({ resource, action, params }) { if (resource posts action edit) { return { can: false, reason: Unauthorized, }; } return { can: true }; }, options: { buttons: { enableAccessControl: true, hideIfUnauthorized: false, }, queryOptions: { // ... default global query options }, }, }} {/* your app */} /Refine ); };:::caution 只把accessControlProvider传给Refine /并不会自动强制执行访问控制你还需要用CanAccess /组件包裹受保护的路由内容或在路由层面配置对应机制。根据你使用的路由方案可参考React Router 访问控制NextJS Router 访问控制Remix Router 访问控制 :::Meta Access利用资源元信息实现 ABACcan方法收到的resource参数正是你传给Refine /的ResourceProps资源对象本身。这意味着你可以读取资源上的meta字段从而基于资源属性值来做权限判断——这就是典型的属性级访问控制ABACexport const accessControlProvider { can: async ({ resource, action, params }) { const resourceName params?.resource?.name; const anyUsefulMeta params?.resource?.meta?.yourUsefulMeta; if ( resourceName posts anyUsefulMeta true action edit ) { return { can: false, reason: Unauthorized, }; } }, };从源码看useCan在调用can之前会通过sanitizeResource对resource对象做处理见 packages/core/src/hooks/accessControl/useCan/index.ts其目的是移除icon等 React 元素属性——因为这些元素会导致 react-query 序列化 queryKey 时出现循环引用报错详见该文件注释中引用的 issue #2220同时保留meta、name等可用信息。使用reason属性提升体验如果can方法返回的响应中带有reason属性当按钮因未授权被禁用时该reason会显示在按钮的 tooltip 中。这让为什么点不了变得对用户透明。Hooks 与组件useCan与CanAccess /Refine 提供了两个入口来调用can方法Hook 形式的useCan与组件形式的CanAccess /。useCanHookuseCan以can作为react-query的useQuery查询函数接收与can相同的参数可通过queryOptions配置查询行为并返回useQuery的结果。const { data } useCan({ resource: resource-you-ask-for-access, action: action-type-on-resource, params: { foo: optional-params }, queryOptions: { cacheTime: 5000, // ... other query options }, });其类型签名如下const useCan: ({ action, resource, params, queryOptions, }: CanParams) UseQueryResultCanReturnType在 packages/core/src/hooks/accessControl/useCan/index.ts 的实现中值得注意的细节通过useContext(AccessControlContext)读取can与全局queryOptions并将两者合并组件级配置优先enabled默认取决于typeof can ! undefined即未提供 Provider 时查询不会发起未提供can时useCan直接返回{ data: { can: true } }——即没有访问控制 Provider 时默认放行retry被显式设置为false权限判定失败不会自动重试内部通过useKeys().access().resource(...).action(...).params(...)构建稳定的 queryKey保证缓存命中。CanAccess /组件CanAccess /是useCan的组件形态包装内部调用useCan完成权限检查若返回true则渲染 children否则渲染fallback未提供时渲染null。CanAccess resourceposts actionedit params{{ id: 1 }} fallback{CustomFallback /} queryOptions{{ cacheTime: 25000 }} YourComponent / /CanAccess自动推断 resource 与 actionCanAccess /的智能之处在于不传resource/action时它会根据当前路由自动推断。例如位于/posts路由时会以{ resource: posts, action: list }进行校验位于/posts/show/:id时action推断为showid也会被自动解析出来。这背后依赖useResourceParams见 packages/core/src/components/canAccess/index.tsx。覆盖推断结果当推断结果不符合需求时可以显式传入 props。例如在/posts/show/:id页面中你希望对category资源授权delete动作import { CanAccess } from refinedev/core; export const MyComponent () { return ( Buttons CreateButtonCreate/CreateButton CanAccess resourceposts actiondelete DeleteButtonDelete/DeleteButton /CanAccess /Buttons ); };其他 PropsonUnauthorized当useCan返回can: false时触发的回调可接收{ resource, reason, action, params }CanAccess onUnauthorized{({ resource, reason, action, params }) console.log( You cannot access ${resource}-${params.id} resource with ${action} action because ${reason}, ) } YourComponent / /CanAccessfallback未授权时渲染的内容为undefined时渲染nullCanAccess fallback{divYou cannot access this section/div} YourComponent / /CanAccessqueryOptions接受UseQueryOptionsCanReturnType以定制底层查询的缓存行为CanAccess queryOptions{{ cacheTime: 25000 }} YourComponent / /CanAccess在组件实现中children为合法 React 元素时还会通过React.cloneElement透传多余 props见 packages/core/src/components/canAccess/index.tsx。性能利用缓存减少权限校验开销随着应用中访问控制检测点的增多性能可能受到影响尤其是权限校验涉及远端接口时。既然 Refine 基于 react-query最直接的优化手段就是配置staleTime与cacheTime// inside your component const { data } useCan({ resource: resource-you-ask-for-access, action: action-type-on-resource, params: { foo: optional-params }, queryOptions: { staleTime: 5 * 60 * 1000, // 5 minutes // ... other query options }, });:::note 对于 Refine 自身的访问控制检测点默认使用5 分钟cacheTime与0 分钟staleTime。这意味着数据在 5 分钟内不会重复请求但超过 0 分钟后即视为过期、会在重新挂载时触发后台重新校验从而在安全性与性能之间取得平衡。 :::另外Refine 还提供了不经缓存的校验入口useCanWithoutCache见 packages/core/src/hooks/accessControl/useCanWithoutCache.ts。它直接从AccessControlContext读取can仅在每次调用时对resource做sanitizeResource清洗不参与 react-query 缓存适合对实时性要求极高的场景。默认访问控制检测点Refine 内置了对常用 UI 区域的访问控制检测无需你手动接入。Sider侧边菜单Sider 已深度集成访问控制不可访问的资源不会出现在侧边菜单中。每个菜单项会以{ resource, action: list }进行校验。例如应用中存在posts资源则校验参数为{ resource: posts, action: list }。Buttons操作按钮以下按钮都会进行访问控制检测当can返回{ can: false }时按钮会被禁用而非隐藏import { EditButton, ShowButton, ListButton, CreateButton, CloneButton, DeleteButton } from refinedev/antd; // or refinedev/mui, refinedev/chakra-ui, refinedev/mantine export const MyPage () { return ( My Page {/* These buttons will be disabled if access control returns { can: false } */} ListButton resourceposts / {/* { resource: posts, action: list, params: { *resource } } */} CreateButton resourceposts / {/* { resource: posts, action: create, params: { *resource } } */} CloneButton resourceposts recordItemId{1} / {/* { resource: posts, action: create, params: { id: 1, *resource } } */} EditButton resourceposts recordItemId{1} / {/* { resource: posts, action: edit, params: { id: 1, *resource } } */} DeleteButton resourceposts recordItemId{1} / {/* { resource: posts, action: delete, params: { id: 1, *resource } } */} ShowButton resourceposts recordItemId{1} / {/* { resource: posts, action: show, params: { id: 1, *resource } } */} / ); };各按钮的校验参数约定ListButton→{ resource: posts, action: list }CreateButton→{ resource: posts, action: create }CloneButton→{ resource: posts, action: create, params: { id: 1 } }克隆动作映射为createEditButton→{ resource: posts, action: edit, params: { id: 1 } }DeleteButton→{ resource: posts, action: delete, params: { id: 1 } }ShowButton→{ resource: posts, action: show, params: { id: 1 } }这些按钮的底层逻辑统一封装在useButtonCanAccessHook见 packages/core/src/hooks/button/button-can-access/index.tsx其行为要点enabled取按钮自身accessControl?.enabled未配置时回退到全局options.buttons.enableAccessControlhideIfUnauthorized同理未配置时回退到全局配置未授权时的 tooltip 文案优先使用can返回的reason否则使用 i18n 翻译键buttons.notAccessTitle默认文案为 You dont have permission to accessclone动作会被映射为create进行校验与上文 CloneButton 的约定一致。:::simple 如果希望隐藏未授权的按钮而不是禁用它们只需在accessControlProvider的options中传入hideIfUnauthorized: true即可。 :::生态集成与完整示例由于accessControlProvider只是薄薄一层异步接口业界主流授权库都能轻松接入。仓库中的 access-control-casbin 示例演示了如何基于Casbin实现 RBAC/ABAC 策略策略模型文件与配置位于examples/access-control-casbin/src下同时仓库还提供 access-control-cerbos集成 Cerbos与 access-control-permify集成 Permify两个参考实现三者结构一致、可直接对照阅读。Casbin以.conf模型 CSV 策略文件表达权限规则适合需要策略文件化管理的团队Cerbos策略以 YAML 定义、可通过 PDP策略决策点服务远程评估Permify提供细粒度的关系型权限建模适合复杂多租户场景。你也可以在 documentation/docs/authorization/access-control-provider/index.md 查看本文对应的原始文档并配合 documentation/docs/authorization/components/can-access/index.md 与 documentation/docs/authorization/hooks/use-can/index.md 阅读CanAccess /与useCan的完整 API 细节。小结Refine 的访问控制体系可以用一条主线概括一个can异步方法 两个调用入口useCan/CanAccess / 一组内置检测点Sider 与各类按钮。can方法决定能否useCan让你在任意组件中编程式询问CanAccess /让你声明式地包裹受保护内容而 Sider 与按钮则自动遵循权限结果。配合 react-query 的缓存能力与reason提示你可以在几乎零 UI 侵入的前提下为管理后台落地一套清晰、可审计、可替换的 RBAC/ABAC 权限体系。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考