NocoBase 插件开发(服务端)——ACL 权限控制完整指南:Snippet、allow/deny、固定参数与权限中间件

📅 发布时间:2026/9/17 4:42:46
NocoBase 插件开发(服务端)——ACL 权限控制完整指南:Snippet、allow/deny、固定参数与权限中间件
NocoBase 插件开发服务端——ACL 权限控制完整指南Snippet、allow/deny、固定参数与权限中间件【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobaseNocoBase 的服务端通过nocobase/acl提供了一套基于资源操作Action的访问控制列表ACL机制用于控制数据源上每个资源操作如list、create、destroy的访问权限。本篇指南以 NocoBase 插件开发文档《ACL 权限控制服务端》为主体结合packages/core/acl源码与测试用例系统讲解权限片段Snippet、跳过角色约束allow、权限中间件use、固定数据约束addFixedParams、权限判断can与可配置操作setAvailableAction六个核心能力读完你将能在自己的插件中为自定义接口和数据资源配置细粒度的服务端权限。ACL 对象的归属与访问方式在动手注册权限之前先明确 ACL 对象在 NocoBase 架构中的位置ACL 对象归属于数据源Data Source通过dataSource.acl访问。每个数据源拥有独立的 ACL 实例互不干扰。主数据源Main Data Source的 ACL 可以通过app.acl快捷访问这也是插件开发中最常用的入口。在 main-data-source.ts 中可以看到MainDataSource在初始化时接收acl实例并挂载到自身同时将acl.middleware()注册到资源管理器resourceManager.use(this.acl.middleware(), { group: acl, after: auth })这意味着 ACL 检查位于鉴权auth之后、实际业务处理器之前。其他数据源的 ACL 使用方式通过app.dataSourceManager获取对应数据源的acl属性详细说明见 DataSourceManager 数据源管理。ACL 的核心类ACL定义在 acl.ts内部维护了角色表roles、可用策略availableStrategy、跳过规则allowManager、权限片段snippetManager、固定参数fixedParamsManager以及按拓扑序排列的权限中间件middlewares。注册权限片段Snippet权限片段Snippet可以把一组常用的权限组合注册为可复用的权限单元角色绑定 Snippet 后即可获得对应的一组权限减少重复配置。acl.registerSnippet({ name: ui.customRequests, // ui.* 前缀表示允许在界面上配置的权限 actions: [customRequests:*], // 对应资源操作支持通配符 });底层实现与命名约束从 snippet-manager.ts 的实现可以看到三个关键细节.*后缀自动剥离注册时snippet.name snippet.name.replace(.*, )因此pm.users.*会以pm.users为名注册测试用例见 snippet.test.ts。非法命名直接抛错若名称仍包含*或以.结尾会抛出Invalid snippet name错误。同名片段自动合并同一名称重复注册时actions会做并集去重new Set([...existed.actions, ...snippet.actions])因此多个插件可以安全地向同一个片段追加操作测试见 snippet.test.ts。通配符与否定片段角色在绑定片段时role.snippets.add(...)可以使用minimatch通配符也可以使用!前缀做否定排除adminRole.snippets.add(sc.*)绑定所有sc.开头的片段adminRole.snippets.add(!sc.collection-manager.gi)显式排除某个片段。effectiveSnippets()会先计算「允许集合减去被排除集合」得到生效片段再逐条用minimatch匹配资源:操作路径见 acl-role.ts。注意否定规则优先于允许规则——即使某个动作同时被允许片段和否定片段命中最终也是被拒绝。这一行为有完整测试覆盖snippet.test.ts。跳过角色约束的权限allowacl.allow()用于让某些操作绕过角色约束适用于公开 API、需要动态判断权限的场景或者需要基于请求上下文做权限判断的情况。// 公开访问无需登录 acl.allow(app, getLang, public); // 已登录用户即可访问 acl.allow(app, getInfo, loggedIn); // 基于自定义条件判断 acl.allow(orders, [create, update], (ctx) { return ctx.auth.user?.isAdmin ?? false; });condition 参数说明public任何用户包括未登录用户都可访问无需任何身份验证loggedIn仅已登录用户可访问需要有效的用户身份(ctx) Promiseboolean或(ctx) boolean自定义函数根据请求上下文动态判断是否允许访问可以实现复杂的权限逻辑。底层实现allow 是 skip 的别名源码中allow()直接委托给skip()见 acl.tsskip()会把资源:操作与条件记录到AllowManager。AllowManager内部allow-manager.ts预置了三个具名条件条件名判断逻辑public恒为true任何请求都放行loggedInctx.state.currentUser存在已登录allowConfigure当前角色存在且其策略开启了allowConfigure其中loggedIn、public、allowConfigure都通过registerAllowCondition(name, fn)注册为具名条件自定义函数条件则直接在匹配时被调用isAllowed中逐个 await 执行。AllowManager还支持用*通配资源名或动作名例如// 所有资源的 list 操作都对已登录用户开放 acl.allow(*, list, loggedIn);在请求到达时AllowManager.aclMiddleware()会先于核心权限检查运行在 acl.ts 中以tag: allow-manager, before: core注册一旦命中条件就将ctx.permission.skip置为true从而跳过后续的 ACL 检查。相关测试见 allow.test.ts。注册权限中间件useacl.use()用于注册自定义权限中间件可以在权限检查流程中插入自定义逻辑通常和ctx.permission搭配使用用于实现非常规权限控制。典型应用场景公开表单场景无用户无角色但需要通过自定义密码来约束权限基于请求参数、IP 地址等条件的权限控制自定义权限规则跳过或修改默认的权限检查流程。通过ctx.permission控制权限acl.use(async (ctx, next) { const { resourceName, actionName } ctx.action; // 示例公开表单需要验证密码后跳过权限检查 if (resourceName publicForms actionName submit) { const password ctx.request.body?.password; if (password your-secret-password) { // 验证通过跳过权限检查 ctx.permission { skip: true, }; } else { ctx.throw(403, Invalid password); } } // 执行权限检查继续 ACL 流程 await next(); });ctx.permission 属性说明skip: true跳过后续的 ACL 权限检查直接允许访问可以在中间件中根据自定义逻辑动态设置实现灵活的权限控制。从 acl.ts 的middleware()实现看ctx.permission默认还携带三个字段canctx.can的权限判断结果、resourceName、actionName在核心中间件运行后还会追加deferred针对firstOrCreate、updateOrCreate等需要先执行再判断的操作、parsedParams、rawParams、mergedParams等字段。中间件通过Toposort排序执行acl.use()注册的自定义中间件默认进入prep组可用{ before, after, group }选项控制执行顺序。为特定操作添加固定数据约束addFixedParamsaddFixedParams可以为某些资源的操作添加固定的数据范围filter约束这些约束会绕过角色限制直接生效通常用于保护系统关键数据。acl.addFixedParams(roles, destroy, () { return { filter: { $and: [ { name.$ne: root }, { name.$ne: admin }, { name.$ne: member }, ], }, }; }); // 即使用户拥有删除角色的权限也无法删除 root、admin、member 这些系统角色底层实现FixedParamsManagerFixedParamsManagerfixed-params-manager.ts以资源:操作为键存储「固定参数生产函数」Merger并支持两类注册addFixedParams(resource, action, merger)为指定资源、指定操作注册固定参数addGeneralFixedParams(merger)注册全局固定参数对任意资源:操作生效merger(resource, action)接收资源名与操作名。多个固定参数会叠加合并合并策略为filter用andMerge多个 filter 条件以$and合并、fields/whitelist/blacklist取交集、appends/except取并集、sort后写覆盖。这一点有测试佐证fixed-params.test.ts 中连续注册两个name.$ne条件后最终得到filter: { $and: [...] }的合并结果。固定参数与角色权限叠加生效在can()的getCanByRole中acl.ts固定参数通过assign(params, fixedParams)合并进最终结果即便角色拥有destroy权限也无法操作被固定参数排除的数据确保系统内置角色、管理员账户等敏感数据不被误删或修改。判断权限canacl.can()用于判断某个角色是否有权限执行指定操作返回权限结果对象或null通常用在中间件或操作的 Handler 中根据角色动态判断是否允许执行某些操作。const result acl.can({ roles: [admin, manager], // 可以传入单个角色或角色数组 resource: orders, action: delete, }); if (result) { console.log(角色 ${result.role} 可以执行 ${result.action} 操作); // result.params 包含了通过 addFixedParams 设置的固定参数 console.log(固定参数:, result.params); } else { console.log(无权限执行该操作); }类型定义interface CanArgs { role?: string; // 单个角色 roles?: string[]; // 多个角色会依次检查返回第一个有权限的角色 resource: string; // 资源名称 action: string; // 操作名称 } interface CanResult { role: string; // 有权限的角色 resource: string; // 资源名称 action: string; // 操作名称 params?: any; // 固定参数信息如果通过 addFixedParams 设置了的话 }多个角色的行为返回并集如果传入多个角色can()会依次检查每个角色并返回并集结果acl.ts第一个有权限的角色的结果作为基准后续角色若有权限则把其params合并进基准结果。因此result.role记录的是首个命中的角色。另外需要注意两个特殊行为root角色当roles中包含root时会直接缩短为[root]且root角色在getCanByRole中直接返回权限结果而不做任何策略/片段检查——即 root 拥有全部权限默认匿名角色在请求中间件中未登录请求的roleName取自ctx.state.currentRole || anonymousacl.ts。请求上下文中的快捷方式在请求处理中ctx.can(...)是acl.can()的便捷封装自动带上ctx.state.currentRoles或当前角色ctx.permission.can即是对当前请求「资源:操作」的预计算结果可以直接读取而无需重复调用。注册可配置操作setAvailableAction如果你希望自定义操作可以在界面上配置权限比如在「角色管理」页面中显示需要用setAvailableAction注册。注册后的操作会出现在权限配置界面中管理员可以在界面上为不同角色配置操作权限。acl.setAvailableAction(importXlsx, { displayName: {{t(Import)}}, // 界面显示名称支持国际化 type: new-data, // 操作类型 onNewRecord: true, // 是否在新记录创建时生效 });参数说明displayName在权限配置界面显示的名称支持国际化使用{{t(key)}}格式type操作类型决定该操作在权限配置中的分类new-data创建新数据的操作如导入、新增等existing-data修改已有数据的操作如更新、删除等onNewRecord是否在新记录创建时生效仅对new-data类型有效aliases可选操作别名setAvailableAction会将别名写入actionAlias在策略匹配、权限判断时自动解析到正式操作名见 acl.tsallowConfigureFields可选是否允许在界面上配置该操作的字段范围。内置可配置操作服务端在创建 ACL 时createACL会批量注册一组内置操作定义于 available-action.ts操作显示名type别名createAdd newnew-datacreateviewViewold-dataget,list,queryupdateEditold-dataupdate,movedestroyDeleteold-datadestroyview、create、update均开启了allowConfigureFields: true管理员可在界面为其配置允许操作的字段。注册后该操作会出现在权限配置界面中管理员可以在「角色管理」页面中配置该操作的权限。插件中的综合应用示例以仓库内置的plugin-acl插件为例server.ts可以看到各 API 的真实组合用法// 登录后即可访问的公开动作 this.app.acl.allow(users, setDefaultRole, loggedIn); this.app.acl.allow(roles, check, loggedIn); // root 角色全局放行 this.app.acl.allow(*, *, (ctx) { return ctx.state.currentRoles?.includes(root); }); // 固定参数保护系统角色不可删除 this.app.acl.addFixedParams(roles, destroy, () { return { filter: { $and: [{ name.$ne: root }, { name.$ne: admin }, { name.$ne: member }], }, }; }); // 保护内置数据范围rolesResourcesScopes 的 all / own 不可删除、修改 this.app.acl.addFixedParams(rolesResourcesScopes, destroy, () { return { filter: { $and: [{ key.$ne: all }, { key.$ne: own }], }, }; });这个示例同时印证了本文的多个要点allow可用于放行登录用户的常规操作、root特判可用自定义条件实现、addFixedParams是保护内置数据最直接的手段。相关链接ResourceManager 资源管理 — 注册自定义接口与资源操作Plugin 插件 — 在插件生命周期中注册权限Context 请求上下文 — 在请求中获取当前角色和权限信息Middleware 中间件 — ACL 中间件的注册与使用DataSourceManager 数据源管理 — 各数据源各自拥有独立的 ACL 实例【免费下载链接】nocobaseNocoBase is an open-source AI no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考