身份、租户与访问
每条命令、每次读都要回答同样的问题:哪个聚合、哪个租户、归谁所有、谁在问、允不允许?Wow 对每个问题只在一处回答一次,所以答案不会互相矛盾。本页说明每个答案从哪里来。
聚合由一个值寻址
聚合的身份是一个值,即它的引用:上下文、聚合类型、租户和 ID,写作 context.aggregate/tenant/id。引用只在准入时决定一次,此后没有任何东西能改变它。
- 租户是身份的一部分。 两个租户可以使用相同的 ID 而互不影响:引用不同,哈希到的分区也不同,而且每个唯一键(版本、请求 ID 和预留键)都包含租户。
- ID 来自路由或消息,新聚合的 ID 在准入时生成。
- 命令体不包含身份。 它只承载业务数据。需要所在聚合引用的命令函数,从准入后的命令里读取。
聚合归谁所有
聚合总是属于一个租户,还可以属于一个所有者和一个空间;两者合起来是它的作用域。
- 新聚合从创建它的命令那里得到作用域。
- 此后,只有转移所有者或空间的生命周期命令能改变它。
- 每次提交都记录提交后聚合的作用域,快照也带着它。投影、查询策略或快照订阅不用重放历史就能读到它。
聚合类型声明它的归属:
| 归属 | 属于 | 适用于 |
|---|---|---|
| 所有者(默认) | 它的租户,以及创建它的命令的所有者和空间 | 属于某个人或某个账户的数据:订单、购物车、保存的视图 |
| 租户 | 它的租户,以及它所在的空间(如果有) | 租户的每个调用方都要读的数据:商品、价目表 |
调用方是谁
对每个请求,宿主的调用方解析器把已认证的主体变成一个调用方:
- 它的租户;没有租户的主体属于默认租户;
- 它的作用域:它所代表的所有者和空间;
- 它的主体标识(subject),即主体的 ID;匿名调用方没有;
- 它能否在另一个租户或作用域中行事,若能,就在
Wow-Scope请求头里指明; - 其他属性,例如用于访问控制的角色或属性;
- 它是不是系统里的一个服务,而不是用户。
调用方只存活于请求期间。提交保留下来的是操作人:主体标识、操作人自己的租户,以及它是否在另一个租户或作用域中行事。角色、属性和个人信息从不进入日志。
调用方分两级。所有者级调用方的作用域有所有者,只读写自己的聚合。租户级调用方的作用域没有所有者,读写它租户的所有聚合,例如负责发货的员工。各自能执行哪些动作,由下文的授权决定。
身份来自主体
租户、所有者和空间来自已认证的调用方,从不来自请求的路径或请求体。路由不随租户变化,客户端也不能把另一个租户写进 URL 来冒充它。在别处行事的唯一办法,是成为被允许这样做的调用方,并在 Wow-Scope 里说明。
Saga 发出的命令、补偿和定时命令不是由人发出的。它们继承根命令的作用域和操作人,所以 Saga 创建的聚合属于发出第一条命令的人,日志也写明是以谁的名义做的。每次提交的 causation 说明是什么触发了它。根命令的权限在准入时已经检查过;内核不会拿继承来的作用域去对照已存在的聚合再检查一次。
请求事实
准入把关于请求的几项事实复制进命令,提交时记录下来:
- 请求来自的应用
app_id:只有当调用方解析器认证了它时才记录,例如 CoSec 用那个应用的角色授权了这个请求。客户端自己设置的请求头永远不被信任; - 在公开端口上,客户端的远程地址和 User-Agent;
- 宿主列出的其他请求头。
Saga 发出的命令只继承那些本该随行的事实,例如应用;第一个请求的远程地址和 User-Agent 不继承。
访问
访问在准入时判定,依据的是命令和查询共用的策略:
- 路由级授权,例如公开路由前的 CoSec,决定调用方究竟能不能调用某个路由。
- 动作级授权决定调用方能否对这个聚合执行这条命令。用 CoSec 时,它可以以调用方的身份为每条命令授权。
- 作用域。 内核拒绝作用于调用方作用域之外的聚合的命令;查询准入把调用方的租户、所有者和空间加进每个查询(读模型)。
- 基于属性的访问,默认关闭,用调用方的属性匹配每个聚合的标签。聚合的标签由生命周期命令打上,或从它的状态推出。
内核本身不做授权:命令到达内核时,准入已经做出了判定。
内部端口
每个节点监听两个端口。公开端口提供 HTTP API。内部端口承载:
- 一个服务的节点之间的集群通道,用于转发命令;
- 完成等待的信号,可能来自系统里的任何服务;
- 来自其他服务的同步调用,以
Wow-Call请求头标记; - 来自其他服务的读:与公开端口相同的查询和状态路由,供以自己的服务身份读本服务的服务使用。
信号完成一次等待并把结果交给调用方,所以谁能发信号,谁就能伪造任何命令的结果。因此内部端口绝不暴露到集群之外,也没有公开网关转发到它。在集群内部,发往它的每个请求都要认证:
| 请求 | 接受来自 |
|---|---|
| 转发的命令 | 同一服务的节点,带它的集群令牌 |
| 等待信号 | 同一服务的节点,带它的集群令牌;或另一个服务,带它的服务身份 |
| 同步调用或读 | 另一个服务,带它的服务身份;集群令牌不能发起调用 |
除非服务配置为允许更多,同步调用只能调用领域命令。生命周期命令和系统命令(例如删除、重建快照)必须为调用方服务显式列出。一次调用带着它所代表的根命令的作用域、操作人和请求事实,目标只在调用来自服务身份时才信任这些声明。