Refine v5 接入 Cerbos:基于 accessControlProvider 的 RBAC 访问控制实战

发布时间:2026/9/12 7:02:46
Refine v5 接入 Cerbos:基于 accessControlProvider 的 RBAC 访问控制实战 Refine v5 接入 Cerbos基于 accessControlProvider 的 RBAC 访问控制实战【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine访问控制是后台管理系统中最复杂也最容易被忽视的环节之一涉及 RBAC、ABAC、ACL 等多种模型与大量细粒度授权场景。本篇文章基于 Refine 官方示例access-control-cerbos完整讲解如何将 Cerbos一个以策略为核心的授权服务通过 Refine 的accessControlProvider接入应用实现角色切换、字段级鉴权、路由级鉴权与按钮自动禁用读完即可在真实 React 管理后台中复刻整套方案。示例概览Refine Cerbos 的分工Refine 提供了一套与具体授权方案解耦的 API——accessControlProvider。它不关心你背后用的是 RBAC 还是 ABAC也不关心是本地规则还是远程授权服务只要求你暴露一个异步的can方法用于回答某资源上的某动作是否被允许。Cerbos 则是这套 API 的策略决策引擎你在 Cerbos 端编写策略Policy由 Cerbos 的 PDPPolicy Decision Point根据请求中的主体principal、资源resource与动作actions给出允许/拒绝结论。在 examples/access-control-cerbos 示例中二者的分工非常清晰Refine 负责渲染页面、表格、按钮并在关键位置触发鉴权请求Cerbos 负责根据当前用户的角色admin / editor与目标资源posts / users / categories做出授权判断角色通过顶部 Header 的 Radio 切换并写入localStorage刷新后生效方便直观演示不同角色看到的不同界面。示例的技术栈为 React 19 Ant Design 5 React Router 7依赖了cerbos/http、refinedev/core、refinedev/antd、refinedev/simple-rest等见 package.json数据源使用https://api.fake-rest.refine.dev。快速运行示例在本地启动该示例只需一条命令npm create refine-applatest -- --example access-control-cerboscreate-refine-app会自动拉取仓库中的 examples/access-control-cerbos 目录并安装依赖。之后在项目内执行npm run dev即可在浏览器中打开应用。页面顶部会出现 Admin / Editor 两个角色按钮切换角色后应用会重新加载界面上的菜单项、操作按钮与字段随之发生变化。核心实现将 Cerbos 封装为 accessControlProvider整个示例的关键代码集中在 src/App.tsx。第一步是初始化 Cerbos 的 HTTP 客户端指向 Cerbos 的托管 PDPimport { HTTP as Cerbos } from cerbos/http; // The Cerbos PDP instance const cerbos new Cerbos(https://demo-pdp.cerbos.cloud, { playgroundInstance: WS961950bd85QNYlAvTmJYubP0bqF7e3, // The playground instance ID to test });这里使用的是 Cerbos 托管的演示 PDP 与 Playground 实例示例中的策略定义在 Cerbos Playground 中维护。代码注释明确指出生产环境建议在自己的应用旁边以容器方式部署 PDP 实例而不是依赖演示端点。第二步是在Refine /组件上挂载accessControlProvider将 Cerbos 的checkResource调用包装成 Refine 期望的can方法Refine routerProvider{routerProvider} dataProvider{dataProvider(API_URL)} accessControlProvider{{ can: async ({ action, params, resource }) { const result await cerbos.checkResource({ principal: { id: demoUser, // Fake a user ID roles: [role], policyVersion: default, // this is where user attributes can be passed attributes: {}, }, resource: { kind: resource ?? , policyVersion: default, id: ${params?.id} || new, attributes: params, }, // the list of actions on the resource to check authorization for actions: [action], }); return Promise.resolve({ can: result.isAllowed(action) || false, }); }, }} resources{[ { name: posts, list: /posts, show: /posts/show/:id, create: /posts/create, edit: /posts/edit/:id, meta: { canDelete: true } }, { name: users, list: /users, show: /users/show/:id, create: /users/create, edit: /users/edit/:id }, { name: categories, list: /categories, show: /categories/show/:id, create: /categories/create, edit: /categories/edit/:id }, ]} ... 这段代码值得逐点拆解它体现了 Refine 的CanParams与 Cerbos 请求模型之间的映射principal主体id为模拟用户demoUserroles从localStorage读取的roleadmin或editorpolicyVersion为defaultattributes用于承载用户级属性——这是 ABAC基于属性的访问控制的接入点resource资源kind直接取自 Refine 的resource参数即 posts / users / categoriesid取params?.id新建场景下回退为newattributes透传 Refine 的params可用于携带资源字段信息actions动作将 Refine 的actionlist / create / edit / show / delete / field 等原样交给 Cerbos 判定返回结构can方法返回PromiseCanResponse其中can取result.isAllowed(action)的结果。通过这种包装Refine 内部所有的鉴权点——Sider 菜单、各类按钮、useCan、CanAccess /——都会自动走这条链路。理解 accessControlProvider 接口can方法背后的接口定义可以从 Access Control Provider 文档 中看到完整形态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 }, }, };关键约定如下can至少接收{ resource, action, params }resource是你在Refine /的resources中声明的资源对象注意在 v5 中它是完整的资源配置对象而非字符串params中通常携带id等记录级信息返回值CanResponse包含can: boolean可选reason: string——当按钮因无权而被禁用时reason会显示在按钮的 tooltip 中options.buttons是全局按钮行为配置enableAccessControl默认truehideIfUnauthorized默认false即默认禁用而非隐藏无权按钮。单个按钮可独立覆盖未配置时回退到全局值options.queryOptions用于全局配置can查询的 react-query 选项。注意仅把accessControlProvider传给Refine /并不会自动强制访问控制还需要用CanAccess /包裹受保护的路由或组件详见 useCan 文档 与 CanAccess 文档。另外can方法中可以通过params?.resource拿到完整的资源配置对象包括自定义的meta字段从而在资源声明的元数据层面做 ABAC 判定例如当资源的 meta 中某个标志为 true 时禁止编辑。字段级鉴权用 useCan 控制表格列示例中最能体现细粒度的部分在 posts/list.tsx它用useCan对posts资源上自定义的field动作做鉴权决定是否渲染 Hit 列const { data: canAccess } useCan({ resource: posts, action: field, params: { field: hit }, });然后在表格列定义中根据结果条件渲染{canAccess?.can ( Table.Column dataIndexhit titleHit render{(value: number) ( NumberField value{value} options{{ notation: compact }} / )} / )}useCan本质上把can方法作为 react-query 的查询函数封装签名可见 useCan 文档因此它支持queryOptions。这意味着你可以针对频繁调用的鉴权点做缓存配置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 }, });默认情况下Refine 自身的访问控制点使用5 分钟 cacheTime、0 分钟 staleTime见 Access Control Provider 文档 的 Performance 一节。如果鉴权涉及远程端点本示例正是如此缓存能显著减少对 PDP 的请求量。路由级鉴权用 CanAccess 保护整块区域在 App.tsx 中示例在布局层使用CanAccess /包裹所有子路由的OutletThemedLayout Header{() Header role{role} /} CanAccess Outlet / /CanAccess /ThemedLayoutCanAccess /内部使用useCan做鉴权鉴权通过则渲染 children否则渲染fallback未提供时不渲染任何内容。它的完整签名支持resource、action、params、fallback与queryOptionsCanAccess resourceposts actionedit params{{ id: 1 }} fallback{CustomFallback /} queryOptions{{ cacheTime: 25000 }} YourComponent / /CanAccess与此同时Refine 的默认访问控制点会自动生效Sider 菜单菜单项以{ resource, action: list }发起鉴权无权的资源不会出现在侧边栏中——这就是切换角色后菜单随之变化的原因按钮组件ListButton、CreateButton、CloneButton、EditButton、DeleteButton、ShowButton在渲染时会按对应action自动发起鉴权例如EditButton对应{ resource: posts, action: edit, params: { id: 1 } }返回{ can: false }时按钮被禁用hideIfUnauthorized: true时按钮被隐藏。从源码结构看Cerbos 的职责被完全收敛在accessControlProvider内部页面组件无需感知 Cerbos 的存在——这正是 Refine 这套抽象的价值未来把 Cerbos 换成 Casbin、CASL 或自研授权服务页面层代码几乎不用改动。角色切换Header 与 localStoragecomponents/header.tsx 实现了示例的交互入口——一个 Ant Design 的Radio.Group提供 Admin 与 Editor 两个角色Radio.Group value{role} onChange{(event) { localStorage.setItem(role, event.target.value); location.reload(); }} Radio.Button valueadminAdmin/Radio.Button Radio.Button valueeditorEditor/Radio.Button /Radio.Group切换角色后写入localStorage并刷新页面App.tsx通过localStorage.getItem(role) ?? admin重新读取角色进而影响accessControlProvider中principal.roles的值。role通过 props 传给 Header 用于高亮当前选中项。在真实项目中角色通常来自登录态如 JWT、用户 Profile而非localStorage示例用localStorage是为了在无后端认证的情况下方便演示。接入时只需把principal.id换成真实用户 ID、roles换成服务端下发的角色列表并考虑在登录/登出时刷新 Cerbos 客户端或鉴权查询缓存。相关文件索引示例入口与accessControlProvider完整实现examples/access-control-cerbos/src/App.tsx角色切换 Headerexamples/access-control-cerbos/src/components/header.tsx字段级鉴权useCan 条件渲染列examples/access-control-cerbos/src/pages/posts/list.tsx其余页面posts 增删改查、users、categoriesexamples/access-control-cerbos/src/pages/依赖与脚本examples/access-control-cerbos/package.json本地运行说明examples/access-control-cerbos/README.mdaccessControlProvider接口与默认访问控制点documentation/docs/authorization/access-control-provider/index.mduseCan钩子参考documentation/docs/authorization/hooks/use-can/index.mdCanAccess /组件参考documentation/docs/authorization/components/can-access/index.md小结通过access-control-cerbos示例可以总结出一条清晰的接入路径初始化 Cerbos 客户端 → 在accessControlProvider.can中把CanParams映射为checkResource请求 → 在需要保护的路由外包CanAccess /→ 用useCan实现字段级等自定义鉴权 → 借助options.buttons统一控制按钮的禁用/隐藏行为。由于 Refine 已内置 Sider、按钮等默认访问控制点这套方案可以用极少的样板代码把 Cerbos 的策略能力铺满整个后台应用同时保持页面代码与授权引擎的解耦。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考