简体中文
  • custom JWT
  • JWT 声明
  • 授权
  • 身份验证
  • OAuth 2.0
  • Logto

借助 Logto 为 JWT 访问令牌添加自定义声明,提升授权能力

在本文中,我们将介绍如何使用 Logto 的自定义 JWT 声明功能,通过一个真实示例来提高授权的灵活性以及服务提供商的性能。

Darcy Ye
Darcy Ye
Developer

在以往的文章中,我们提到越来越多的系统正在使用 JWT 格式的访问令牌进行用户身份验证和访问控制。造成这种现象的一个重要原因是,JWT 可以包含一些有用的信息,例如用户角色和权限。这些信息可以帮助我们在服务器和客户端之间传递用户身份信息,从而实现用户身份验证和访问控制。

通常,JWT 所包含的信息由身份验证服务器决定。根据 OAuth 2.0 协议,JWT 通常包含一些如 sub(主体)、aud(受众)、以及 exp(过期时间)等字段,这些字段通常被称为声明。这些声明可以帮助验证访问令牌的有效性。

然而,在使用 JWT 进行验证的无数场景中,常见的 JWT 声明往往不能满足用户的需求。人们常常会想,既然 JWT 可以包含一些信息,那么我们是否可以向其中添加一些信息,以便使授权变得更容易?

答案是肯定的,我们可以向 JWT 添加自定义声明,如当前用户的范围和订阅级别。这样,我们可以在客户端和服务器之间传递用户身份信息(这里指提供各种不同服务的服务器,也称为服务提供商),以实现用户身份验证和访问控制。

有关标准 JWT 声明,请参阅 RFC7519。Logto 作为一个既支持身份验证又支持授权的身份解决方案,在此基础上扩展了资源和范围声明,以支持标准的 RBAC。虽然 Logto 的 RBAC 实现是标准的,但它并不简单,也不够灵活,无法适应所有使用场景。

基于此,Logto 推出了自定义 JWT 声明的新功能,允许用户自定义额外的 JWT 声明,以便更灵活地实现用户身份验证和访问控制。

Logto 自定义 JWT 声明如何工作?

你可以通过点击侧边栏上的 "JWT claims" 按钮来到 Custom JWT 列表页。

custom-jwt-listing-page

让我们从为最终用户添加自定义声明开始。

在左侧的编辑器中,你可以自定义你的 getCustomJwtClaims 函数。这个方法有三个输入参数:tokendataenvVariables

  • token 是基于当前最终用户的凭据和你的系统配置在 Logto 中获取的原始访问令牌负载,以及用户在 Logto 中的访问相关信息
  • data 是 Logto 中关于用户的所有信息,包括用户的所有角色、社交登录身份、SSO 身份、组织成员身份等。
  • envVariables 是你为当前最终用户访问令牌使用场景在 Logto 中配置的环境变量,例如所需外部 API 的 API 密钥等。
details-page-user-data

右侧的卡片可以展开以显示相应参数的介绍,你也可以在此处为当前场景设置环境变量。

details-page-user-test

在阅读右侧所有卡片的介绍后,你可以切换到测试模式,在测试模式下,你可以编辑测试数据,并使用编辑后的测试数据来检查你在左侧代码编辑器中编写的脚本行为是否符合你的期望。

这是一个序列图,展示了当最终用户向 Logto 发起身份验证请求并最终获得 Logto 返回的 JWT 格式访问令牌时,getCustomJwtClaims 函数的执行过程。

如果没有启用 Custom JWT 功能,图中的步骤 3 将被跳过,并在步骤 2 结束后立即执行步骤 4。此时,Logto 会假定 getCustomJwtClaims 的返回值为空对象,然后继续执行后续步骤。

通过自定义 JWT 声明提升你的授权:一个实际案例

在上一节中,我们介绍了 Logto 自定义 JWT 的工作原理。在本部分中,我们将通过一个真实的例子,向你展示如何使用 Logto 自定义 JWT 声明来提高授权的灵活性以及服务提供商的性能。

场景设定

John 的团队开发了一款 AI 助手应用程序,允许用户与 AI 机器人对话以获取各种服务。

AI 机器人服务分为免费和付费服务。免费服务包括特价机票推荐,而付费服务包括股票预测。

AI 助手应用程序使用 Logto 管理所有用户,这些用户分为三种类型:免费用户、预付费用户和高级用户。免费用户只能使用免费服务,预付费用户可以使用所有服务(按使用收费),高级用户可以使用所有服务(但有速率限制以防止恶意使用)。

此外,AI 助手应用程序使用 Stripe 管理用户支付,并有自己的日志服务来记录用户操作日志。

Logto 配置

我们首先为 AI 助手应用程序服务创建 API 资源并创建两个范围,recommend:flightpredict:stock

ai-assistant-app-resource

然后我们创建两个 rolesfree-userpaid-user,并分配相应的范围:

  • recommend:flight 范围分配给 free-user 角色。
  • recommend:flightpredict:stock 范围都分配给 paid-user 角色。
free-user-role
paid-user-role

最后,我们创建三个用户,free-userprepaid-userpremium-user,并分配相应的角色:

  • 为用户 free-user 分配 free-user 角色。
  • 为用户 prepaid-userpremium-user 分配 paid-user 角色。
assign-free-user-role
assign-paid-user-role

如下图所示,为实施上述场景中所需的授权信息,我们希望在 JWT 中包含当前登录用户的 rolesbalancenumOfCallsToday 信息。在 AI 助手应用程序中验证访问令牌时,这些信息可以用于快速执行权限验证。

test-custom-jwt-claims

配置 envVariables 之后,我们实现了 getCustomJwtClaims 函数,并点击 "Run test" 按钮,以查看基于当前测试数据的额外 JWT 声明的结果。

由于我们还没有为 data.user.roles 配置测试数据,结果中显示的 roles 是一个空数组。

检查自定义 JWT 功能是否生效

根据以上 Logto 配置,我们在测试中得到了相应的结果。接下来,我们将使用 Logto 提供的示例应用程序来验证我们的自定义 JWT 是否有效。在 Logto SDKs 中找到你熟悉的 SDK,并根据文档和相应的 GitHub 仓库部署一个示例应用程序。

基于我们上面描述的配置,以 React SDK 为例,我们需要在 LogtoConfig 中更新相应的配置:

在登录模拟 AI 助手应用程序的示例应用程序时,我们可以通过查看 JWT 访问令牌的负载部分,看到我们添加的 rolesbalancenumOfCallsTodayisPaidUserisPremiumUser 信息。

sample-app-access-token-preview-free

balancenumOfCallsTodayisPaidUserisPremiumUser 的值与之前的测试一致,而 roles["free-user"]。这是因为在实际最终用户登录过程中,我们将获取用户的所有可访问数据并相应地处理。

sample-app-access-token-preview-premium

对于高级用户,我们可以看到 roles["paid-user"],而 isPaidUserisPremiumUser 都为 true

更新服务提供商授权逻辑

在前面的步骤中,我们根据业务需求在用户访问令牌中添加了自定义声明。接下来,我们可以使用这些声明快速执行授权验证。

这里我们提供了 Logto 在 API 端验证 JWT 访问令牌的逻辑。完整的代码实现可以在 GitHub 仓库 中找到:

你可以参考 Logto API 验证访问令牌的逻辑,并根据自己的业务逻辑进行自定义。例如,在本文所描述的 AI 助手应用场景中,你可以在 verifyBearerTokenFromRequest 函数中添加对 rolesbalancenumOfCallsTodayisPaidUserisPremiumUser 等自定义声明的验证逻辑。

上述示例适用于影响最终用户登录并获取 JWT 访问令牌的场景。如果你的用例是机器对机器(M2M)通信,你也可以单独为 M2M 应用配置自定义 JWT 声明。

为用户配置自定义 JWT 不会影响 M2M 应用获取访问令牌的结果,反之亦然。

由于 M2M 连接的一般性,Logto 当前没有提供 M2M 应用的 getCustomJwtClaims 方法接收 Logto 内部数据的功能。在其他方面,为 M2M 应用配置自定义 JWT 的方法与用户应用相同。本文不再详细展开。你可以使用 Logto 的自定义 JWT 功能开始入手。

为什么使用自定义 JWT 声明?

我们提供了 John 的 AI 助手应用场景,并演示了如何使用 Logto 的自定义 JWT 功能实现更灵活的授权验证。在这个过程中,我们可以看到自定义 JWT 功能的优势:

  1. 没有自定义 JWT 功能,用户在每次检查权限时都需要请求外部 API(例如在 getCustomJwtClaims 中所做的)。对于提供该 API 的服务提供商来说,这可能会增加额外的负担。通过自定义 JWT 功能,这些信息可以直接放入 JWT 中,减少频繁调用外部 API 的需要。
  2. 对于服务提供商而言,自定义 JWT 功能可以帮助他们更快地验证用户权限,尤其是当客户端频繁调用服务提供商时,从而提高了服务性能。
  3. 自定义 JWT 功能可以帮助你快速实现业务所需的额外授权信息,并且由于 JWT 自包含且可以加密,因此信息可以在客户端和服务提供商之间以安全的方式传递,不易被伪造。

同时,由于 getCustomJwtClaims 在每次用户需要 Logto 签发访问令牌时都会执行,因此应避免执行过于复杂的逻辑和高带宽需求的外部 API 请求。否则,用户可能需要等待过长时间来获得 getCustomJwtClaims 的结果。如果你的 getCustomJwtClaims 返回一个空对象,我们强烈建议你暂时删除该配置项,直到你实际需要使用它为止。

结论

在本文中,Logto 扩展了基本的 JWT 访问令牌,并扩展了额外 JWT 声明的功能,使用户可以根据自己的业务需求将额外的最终用户信息放入 JWT 访问令牌中,从而在用户登录后可以快速验证用户权限。

我们提供了 John 的 AI 助手应用场景,并演示了如何使用 Logto 的自定义 JWT 功能实现更灵活的授权验证。我们还指出了使用自定义 JWT 的一些关键点。结合实际业务场景,用户可以根据自己的业务需求将各种用户相关信息放入 JWT 访问令牌中,从而服务提供商可以快速验证用户权限。