# Runtime 用户委托

在外部模块后端通过 ServiceReference 调用下游服务时，安全传递当前已验证用户，并由 Runtime 按服务跳点轮换短期委托。

`ServiceReference` token 证明来源服务可以调用目标服务及方法，但它不代表最终用户。目标方法还需要当前
用户时，Runtime 使用短期 Actor assertion 把已经建立的用户身份委托给外部模块后端。

外部服务不签名、不解析 assertion，也不从业务参数构造用户身份。它只在当前同步请求内接收并转发一个
不透明 Header：

```http
X-Runtime-Actor-Assertion: <opaque-short-lived-assertion>
```

> **Actor assertion 不能替代 ServiceReference token**
>
> 每次服务引用调用仍必须发送 `Authorization: Bearer {reference.token}`。Bearer token 决定来源服务能否调用
> 目标服务及方法；Actor assertion 只表示本次调用代表哪个已验证用户。缺少任一项都不能用另一项补足。

## 单跳调用

当 Runtime 把一个已建立用户身份的请求交给模块 A 外部后端时，会注入 Actor Header。模块 A 调用绑定的
模块 B 时，在每个 HTTP 请求上同时发送引用 token 和自己收到的 assertion：

```http
POST {reference.url}/listManagedShops
Authorization: Bearer {reference.token}
X-Runtime-Actor-Assertion: {currentRequest.actorAssertion}
Content-Type: application/json
```

同一模块 A 可以在同一个短时同步请求中，把同一个 assertion 用于多个已绑定的 ServiceReference 调用。
新的入站业务请求必须使用 Runtime 新注入的值；服务不能建立跨请求 assertion 缓存。

## 链式调用与按跳轮换

服务 B 收到请求后如果还要调用模块 C，不负责替用户签名。Runtime 在验证 A 的 ServiceReference token 和
Actor assertion 后、把调用交给 B 外部后端之前，会为 B 轮换一个来源绑定为 B 的下一跳 assertion：

```text
用户请求
  -> Runtime 为 A 注入 assertion-A
  -> A 使用 ServiceReference(A→B) + assertion-A 调用 Runtime
  -> Runtime 验证两项凭据，为 B 注入 assertion-B
  -> B 使用 ServiceReference(B→C) + assertion-B 调用 Runtime
  -> Runtime 验证两项凭据，为 C 注入 assertion-C
```

因此每个外部服务只有两个动作：读取本次入站请求中的 `X-Runtime-Actor-Assertion`，并在本次请求产生的
ServiceReference 调用中原样发送它。跨到新的目标服务时由 Runtime 自动轮换，A、B、C 都不持有签名密钥。

## 请求生命周期规则

- Header 必须恰好有一个非空值；多个值、合并值或非法 Header 必须拒绝。
- assertion 只保存在当前请求的内存上下文中，不进入数据库、缓存、消息队列、任务参数或事件载荷。
- 不在访问日志、错误、指标、Trace、崩溃报告或调试响应中输出 assertion。
- 不解析 assertion 格式、claims、过期时间或签名算法；这些是 Runtime 内部协议。
- 业务重试只有在原请求仍在执行、当前 assertion 仍由 Runtime 接受且目标方法允许重试时才能继续使用。
- 延迟任务、定时任务、异步消费者和请求结束后的补偿不能复用 assertion；它们必须使用自己的服务身份，
  或从新的已认证用户入口重新建立委托。

## 缺失用户的处理

Actor Header 不保证一定存在。匿名入口、服务级后台任务或没有建立用户身份的调用不会虚构用户。目标方法
要求 `userId` 时，Runtime 必须在网络调用前失败关闭；外部服务不能回退到安装用户、生命周期 Hook 的
`userId`、环境默认用户、固定管理员或业务请求体中的同名字段。

只需要服务或工作区身份的方法不应为了统一字段而虚构 Actor。调用方也不能把 Actor Header 当作普通公开
Endpoint 的鉴权方式；它只对通过 `ExternalServiceReference.url` 返回 Runtime 的服务引用调用有效。

## 与生命周期和方法定义的关系

- Actor assertion 不属于 `ExternalServiceReference` 的持久字段，不会通过生命周期 Hook 交付。
- 生命周期 Hook 中的操作 `userId` 只描述执行该配置操作的用户，不能保存用于后续业务调用。
- 模块 `methodBody` 不声明、映射或生成 `X-Runtime-Actor-Assertion`；该 Header 只由 Runtime 注入。
- 外部后端可以在自己的 HTTP 入口把 assertion 包装成不实现 `Debug`/序列化的请求级类型，降低误记录风险。

完整的引用生成与 Bearer 调用方式见
[ServiceReference 声明、绑定与运行时交付](/development/bundle-development/module-development/service-reference/)
和[模块服务间调用协议](/development/bundle-development/module-development/service-to-service-calls/)。
