Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

权限

Oak 的权限模型,和很多传统系统里的“菜单权限 / 路由权限 / 角色字符串判断”不是一回事。

它的设计核心是:

权限不是一堆散落在代码里的 if (role === 'admin'),而是一套可以被数据表达、被路径推导、被对象关系复用的规则体系。

这也是 Oak 最值得认真理解的一部分。

Oak 权限体系里的几个核心实体

Relation

oak-domain/src/entities/Relation.ts

它表示某个对象上的“关系名”,例如:

  • 某个 system 上的 admin
  • 某个 order 上的 owner
  • 某个 project 上的 member

也就是说,Relation 不是全局角色,而是某个对象上下文中的关系

UserRelation

oak-domain/src/entities/UserRelation.ts

它表示“某个用户,在某个对象上,拥有某个 relation”。

这是 Oak 权限真正落到用户身上的那一层。

Path

oak-domain/src/entities/Path.ts

它定义了权限如何沿对象关系传播。最关键的字段是:

  • sourceEntity
  • destEntity
  • value
  • recursive

例如,如果一个 application 通过 system 外键指向 system,那么从 system 权限传播到 application 的路径就可以写成 system

ActionAuth

oak-domain/src/entities/ActionAuth.ts

它定义:

  • 某种 relation
  • 沿某条 path
  • 可以对目标对象执行哪些 deActions

这就是“某类关系能做什么事”的核心授权数据。

RelationAuth

oak-domain/src/entities/RelationAuth.ts

它定义:

  • 某种 sourceRelation
  • 沿某条 path
  • 能否去授予或移除某个 destRelation

也就是说,ActionAuth 解决“能操作什么”,而 RelationAuth 解决“能给别人授什么权”。

一个最容易理解的例子

假设有两个对象:

  • system
  • application

并且 application.systemId -> system.id

那么 Oak 中一个很自然的权限表达方式就是:

  1. 在某个 system 上定义 admin relation;
  2. 通过 UserRelation 把用户绑定到这个 system 的 admin
  3. 定义一条 Path,表示从 application 回到 system 的路径是 system
  4. 定义 ActionAuth,说明 system.admin 可以沿着 system 这条路径,对 application 执行 select/update/...

这样,权限就不是写死在某个页面按钮上,而是变成了“对象关系 + 路径 + 动作”的数据规则。

运行时是谁在做权限判断

真正的权限判定核心在 oak-domain/src/store/RelationAuth.ts

AppLoader 在创建 dbStore 时,会把下面几类项目配置传进去:

  • authDeduceRelationMap
  • selectFreeEntities
  • updateFreeDict

后续无论是前端还是后端,Oak 都会尽量通过这一套关系授权体系去推导:

  • 当前用户能否 select
  • 当前用户能否 create/update/remove
  • 级联操作中的子对象权限是否成立

authDeduceRelationMap 是做什么的

有些对象本身并不需要单独定义权限,因为它的权限完全可以从某个父对象推导出来。

这时就可以在项目的 src/configuration/relation.ts 中配置:

export const authDeduceRelationMap = {
};

如果某个实体的权限可以通过某个外键直接 deduce,Oak 就不必再单独为它搜索完整的 relation 路径。这样既减少配置,也减少权限判断开销。

框架内部还会自动补上一条:

  • modi: 'entity'

也就是说,modi 的权限默认就会从其父实体推导。

selectFreeEntitiesupdateFreeDict

这两个配置是 Oak 权限体系中很实用的“开口子”能力。

selectFreeEntities

表示这些实体允许自由查询,不必经过完整的 relation auth 推导。

例如 bm-smart/src/configuration/relation.ts 中,就把 manufactureproductoauthProvidertagcommunitybannerpostreplyanswer 等对象列进了 selectFreeEntities

这类对象通常具备公共内容或公共维表属性。

updateFreeDict

表示某些实体上的特定动作可以自由执行,而不走常规授权推导。

这类配置一定要慎用,因为它是在权限系统上显式开白名单。

权限和 checker 的关系

在 Oak 的 checker 类型中,有一个保留类型叫 relation。它的语义就是:这是权限相关的检查

不过大多数时候,开发者并不需要自己手写 relation checker,因为 Oak 已经把权限规则数据化了。你真正需要做的事情,更多是:

  • 定义 relation 数据;
  • 配置 path;
  • 配置 actionAuth / relationAuth;
  • 维护好项目的 relation 配置。

权限数据写在哪里

项目通常把静态权限数据放在:

  • src/data/path.ts
  • src/data/actionAuth.ts
  • src/data/relationAuth.ts
  • 需要预置对象关系时的 src/data/relation.ts

这些文件使用生成的 CreateOperationData 类型,pathIdrelationId 和 action 名称必须能由当前 domain 对上,不能用未声明字段或宽泛断言掩盖错误。修改这组数据后,应依次执行:

npm run build
npm run upgrade:auth

build 先生成服务端可加载的 lib/dataupgrade:auth 再按项目更新计划把 pathactionAuthrelationrelationAuth 收敛到数据库。只改源码但不执行升级,运行中的权限数据不会自动同步。

为什么 Oak 的权限模型更强

因为它解决的是“对象之间关系传播后的动作权限”,而不是“用户有一个字符串角色,所以放行”。

这带来的好处是:

  • 权限可以落到具体业务对象上;
  • 父子对象可以沿路径传递权限;
  • 授权能力本身也可以被授权;
  • 同一套规则前后端都能复用。

当然,它的代价也很明显:第一次理解起来会比传统 RBAC 更难。但一旦系统进入复杂业务阶段,这套模型的可维护性会远高于散落在各处的手写判断。

一个实践建议

在 Oak 中,优先思考“用户和对象之间是什么关系”,而不是“这个用户属于什么全局角色”。

只要你从这个角度出发,Relation / UserRelation / Path / ActionAuth / RelationAuth 这几个概念就会迅速连起来。