跳到主要内容

用户模拟 (User impersonation)

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

用户模拟 (User impersonation) 允许像 Sarah 这样的授权用户在系统中临时以其他用户(如 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": "Investigating resource access issue",
"ticketId": "TECH-1234"
}

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

步骤 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": "Resource access issue",
"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

对于没有 app secret 的单页应用 (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
&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: 'Investigating resource access issue',
ticketId,
}),
}
);

if (!impersonationResponse.ok) {
throw new Error(`HTTP error occurred. Status: ${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 error! status: ${tokenExchangeResponse.status}`);
}

const tokenData = (await tokenExchangeResponse.json()) as TokenExchangeResponse;
return tokenData.access_token;
} catch (error) {
console.error('Impersonation failed:', 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 这样的授权支持人员才能请求模拟。

act 声明 (Claim)

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

要包含 act 声明,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": "Resource access issue",
"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": "Resource access issue",
"support_engineer": "sarah789"
}
// ... 其他标准声明 (Claims)
}

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

备注:

在为你的令牌添加自定义声明 (Claims) 时要谨慎。避免包含敏感信息,以防令牌被截获或泄露带来安全风险。JWT 是签名但未加密的,因此声明 (Claims) 对任何获得令牌的人都是可见的。

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