权限
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
它定义了权限如何沿对象关系传播。最关键的字段是:
sourceEntitydestEntityvaluerecursive
例如,如果一个 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 解决“能给别人授什么权”。
一个最容易理解的例子
假设有两个对象:
systemapplication
并且 application.systemId -> system.id。
那么 Oak 中一个很自然的权限表达方式就是:
- 在某个
system上定义adminrelation; - 通过
UserRelation把用户绑定到这个 system 的admin; - 定义一条
Path,表示从application回到system的路径是system; - 定义
ActionAuth,说明system.admin可以沿着system这条路径,对application执行select/update/...。
这样,权限就不是写死在某个页面按钮上,而是变成了“对象关系 + 路径 + 动作”的数据规则。
运行时是谁在做权限判断
真正的权限判定核心在 oak-domain/src/store/RelationAuth.ts。
AppLoader 在创建 dbStore 时,会把下面几类项目配置传进去:
authDeduceRelationMapselectFreeEntitiesupdateFreeDict
后续无论是前端还是后端,Oak 都会尽量通过这一套关系授权体系去推导:
- 当前用户能否
select - 当前用户能否
create/update/remove - 级联操作中的子对象权限是否成立
authDeduceRelationMap 是做什么的
有些对象本身并不需要单独定义权限,因为它的权限完全可以从某个父对象推导出来。
这时就可以在项目的 src/configuration/relation.ts 中配置:
export const authDeduceRelationMap = {
};
如果某个实体的权限可以通过某个外键直接 deduce,Oak 就不必再单独为它搜索完整的 relation 路径。这样既减少配置,也减少权限判断开销。
框架内部还会自动补上一条:
modi: 'entity'
也就是说,modi 的权限默认就会从其父实体推导。
selectFreeEntities 和 updateFreeDict
这两个配置是 Oak 权限体系中很实用的“开口子”能力。
selectFreeEntities
表示这些实体允许自由查询,不必经过完整的 relation auth 推导。
例如 bm-smart/src/configuration/relation.ts 中,就把 manufacture、product、oauthProvider、tag、community、banner、post、reply、answer 等对象列进了 selectFreeEntities。
这类对象通常具备公共内容或公共维表属性。
updateFreeDict
表示某些实体上的特定动作可以自由执行,而不走常规授权推导。
这类配置一定要慎用,因为它是在权限系统上显式开白名单。
权限和 checker 的关系
在 Oak 的 checker 类型中,有一个保留类型叫 relation。它的语义就是:这是权限相关的检查。
不过大多数时候,开发者并不需要自己手写 relation checker,因为 Oak 已经把权限规则数据化了。你真正需要做的事情,更多是:
- 定义 relation 数据;
- 配置 path;
- 配置 actionAuth / relationAuth;
- 维护好项目的 relation 配置。
权限数据写在哪里
项目通常把静态权限数据放在:
src/data/path.tssrc/data/actionAuth.tssrc/data/relationAuth.ts- 需要预置对象关系时的
src/data/relation.ts
这些文件使用生成的 CreateOperationData 类型,pathId、relationId 和 action 名称必须能由当前 domain 对上,不能用未声明字段或宽泛断言掩盖错误。修改这组数据后,应依次执行:
npm run build
npm run upgrade:auth
build 先生成服务端可加载的 lib/data,upgrade:auth 再按项目更新计划把 path、actionAuth、relation、relationAuth 收敛到数据库。只改源码但不执行升级,运行中的权限数据不会自动同步。
为什么 Oak 的权限模型更强
因为它解决的是“对象之间关系传播后的动作权限”,而不是“用户有一个字符串角色,所以放行”。
这带来的好处是:
- 权限可以落到具体业务对象上;
- 父子对象可以沿路径传递权限;
- 授权能力本身也可以被授权;
- 同一套规则前后端都能复用。
当然,它的代价也很明显:第一次理解起来会比传统 RBAC 更难。但一旦系统进入复杂业务阶段,这套模型的可维护性会远高于散落在各处的手写判断。
一个实践建议
在 Oak 中,优先思考“用户和对象之间是什么关系”,而不是“这个用户属于什么全局角色”。
只要你从这个角度出发,Relation / UserRelation / Path / ActionAuth / RelationAuth 这几个概念就会迅速连起来。