为什么不能原样转交收到的令牌
一句话总结
服务调用下游服务时使用的令牌有两条路。与用户无关的事情,使用服务以自己名义取得的 client credentials 令牌;代表用户做的事情,就把收到的用户令牌交给 STS,换成一个仅限这个下游、写明是谁在代为调用的新令牌(令牌交换)。把收到的令牌原样转发,两种都不是。
为什么需要它
假设订单 API 收到了用户令牌(aud=lab-api),并要为该用户去调用库存和支付服务。最简单的办法是把收到的令牌原样放进 Authorization 转出去。这条捷径有三个问题。
第一,这个令牌的 aud 不是下游。下游要接受它,就得关掉 aud 检查,而不检查 aud 的服务连为其他服务签发的令牌也会接受。一处泄露的令牌就成了整条链的钥匙。第二,下游无法区分是用户直接调用的,还是服务代为调用的。审计日志里留不下行为者。第三,权限没有缩小。用户令牌的权限会原封不动地传到链条末端。
反过来,如果只使用服务自己的令牌,下游只知道“api-svc 调用了”,却不知道这是为谁发的请求,无法做按用户的权限判断。填补这两者之间空缺的,就是 RFC 8693 OAuth 2.0 Token Exchange。
工作原理
client credentials。RFC 6749 §4.4 说,这种 grant 用于客户端访问自己控制之下的资源,并明确规定只有机密客户端才能使用。建议响应中不要包含 refresh token(§4.4.3)——因为持有密钥的客户端随时可以重新获取。在本实验的 Keycloak 上用 api-svc 获取时,作为主体的不是用户,而是名为 service-account-api-svc 的服务账号。
令牌交换。请求这样发往令牌端点。
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<사용자 토큰> subject_token_type=...:token-type:access_token (필수)
actor_token=<서비스 토큰> actor_token_type=...:token-type:access_token (선택)
audience=orders-api (선택)
RFC 区分了两种含义。impersonation 是 A 变成 B,使别人无法把 A 与 B 区分开;delegation 是 A 保持自己的身份,同时代理 B。如果是委托,就在新令牌里用 act 声明留下当前行为者。嵌套的 act 是之前的行为者,RFC 要求访问控制只使用顶层声明和最外层的 act,其余仅作信息参考。响应中必须有 issued_token_type。subject_token 无效时返回 invalid_request,无法为所请求的 audience 签发时返回 invalid_target。
职责分为两部分。STS 要连签名、iss、exp、aud 都验证 subject_token,把 audience 与白名单核对,缩小权限,并给出较短的寿命。漏掉验证的 STS 会变成一台把伪造令牌用自己的真签名洗白的机器。下游则检查 aud 是不是自己、act 是不是被允许的调用方。
在现场相遇的样子
“用 Keycloak 做令牌交换不就行了”这句话,要先看版本。Keycloak 从 26.2 起正式支持标准令牌交换。按 token-exchange 文档 所述,标准交换(V2)在服务器启动后默认启用,而旧版交换(V1)是 preview 并且计划弃用,需要同时开启 --features=token-exchange 这样的标志和 fine-grained admin permissions v1。V2 也只支持同一个 Realm 内的客户端之间交换,不支持用户 impersonation 和外部令牌交换。委托(delegation)在文档中被归为实验功能。
本实验 Pod 中的 Keycloak 是 26.0.7。也就是说没有 V2。实际上 discovery 文档的 grant_types_supported 里没有 token-exchange,用这个 grant 请求会返回 unsupported_grant_type。所以本实验对服务到服务用 Keycloak 的 client credentials 实测,而交换则自己搭建 STS 来复现。在实际工作中也要做同样的判断——升级,还是把生产押在 preview 功能上,还是另设一个交换窗口。
下一项实验要做什么
在 Keycloak 中获取 api-svc 的服务令牌,确认签名和声明,并亲眼看到 Keycloak 26.0.7 拒绝令牌交换。然后在 8308 上搭建本地 STS,把 dev1 的令牌换成 orders-api 专用令牌(在 act 中记录 api-svc),并让 8309 的下游检查 aud 和 act。最后用真实请求证明,伪造、篡改、过期的 subject_token 以及未知的 audience 都会被拒绝。