尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Refine v5 集成 Casbin 实现 RBAC 访问控制:完整示例与源码级剖析

Refine v5 集成 Casbin 实现 RBAC 访问控制:完整示例与源码级剖析 Refine v5 集成 Casbin 实现 RBAC 访问控制完整示例与源码级剖析【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读访问控制Access Control是现代企业级 React 管理后台中最复杂、也最关键的模块之一。Refine v5 通过内置的accessControlProvider抽象层提供了一套与具体权限方案解耦的 API可以灵活接入 RBAC、ABAC、ACL 等模型。本篇文章以仓库中的官方示例 examples/access-control-casbin 为主体完整讲解如何在 Refine v5 应用中集成 Casbin 权限库从 Casbin 模型model与策略policy的定义到accessControlProvider.can的实现再到路由级CanAccess /保护与字段级useCan控制一步步还原一个权限策略单一来源、前端各处统一裁决的实战方案。读完本文你将掌握在 Refine 应用中落地 RBAC 字段级 ABAC 控制的完整套路并能直接复刻到自己的管理后台项目中。一、示例概览一个双角色 三资源的权限演示应用access-control-casbin示例在 Refine 官方文档中被收录于 documentation/docs/examples/access-control/casbin.md它演示的核心思路是访问控制是一个复杂的主题借助 Casbin 这类成熟权限库可以把权限判定逻辑从业务代码中剥离出来集中管理。示例应用的业务模型非常清晰两个角色admin管理员与editor编辑三个资源posts文章、users用户、categories分类四种动作list、create、edit、show、delete外加一个自定义动作field用于字段级显隐控制一个 REST 数据源https://api.fake-rest.refine.devApp.tsx示例的完整目录结构如下其中src/accessControl.ts是整个权限系统的核心其余页面组件则演示了权限在 UI 各层级的落地方式examples/access-control-casbin/ ├── src/ │ ├── accessControl.ts # Casbin model policy 定义 │ ├── App.tsx # accessControlProvider 集成与路由 │ ├── components/header.tsx # 角色切换控件 │ ├── interfaces/index.d.ts # 业务类型定义 │ └── pages/ │ ├── posts/ # 文章页含字段级权限演示 │ ├── users/ # 用户页 │ └── categories/ # 分类页 ├── package.json ├── vite.config.js └── README.md提示示例配套的文档入口页还指向了 Refine 的 Access Control Provider 文档其中定义了accessControlProvider的标准接口本文后面的源码分析会引用该接口作为对照。二、快速运行三行命令启动示例与仓库中其他示例一致access-control-casbin可以通过 Refine CLI 直接创建并运行见 README.mdnpm create refine-applatest -- --example access-control-casbin进入项目后package.jsonexamples/access-control-casbin/package.json提供了三个核心脚本scripts: { build: tsc refine build, dev: refine dev, start: refine start }dev启动本地开发服务器是日常开发最常用的命令build先执行 TypeScript 类型检查tsc再由refine build产出生产构建start启动生产模式服务器。示例运行后会以Admin角色登录首页默认进入posts列表页页头提供了 Admin / Editor 两个角色的切换按钮切换即触发权限重新判定非常适合直观观察不同角色下的界面差异。三、权限规则单一来源Casbin 模型Model解析示例把整套权限规则收敛在一个文件里——src/accessControl.ts。它首先通过 Casbin 的newModel定义了权限判定模型import { newModel, StringAdapter } from casbin; export const model newModel( [request_definition] r sub, obj, act [policy_definition] p sub, obj, act, eft [role_definition] g _, _ [policy_effect] e some(where (p.eft allow)) !some(where (p.eft deny)) [matchers] m g(r.sub, p.sub) keyMatch(r.obj, p.obj) regexMatch(r.act, p.act) );这个模型由五个标准 section 组成每一段都对应 Casbin 权限引擎的一个核心概念Section内容含义request_definitionr sub, obj, act一次权限请求的入参三元组主体 sub谁通常是角色名、客体 obj对什么如posts/1、动作 act做什么如editpolicy_definitionp sub, obj, act, eft策略行的结构。注意这里多了第四列efteffect允许策略显式声明allow或deny这是实现黑名单显式拒绝的关键role_definitiong _, _角色继承关系定义g表示从属关系如 editor 属于某组_, _表示两个成员父、子policy_effectsome(where (p.eft allow)) !some(where (p.eft deny))裁决规则当存在至少一条allow策略、且不存在任何deny策略时请求被允许。这意味着显式拒绝优先于允许是 RBAC 中非常常见的默认允许 黑名单兜底模型matchersm g(r.sub, p.sub) keyMatch(r.obj, p.obj) regexMatch(r.act, p.act)匹配表达式请求主体与策略主体通过角色继承关系g匹配客体用keyMatch支持*通配符的路径匹配如posts/*可命中posts/1动作用regexMatch正则匹配因此策略中可以写(edit)\|(show)这类正则分支这个模型设计蕴含了两个值得一提的实践要点角色与权限分离g _, _把用户与角色解耦。虽然本示例直接用角色名admin/editor作为sub但在真实系统中sub可以是用户 ID通过g的关系表把用户归入角色从而实现标准的 RBAC。策略效果可显式声明eft列的加入让同一资源上可以同时存在允许与拒绝策略配合policy_effect的!some(...deny)子句天然支持例外规则例如下文 editor 对posts/hit字段的显式拒绝。四、策略数据StringAdapter 与内联 CSV 策略模型只定义了如何判定具体的权限规则则由策略policy承载。示例通过 Casbin 的StringAdapter直接以内联 CSV 字符串方式提供策略src/accessControl.tsexport const adapter new StringAdapter( p, admin, posts, (list)|(create) p, admin, posts/*, (edit)|(show)|(delete) p, admin, posts/*, field p, admin, users, (list)|(create) p, admin, users/*, (edit)|(show)|(delete) p, admin, categories, (list)|(create) p, admin, categories/*, (edit)|(show)|(delete) p, editor, posts, (list)|(create) p, editor, posts/*, (edit)|(show) p, editor, posts/hit, field, deny p, editor, categories, list );逐行解读这份策略表可以看出示例刻意演示了 Casbin 的多种表达能力集合级权限p, admin, posts, (list)|(create)表示 admin 对posts这个集合资源拥有list与create权限。这里(list)|(create)是一个正则分支与模型中的regexMatch(r.act, p.act)相呼应——动作列直接使用正则表达式。通配路径权限p, admin, posts/*, (edit)|(show)|(delete)中的posts/*由keyMatch负责匹配可命中posts/1、posts/42等任意具体记录表示 admin 可以编辑、查看、删除任意文章。自定义动作fieldp, admin, posts/*, field为字段级权限预留了入口。当请求posts/hitfield时posts/*通配能命中表示 admin 可以查看所有文章的hit浏览量字段。显式拒绝denyp, editor, posts/hit, field, deny是全文最关键的一行——editor 对posts/hit的field动作显式拒绝。由于模型中的policy_effect是some(where (p.eft allow)) !some(where (p.eft deny))即使存在其他允许策略这条 deny 也会让判定最终失败实现了字段级黑名单。角色能力差异对比 admin 与 editor 的策略可以发现editor 没有users的任何权限对categories只有list无create对文章也只有(edit)|(show)无delete——这正是权限随角色收敛的直观示范。在实际项目中StringAdapter可以被替换为FileAdapter从 CSV 文件加载或数据库 Adapter如TypeORMAdapter/SequelizeAdapter从而让权限规则交由运维动态维护而不必改代码重新部署。这也是 Casbin 的一大卖点策略与代码解耦。五、桥梁把 Casbin 接到 Refine 的 accessControlProvider有了模型与策略接下来就是本示例最核心的集成环节在 App.tsx 中把 Casbin 的 Enforcer 包装成 Refine 的accessControlProvider。先看 Refine 侧的标准接口定义见 documentation/docs/authorization/access-control-provider/index.mdexport interface IAccessControlContext { can?: ({ resource, action, params }: CanParams) PromiseCanResponse; options?: { buttons?: { enableAccessControl?: boolean; hideIfUnauthorized?: boolean; }; queryOptions?: UseQueryOptionsCanReturnType; }; }示例中的实现如下accessControlProvider{{ can: async ({ action, params, resource }) { const enforcer await newEnforcer(model, adapter); if ( action delete || action edit || action show ) { return Promise.resolve({ can: await enforcer.enforce( role, ${resource}/${params?.id}, action, ), }); } if (action field) { return Promise.resolve({ can: await enforcer.enforce( role, ${resource}/${params?.field}, action, ), }); } return { can: await enforcer.enforce(role, resource, action), }; }, }}这段代码体现了 Refine 权限 API 的三种典型调用形态记录级操作edit / show / delete当操作对象是某条具体记录时把客体obj拼接为${resource}/${params?.id}例如posts/5交给enforcer.enforce(role, posts/5, edit)裁决。params?.id由 Refine 自动从路由参数中注入。字段级操作field这是示例自定义的动作类型客体拼接为${resource}/${params?.field}例如posts/hit用于裁决某个字段是否可见。params?.field来自调用方显式传入下文第六节的useCan会演示。集合级操作list / create 等其余动作直接把resource本身作为客体例如enforcer.enforce(role, posts, list)对应策略中的集合级权限行。需要注意示例中的role取自localStorage.getItem(role) ?? adminApp.tsx即当前登录用户的角色。真实项目中这个值应来自认证状态如authProvider的getIdentity结果。按 Refine 官方文档的约定can方法的返回类型是PromiseCanResponse其中can: boolean表示是否放行可选的reason字段用于说明拒绝原因。示例中返回的是{ can: ... }这一最简形式。六、路由级保护 包裹整个布局Refine 官方文档在accessControlProvider的说明中特别强调了一条关键注意事项见 documentation/docs/authorization/access-control-provider/index.md仅仅向Refine /传入accessControlProvider并不会自动强制执行访问控制你需要用CanAccess /组件包裹受保护的路由。示例正是这样做的App.tsxRoute element{ ThemedLayout Header{() Header role{role} /} CanAccess Outlet / /CanAccess /ThemedLayout } 这里CanAccess包裹了 React Router 的Outlet /即所有子路由都在权限裁决通过后才能渲染。当can判定为false时CanAccess /会渲染无权访问的提示Refine 内置的未授权界面从根上阻断未授权路由的访问。配套地resources配置中为posts声明了meta.canDelete: trueApp.tsx用于配合列表页中删除按钮的权限判定。路由结构上示例注册了三个资源的完整 CRUD 页面并把默认首页重定向到文章列表Route index element{NavigateToResource resourceposts /} / Route path/posts Route index element{PostList /} / Route pathcreate element{PostCreate /} / Route pathedit/:id element{PostEdit /} / Route pathshow/:id element{PostShow /} / /Route Route path/users ... /Route Route path/categories ... /Route Route path* element{ErrorComponent /} /同时Refine 的权限机制还会自动渗透到 UI 层EditButton、ShowButton、DeleteButton等组件默认会调用can进行前置校验accessControlProvider.options.buttons.enableAccessControl默认为true。当hideIfUnauthorized为true时无权操作的按钮会直接被隐藏。这解释了为什么 Editor 角色登录后文章列表中的编辑/查看按钮行为会与 Admin 不同——即使按钮本身没有任何手动判断逻辑。七、字段级控制useCan 实现 ABAC 式显隐除了路由与按钮示例还演示了更精细的字段级权限——这在列表页 src/pages/posts/list.tsx 中体现得淋漓尽致。页面通过useCan钩子发起一次自定义动作的权限查询const { data: canAccess } useCan({ resource: posts, action: field, params: { field: hit }, });随后根据返回值决定是否渲染hit浏览量列{canAccess?.can ( Table.Column dataIndexhit titleHit render{(value: number) ( NumberField value{value} options{{ notation: compact }} / )} / )}这条链路完整串起了前文的所有要素useCan发出{ resource: posts, action: field, params: { field: hit } }Refine 调用accessControlProvider.can命中action field分支组装客体posts/hitCasbin Enforcer 执行裁决对admin而言p, admin, posts/*, field命中posts/*通配匹配posts/hit→ 允许对editor而言虽然也有p, editor, posts/*, (edit)|(show)但那条策略的act是(edit)|(show)而非field同时存在显式拒绝p, editor, posts/hit, field, deny→ 被!some(...deny)拦截最终拒绝结果can: false让hit列对 Editor 隐藏。这就是一个典型的ABAC基于属性的访问控制落地权限不仅取决于角色是谁还取决于对象上的哪个字段。由于 Refine 的accessControlProvider本身是 agnostic与具体权限库解耦的这个模式可以平滑迁移到 CASL、Cerbos、AccessControl.js 等其他权限方案。八、角色切换Header 组件与运行时重载示例的 src/components/header.tsx 提供了一个基于 Ant DesignRadio.Group的角色切换器它被注入ThemedLayout的Header插槽export const Header: React.FCHeaderProps ({ role }) { return ( AntdLayout.Header style{{ ... }} 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 /AntdLayout.Header ); };其工作机制是角色写入localStorage键名为role调用location.reload()重新加载应用App.tsx在渲染入口读取localStorage.getItem(role) ?? admin把角色传入accessControlProvider.can与 Header 组件。由于can每次判定都会新建 Enforcer 并以当前role作为sub角色切换后的页面刷新即可完整看到权限差异切换为 Editor 后用户页入口会被拦截、文章列表的hit列消失、部分操作按钮行为变化。这也是示例用于教学演示的最小化认证方案——在实际产品中角色应来源于服务端会话或认证 Provider而不是localStorage。九、依赖与版本复刻示例所需的技术栈从 package.json 可以看到该示例的完整依赖栈这决定了复刻示例所需的运行环境依赖版本作用refinedev/core^5.0.12Refine v5 核心提供CanAccess、useCan、accessControlProvider等refinedev/antd^6.0.3Ant Design 集成包ThemedLayout、useTable、按钮组件等refinedev/simple-rest^6.0.1指向 fake REST API 的数据提供器refinedev/react-router^2.0.4React Router 集成casbin^5.15.2权限引擎本体newModel、newEnforcer、StringAdapterreact/react-dom^19.1.0React 19antd^5.23.0UI 组件库uiw/react-md-editor^4.0.8文章编辑页的 Markdown 编辑器vite^5.4.15构建工具另外需要注意engines字段要求Node.js 20且示例启用了ant-design/v5-patch-for-react-19补丁以兼容 React 19。如果你要在一个已有 Refine v5 项目中接入 Casbin最低限度的追加依赖其实只有casbin一个包其余能力 Refine 核心均原生提供。十、运行效果与权限矩阵总结综合前文源码可以整理出示例最终的权限矩阵判定逻辑全部收敛在src/accessControl.ts一个文件内资源/动作admineditorposts list / create✅(list)\|(create)✅(list)\|(create)posts/任意ID edit / show✅posts/*✅posts/*posts/任意ID delete✅posts/*❌无 delete 策略posts/hit 字段field✅posts/*通配 field❌显式 denyusers 全部操作✅❌无任何策略categories list✅✅仅 listcategories create / edit / show / delete✅❌这套矩阵在运行时的表现是切换为 Editor 后/users路由被CanAccess /拦截、文章列表hit列消失、分类无法新建。所有差异均无需修改任何业务组件仅靠策略数据驱动——这正是把权限逻辑外置到 Casbin 之后获得的核心收益。十一、延伸把示例模式迁移到生产环境从 accessControl.ts 与 App.tsx 的写法出发生产化迁移通常涉及四处替换策略来源StringAdapter换成FileAdapterCSV 文件或数据库 Adapter让权限规则可被后台动态维护同时增加权限变更后的 Enforcer 缓存失效机制如enforcer.invalidateCache()。主体来源把localStorage中的role换成authProvider提供的用户身份并在用户信息中携带角色/角色组 ID通过g关系完成用户 → 角色映射而非直接把角色名当sub。Enforcer 复用示例每次can都调用await newEnforcer(model, adapter)在低频权限查询场景可行生产环境可缓存 Enforcer 实例仅当策略变更时重建避免每次请求重复加载模型。拒绝反馈在can返回体中附带reason字段Refine 的CanResponse支持让未授权界面能向用户解释拒绝原因改善体验。结语access-control-casbin示例演示了一条声明式权限规则 框架级统一裁决的清晰路径Casbin 负责模型与策略的集中管理Refine 的accessControlProvider、CanAccess /、useCan以及各类按钮组件负责在路由、页面、字段三个粒度上自动消费权限判定结果。开发者不需要在几十个页面里手工if (role ...)只需要维护好一份策略表。如果你正在为 Refine v5 管理后台设计 RBAC/ABAC 权限体系这个示例连同 Access Control Provider 官方文档 是一套值得直接参考的入门到进阶素材。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表