跳到主要内容

用户模拟 (User impersonation)

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

用户模拟 (User impersonation) 允许像 Sarah 这样的授权 (Authorization) 用户在系统内临时以其他用户(如 Alex)的身份进行操作。这个强大的功能对于故障排查、客户支持和执行管理任务非常有价值。

工作原理?

模拟流程包含三个主要步骤:

  1. Sarah 通过 TechCorp 的后端服务器请求模拟
  2. TechCorp 的服务器通过 Logto Management API 获取 subject token
  3. Sarah 的应用用该 subject token 换取访问令牌 (Access token)

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

步骤 1:请求模拟

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

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

POST /api/request-impersonation HTTP/1.1
Host: api.techcorp.com
Authorization: Bearer <Sarah's_access_token>
Content-Type: application/json

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

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

步骤 2:获取 subject token

TechCorp 的服务器在验证 Sarah 的请求后,会调用 Logto 的 Management API 获取 subject token。

请求(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 的服务器随后应将该 subject token 返回给 Sarah 的应用。

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

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

步骤 3:用 subject token 换取访问令牌 (Access token)

前置条件:

在使用令牌交换授权(token exchange grant)之前,你需要为你的应用程序启用它:

  1. 前往 控制台 > 应用程序 并选择你的应用程序。
  2. 在应用程序设置中,找到“令牌交换”部分。
  3. 启用“允许令牌交换”开关。

出于安全原因,令牌交换默认是禁用的。如果你没有启用它,你将会收到“此应用程序不允许令牌交换”的错误。

现在,Sarah 的应用将该 subject token 换取代表 Alex 的访问令牌 (Access token),并指定该令牌将被用于的资源。

请求(Sarah 的应用到 Logto token endpoint)

对于传统 Web 应用或带有 app secret 的机器对机器应用,请在 Authorization 头中包含凭证:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&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

对于单页应用 (SPA) 或无 app secret 的原生应用,请在请求体中包含 client_id

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,
clientSecret?: string // 传统 Web 或机器对机器应用需要
): Promise<string> {
try {
// 步骤 1 & 2:请求模拟并获取 subject token
const impersonationResponse = await fetch(
'https://api.techcorp.com/api/request-impersonation',
{
method: 'POST',
headers: {
Authorization: "Bearer <Sarah's_access_token>",
'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:用 subject token 换取访问令牌 (Access token)
// 传统 Web 或 M2M 应用使用 Basic auth 和 client secret
// SPA 或原生应用在请求体中包含 client_id
const headers: Record<string, string> = {
'Content-Type': 'application/x-www-form-urlencoded',
};

const tokenExchangeBody = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
scope: 'openid profile resource.read',
subject_token: subjectToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
resource: resource,
});

if (clientSecret) {
// 机密客户端:使用 Basic auth
headers['Authorization'] =
`Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;
} else {
// 公共客户端:在 body 中包含 client_id
tokenExchangeBody.append('client_id', clientId);
}

const tokenExchangeResponse = await fetch('https://techcorp.logto.app/oidc/token', {
method: 'POST',
headers,
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 {
// 传统 Web 或 M2M 应用传递 client secret
const accessToken = await impersonateUser(
'alex123',
'techcorp_support_app',
'TECH-1234',
'https://api.techcorp.com/customer-data',
'your-client-secret' // SPA 或原生应用无需此参数
);
console.log('Alex 的模拟访问令牌 (Access token):', accessToken);
} catch (error) {
console.error('执行模拟失败:', error);
}
}

// 执行模拟
void performImpersonation();
备注:
  1. subject token 是短时且一次性使用的。
  2. 模拟访问令牌 (Access token) 不会携带 刷新令牌 (Refresh token)。如果令牌在 Sarah 解决 Alex 问题前过期,需要重复此流程。
  3. TechCorp 的后端服务器必须实现适当的授权 (Authorization) 检查,确保只有像 Sarah 这样的授权 (Authorization) 支持人员才能请求模拟。

act 声明 (Claim)

在使用令牌交换流程进行模拟时,签发的访问令牌 (Access token) 可以包含额外的 act(actor)声明 (Claim)。该声明 (Claim) 表示“执行方”的身份——在本例中即执行模拟的 Sarah。

要包含 act 声明 (Claim),Sarah 的应用需要在令牌交换请求中提供 actor_token。该 token 应为带有 openid 权限 (Scope) 的 Sarah 的有效访问令牌 (Access token)。以下是如何在令牌交换请求中包含它:

对于传统 Web 应用或机器对机器应用:

POST /oidc/token HTTP/1.1
Host: techcorp.logto.app
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&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

对于 SPA 或原生应用,在请求体中包含 client_id

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,最终的访问令牌 (Access token) 会包含如下 act 声明 (Claim):

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

这个 act 声明 (Claim) 明确表示 Sarah(sarah789)正在以 Alex(alex123)的身份操作。act 声明 (Claim) 对于审计和追踪模拟操作非常有用。

自定义令牌声明 (Claims)

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

当 TechCorp 的服务器向 Logto Management API 请求 subject token 时,可以包含一个 context 对象:

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

这个 context 可在 getCustomJwtClaims() 函数中用于向最终访问令牌 (Access token) 添加特定声明 (Claims)。示例实现如下:

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 获得的最终访问令牌 (Access token) 可能如下所示:

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

通过这种方式自定义访问令牌 (Access token) 声明 (Claims),TechCorp 可以在系统中包含关于模拟上下文的有价值信息,便于审计和理解模拟活动。

备注:

在为令牌添加自定义声明 (Claims) 时请谨慎。避免包含敏感信息,以防令牌被截获或泄露带来安全风险。JWT 虽然已签名但未加密,任何获得令牌的人都能看到声明 (Claims) 内容。

什么是网络安全和身份管理中的模拟 (Impersonation)?AI 代理如何使用它?