保护你的API资源用于机器与机器之间的通信
学习如何利用 OAuth 2.0 和 JWT 来保护你的API资源用于机器与机器之间的通信。
当构建涉及多个服务的项目时,API资源的安全性是一个关键问题。在这篇文章中,我将向你展示如何利用 OAuth 2.0 和 JWT 来保护服务之间的通信(机器与机器),以及如何应用基于角色的访问控制(RBAC)以遵循最低权限原则。
入门
为了跟随本教程,我假设你有以下先决条件:
- 一个 Logto Cloud 账户,或一个自托管的 Logto 实例
- 至少有两个需要相互通信的服务
出于演示目的,假设我们有以下服务:
- 一个提供API用来管理购物车的购物车服务
- 端点:
https://cart.example.com/api
- 端点:
- 一个提供API用来处理支付的支付服务
- 端点:
https://payment.example.com/api
- 端点:
认证流程
现在,我们的购物车服务需要调用支付服务来处理支付。认证流程如下:
上图中的一些关键概念:
- JWT(RFC 7519):JSON Web 令牌。请参考我们的上一篇文章了解 JWT 的介绍。
- JWK(RFC 7517):用于验证 JWT 签名的 JSON Web 密钥。一个 JWK 集是一组 JWK。
- "client_credentials"授权(RFC 6749):OAuth 2.0 中的一种授权类型。它使用客户端的凭据获取访问令牌。我们将在接下来的部分中演示细节。
上图中的每个参与者在认证流程中都有一个角色要扮演:
- 购物车服务:需要调用支付服务的客户端。虽然它是一个服务,但在 OAuth 2.0 上下文中,它仍然是一个客户端,我们在 Logto 中称这种客户端为“机器对机器应用程序”。
- Logto:颁发访问令牌的 OAuth 2.0 授权服务器。
- 支付服务:提供API用来处理支付的API资源。
让我们逐步讲解认证流程。
初始设置
要执行认证流程,我们需要在 Logto 中创建一个机器对机器应用程序(购物车服务)和一个API资源(支付服务)。
创建API资源
由于我们的购物车服务在执行认证时需要知道支付服务的API,我们首先需要创建一个API资源。前往 Logto 控制台,点击左侧边栏的API资源,然后点击创建API资源。在打开的对话框中,我们提供了一些教程帮助你入门。你也可以点击不使用教程继续跳过 它。
输入API名称和标识符,例如Payment service
和https://payment.example.com/api
,然后点击创建API资源。
创建API资源后,你将被重定向到详细信息页面。我们现在可以暂时不做任何更改。
创建机器对机器应用程序
点击左侧边栏的应用程序,然后点击创建应用程序。在打开的对话框中,找到机器对机器卡片,然后点击开始构建。
输入应用程序名称,例如Cart service
,然后点击创建应用程序。一个互动指南将会显示,帮助你设置应用程序。你可以跟随指南了解基本用法,或者点击完成并完成跳过它。
请求访问令牌
由于假设机器对机器应用程序是安全的(例如,它们部署在私人网络中),所以我们可以使用 OAuth 2.0 的“client_credentials”授权来获取访问令牌。它使用基本认证来认证客户端:
- 请求 URL 是你 Logto 实例的令 牌端点。你可以在机器对机器应用程序详情页的高级设置选项卡中找到并复制它。
- 请求方法是
POST
。 - 请求的
Content-Type
头是application/x-www-form-urlencoded
。 - 对于
Authorization
头,其值是Basic <base64(app_id:app_secret)>
,其中app_id
和app_secret
分别是机器对机器应用程序的应用程序ID和应用程序密钥。你可以在应用程序详情页中找到它们。 - 请求体需要指定授权类型和API标识符。例如,
grant_type=client_credentials&resource=https://payment.example.com/api
。grant_type=client_credentials
:"client_credentials"授权的常量值。resource=https://payment.example.com/api
:客户端希望访问的API资源的API标识符。- 如果应用程序需要使用权限进行授权,你还可以在请求体中指定权限。例如,
scope=read:payment write:payment
。我们将在后面介绍权限。
以下是使用curl
的请求示例:
成功的响应体如下所示:
带授权标头发送请求
现在我们有了访问令牌,可以将其添加到对API资源请求的Authorization
头中。例如,如果我们想调用支付服务的POST /payments
API,可以发送以下请求:
验证JWT
你可能注意到,支付服务需要使用 JWK 集验证 JWT,并且可能有一个本地 JWK 集缓 存以避免每次从 Logto 获取 JWK 集。幸运的是,由于 JWT 的流行,有许多库可以帮助你用几行代码实现目标。
这些库通常被称为“jose”(JavaScript对象签名和加密)或“jsonwebtoken”。例如,在 Node.js 中我们可以使用jose来验证 JWT:
如果验证成功,payload
变量将是解码后的 JWT 负载。否则,将抛出一个错误。
应用基于角色的访问控制
现在我们已经成功保护了购物车服务和支付服务之间的通信。然而,认证流程仅确保客户端是实际的购物车服务,并不能确保购物车服务有任何权限执行支付服务上的操作。
假设我们想允许购物车服务创建支付,但不允许读取支付。
定义权限
在 Logto 中,“范围”和“权限”是可以互换使用的。前往支付服务的API资源详情页面,并导航到权限选项卡。现在应该是空的。点击创建权限,输入read:payment
作为权限名称,输入读取支付
作为权限描述。然后点击创建权限。
重复上述步骤创建另一个名称为write:payment
,描述为创建支付
的权限。
创建机器对机器角色
角色是一组权限。在 Logto 中,机器对机器应用程序可以分配角色来授予权限。点击左侧边栏中的"角色",然后点击创建角色。
- 输入
checkout
作为角色名称,输入Checkout service
作为角色描述。 - 点击显示更多选项。选择“机器对机器应用程序角色”作为角色类型。
- 在“已分配权限”部分,点击API资源名称(支付服务)左侧的箭头图标,选择
write:payment
权限。 - 点击创建角色。
- 由于我们已经有一个机器对机器应用程序(购物车服务),我们可以直接在下一步中将角色分配给它。勾选应用程序名称(购物车服务)左侧的复选框,然后点击分配应用程序。
带范围请求访问令牌
除了我们在请求访问令牌中提到的请求体参数,我们还可以在请求体中指定范围。例如,如果我们想请求write:payment
权限, 可以发送以下请求:
要请求多个范围,你可以用空格分隔它们。例如,scope=write:payment read:payment
。
验证范围
如果某个操作需要支付服务中的write:payment
权限,我们可以通过断言 JWT 负载的scope
声明来验证范围:
结论
如果你想保护对购物车服务的访问,也可以应用相同的认证流程。这一次,购物车服务是API资源,而客户端则是另一个需要访问它的服务。
使用 Logto,你的API资源通过 OAuth 2.0 和 JWT 得到保护,并且通过应用基于角色的访问控制,你可以遵循最低权限原则。此外,你还可以使用 Logto 管理你的用户及其权限,甚至与第三方身份提供者集成。