服务到服务委托
在某些 API 架构中,后端服务会收到来自已登录用户的请求,并需要在保留用户身份的情况下调用另一个后端服务。
例如:
用户 -> API A -> API B
API B 需要知道两件事:
- 调用方是受信任的服务,例如 API A。
- 操作是为原始用户执行的。
使用 Logto 的令牌交换授权 (token exchange grant),可以将用户的访问令牌 (access token) 交换为以下游 API 资源为受众 (audience) 的新访问令牌 (access token)。这遵循 OAuth 2.0 令牌交换模式,并避免将原始用户令牌转发到下游服务。
何时使用此流程
在以下情况下使用服务到服务委托:
- API A 是一个可以安全地向 Logto 令牌端点进行认证 (Authentication) 的后端服务。
- API A 收到 Logto 签发的用户访问令牌 (access token)。
- API A 需要以同一用户的身份调用 API B。
- API B 应该验证一个以自身 API 资源为受众 (audience) 的访问令牌 (access token)。
不要将此流程用于纯机器对机器 (machine-to-machine) 的无用户访问。在这种情况下,请使用 客户端凭证流程。对于支持、管理员或代理场景,即一个用户临时以另一个用户身份操作,请使用 用户模拟 (User impersonation)。
工作原理
交换后的访问令牌 (access token) 代表原始用户(sub),并绑定到下游 API 资源(aud)。下游 API 还可以检查 client_id 声明 (Claim) 以识别发起交换的应用程序。
前置条件
- 为涉及的服务创建 API 资源。参见 保护全局 API 资源。
- 配置 API B 的权限 (Permissions),并通过角色 (Roles) 或组织角色分配给用户。
- 对于 API A,使用服务器端应用程序,如机器对机器 (machine-to-machine) 应用或传统 Web 应用,以便可以通过应用密钥安全认证 (Authentication)。
- 为 API A 的应用启用令牌交换。
在使用令牌交换授权(token exchange grant)之前,你需要为你的应用程序启用它:
- 前往 控制台 > 应用程序 并选择你的应用程序。
- 在应用程序设置中,找到“令牌交换”部分。
- 启用“允许令牌交换”开关。
出于安全原因,令牌交换默认是禁用的。如果你没有启用它,你将会收到“此应用程序不允许令牌交换”的错误。
为下游 API 请求访问令牌 (Access token)
当 API A 需要调用 API B 时,向 Logto 的 令牌端点 发起令牌交换请求。
对于带有应用密钥的传统 Web 应用或机器对机器 (machine-to-machine) 应用,在 Authorization 头中包含凭证:
POST /oidc/token HTTP/1.1
Host: tenant.logto.app
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(api-a-app-id:api-a-app-secret)>
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<user_access_token_received_by_api_a>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api-b.example.com
&scope=read:orders
参数说明:
grant_type:使用urn:ietf:params:oauth:grant-type:token-exchange。subject_token:API A 收到的原始 Logto 用户访问令牌 (access token)。subject_token_type:使用urn:ietf:params:oauth:token-type:access_token。resource:API B 的 API 资源指示器 (resource indicator)。scope:API A 为本次委托调用请求的下游权限 (Scopes)。Logto 仅会根据 RBAC 设置,为原始用户在该资源上可用的权限 (Scopes) 签发请求的权限 (Scopes)。
Logto 返回 API B 的访问令牌 (access token):
{
"access_token": "eyJhbGci...<truncated>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read:orders"
}
解码后,访问令牌 (access token) 包含类似如下的声明 (Claims):
{
"sub": "user_id",
"client_id": "api_a_app_id",
"iss": "https://tenant.logto.app/oidc",
"aud": "https://api-b.example.com",
"scope": "read:orders",
"exp": 1760000000
}
然后 API A 使用交换后的令牌调用 API B:
GET /orders HTTP/1.1
Host: api-b.example.com
Authorization: Bearer <exchanged_access_token>
在 API B 中验证令牌
API B 应像验证任何 Logto 签发的 API 资源访问令牌 (access token) 一样验证交换后的令牌:
- 使用 Logto 的 JWKs 验证签名。
- 检查发行者 (Issuer)(
iss)。 - 检查受众 (Audience)(
aud)是否与 API B 的资源指示器 (resource indicator) 匹配。 - 检查过期时间(
exp)。 - 检查所需的权限 (Scopes)。
- 使用
sub作为原始用户 ID。 - 如只允许特定上游服务进行委托调用,可选检查
client_id。
实现指导参见 在 API 中验证访问令牌 (Access tokens)。