跳到主要内容

用户模拟

想象一下,TechCorp 的支持工程师 Sarah 收到了一张来自客户 Alex 的紧急工单,Alex 无法访问关键资源。为了高效地诊断和解决问题,Sarah 需要看到系统中 Alex 所看到的内容。这时,Logto 的用户模拟功能就派上用场了。

用户模拟允许像 Sarah 这样的授权用户在系统中临时代表其他用户(如 Alex)进行操作。这个强大的功能对于故障排除、提供客户支持和执行管理任务非常有价值。

它是如何工作的?

模拟过程涉及三个主要步骤:

  1. Sarah 通过 TechCorp 的后端服务器请求模拟
  2. TechCorp 的服务器从 Logto 的 Management API 获取主体令牌
  3. Sarah 的应用程序用这个主体令牌交换访问令牌

让我们看看 Sarah 如何使用这个功能来帮助 Alex。

步骤 1:请求模拟

首先,Sarah 的支持应用程序需要从 TechCorp 的后端服务器请求模拟。

请求(Sarah 的应用程序到 TechCorp 的服务器)

POST /api/request-impersonation HTTP/1.1
Host: api.techcorp.com
Authorization: Bearer <Sarah 的访问令牌>
Content-Type: application/json

{
"userId": "alex123",
"reason": "调查资源访问问题",
"ticketId": "TECH-1234"
}

在这个 API 中,后端应该执行适当的授权 (Authorization) 检查,以确保 Sarah 拥有模拟 Alex 的必要权限。

步骤 2:获取主体令牌

TechCorp 的服务器在验证 Sarah 的请求后,将调用 Logto 的 Management API 以获取主体令牌。

请求(TechCorp 的服务器到 Logto 的 Management API)

POST /api/subject-tokens HTTP/1.1
Host: techcorp.logto.app
Authorization: Bearer <TechCorp_m2m_access_token>
Content-Type: application/json

{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "资源访问问题",
"supportEngineerId": "sarah789"
}
}

响应(Logto 到 TechCorp 的服务器)

{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}

TechCorp 的服务器然后应该将这个主体令牌返回给 Sarah 的应用程序。

响应(TechCorp 的服务器到 Sarah 的应用程序)

{
"subjectToken": "sub_7h32jf8sK3j2",
"expiresIn": 600
}

步骤 3:用主体令牌交换访问令牌

现在,Sarah 的应用程序用这个主体令牌交换代表 Alex 的访问令牌,并指定令牌将被使用的资源。

请求(Sarah 的应用程序到 Logto 的令牌端点)

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

响应(Logto 到 Sarah 的应用程序)

{
"access_token": "eyJhbG...<truncated>",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "resource:read"
}

返回的 access_token 将绑定到指定的资源,确保它只能用于 TechCorp 的客户数据 API。

示例用法

以下是 Sarah 如何在 Node.js 支持应用程序中使用此功能:

interface ImpersonationResponse {
subjectToken: string;
expiresIn: number;
}

interface TokenExchangeResponse {
access_token: string;
issued_token_type: string;
token_type: string;
expires_in: number;
scope: string;
}

async function impersonateUser(
userId: string,
clientId: string,
ticketId: string,
resource: string
): Promise<string> {
try {
// 步骤 1 & 2:请求模拟并获取主体令牌
const impersonationResponse = await fetch(
'https://api.techcorp.com/api/request-impersonation',
{
method: 'POST',
headers: {
Authorization: "Bearer <Sarah 的访问令牌>",
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId,
reason: '调查资源访问问题',
ticketId,
}),
}
);

if (!impersonationResponse.ok) {
throw new Error(`HTTP 错误发生。状态:${impersonationResponse.status}`);
}

const { subjectToken } = (await impersonationResponse.json()) as ImpersonationResponse;

// 步骤 3:用主体令牌交换访问令牌
const tokenExchangeBody = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
client_id: clientId,
scope: 'openid profile resource.read',
subject_token: subjectToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
resource: resource,
});

const tokenExchangeResponse = await fetch('https://techcorp.logto.app/oidc/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: tokenExchangeBody,
});

if (!tokenExchangeResponse.ok) {
throw new Error(`HTTP 错误!状态:${tokenExchangeResponse.status}`);
}

const tokenData = (await tokenExchangeResponse.json()) as TokenExchangeResponse;
return tokenData.access_token;
} catch (error) {
console.error('模拟失败:', error);
throw error;
}
}

// Sarah 使用此函数模拟 Alex
async function performImpersonation(): Promise<void> {
try {
const accessToken = await impersonateUser(
'alex123',
'techcorp_support_app',
'TECH-1234',
'https://api.techcorp.com/customer-data'
);
console.log('Alex 的模拟访问令牌:', accessToken);
} catch (error) {
console.error('执行模拟失败:', error);
}
}

// 执行模拟
void performImpersonation()
备注
  1. 主体令牌是短期的并且仅供一次使用。
  2. 模拟访问令牌不附带 刷新令牌。如果令牌在 Sarah 解决 Alex 的问题之前过期,Sarah 将需要重复此过程。
  3. TechCorp 的后端服务器必须实施适当的授权 (Authorization) 检查,以确保只有像 Sarah 这样的授权支持人员可以请求模拟。

act 声明

在使用令牌交换流程进行模拟时,签发的访问令牌可以包含一个额外的 act(actor)声明。此声明表示“执行方”的身份——在我们的示例中,是执行模拟的 Sarah。

要包含 act 声明,Sarah 的应用程序需要在令牌交换请求中提供一个 actor_token。此令牌应是一个有效的 Sarah 的访问令牌,并具有 openid 权限。以下是如何在令牌交换请求中包含它:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=techcorp_support_app
&scope=resource:read
&subject_token=alx_7h32jf8sK3j2
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=sarah_access_token
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&resource=https://api.techcorp.com/customer-data

如果提供了 actor_token,生成的访问令牌将包含一个 act 声明,如下所示:

{
"aud": "https://api.techcorp.com",
"iss": "https://techcorp.logto.app",
"exp": 1443904177,
"sub": "alex123",
"act": {
"sub": "sarah789"
}
}

这个 act 声明清楚地表明 Sarah (sarah789) 正在代表 Alex (alex123) 行事。act 声明对于审计和跟踪模拟操作非常有用。

自定义令牌声明

Logto 允许你为模拟令牌 自定义令牌声明。这对于在模拟过程中添加额外的上下文或元数据非常有用,例如模拟的原因或相关的支持工单。

当 TechCorp 的服务器从 Logto 的 Management API 请求主体令牌时,它可以包含一个 context 对象:

{
"userId": "alex123",
"context": {
"ticketId": "TECH-1234",
"reason": "资源访问问题",
"supportEngineerId": "sarah789"
}
}

这个 context 然后可以在 getCustomJwtClaims() 函数中使用,以向最终访问令牌添加特定声明。以下是如何实现的示例:

const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
if (context.grant?.type === 'urn:ietf:params:oauth:grant-type:token-exchange') {
const { ticketId, reason, supportEngineerId } = context.grant.subjectTokenContext;
return {
impersonation_context: {
ticket_id: ticketId,
reason: reason,
support_engineer: supportEngineerId,
},
};
}
return {};
};

Sarah 收到的最终访问令牌可能如下所示:

{
"sub": "alex123",
"aud": "https://api.techcorp.com/customer-data",
"impersonation_context": {
"ticket_id": "TECH-1234",
"reason": "资源访问问题",
"support_engineer": "sarah789"
}
// ... 其他标准声明
}

通过这种方式自定义访问令牌声明,TechCorp 可以包含有关模拟上下文的有价值信息,使其更容易在系统中审计和理解模拟活动。

备注

在向令牌添加自定义声明时要谨慎。避免包含敏感信息,如果令牌被截获或泄露,可能会带来安全风险。JWT 是签名的,但不是加密的,因此声明对任何有权访问令牌的人都是可见的。