深入浅出 JWT:如何保护 HTTP 端点并完善身份验证

Web 开发人员必须确保只有授权的人员或系统才能访问特定的 HTTP 端点(如 API URL)。这涉及到验证请求的发起者身份以及他们是否拥有权限。在这篇新手友好的指南中,我们将探讨保护 HTTP 端点的常用方法——包括 API 密钥会话 CookieOAuth2 ——并用通俗易懂的语言解释它们的优缺点。然后,我们将深入探讨 JSON Web Tokens (JWT) ,解释什么是 JWT、它们如何工作以及如何保护端点。我们还将介绍访问令牌与刷新令牌的区别、它们如何融入身份验证流程、使用 JWT 的最佳实践以及需要避免的常见安全陷阱。

保护 HTTP 端点的常用方法

有几种广泛使用的方法来验证调用您的 HTTP API 的客户端或用户。每种方法都有其优点和缺点:

API 密钥

API 密钥是简单的秘密令牌(通常是一个长字符串),客户端在每个请求中包含它(例如,在查询参数或标头中)以标识自身。服务器检查此密钥以决定是否允许该请求。它就像 API 客户端的单个秘密密码。

会话 Cookie 是网站使用的传统方法。当用户使用用户名/密码登录时,服务器会创建一个会话(在内存或数据库中存储用户信息),并向客户端发送一个包含会话 ID 的 Cookie。在每个请求中,浏览器会自动发送此 Cookie,服务器查找该会话以了解用户是谁。

OAuth2 令牌

OAuth2 是一个行业标准的授权协议。这是一种更复杂但更灵活的端点保护方法。在 OAuth2 中,客户端不持有简单的密码;相反,它从授权服务器获取访问令牌(有时还有刷新令牌,我们稍后会讨论)。然后,此令牌会作为授权证明(通常通过像 Authorization: Bearer <token>​这样的 HTTP 标头)呈现给 API(资源服务器)。OAuth2 通常用于第三方集成和“使用 X 登录”的场景,但也用于以稳健的方式保护第一方 API。

JSON Web Tokens (JWT):是什么以及为什么?

现代端点安全通常依赖 JSON Web Tokens (JWT) 作为凭证格式,尤其是在像 OAuth2 这样基于令牌的身份验证方案中。JWT 本质上是一个字符串,它断言了关于用户或客户端的一些信息(“声明”),并且经过数字签名,以便服务器可以验证它。让我们详细分解一下:

什么是 JWT? JSON Web Token (JWT) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象的形式传输信息。说它“自包含”是因为令牌本身携带了识别用户和确定其权限所需的数据(声明),并且它是经过签名的(使用密钥或秘钥),因此服务器可以验证它没有被篡改。JWT 由三部分组成:头部、载荷和签名,各部分之间用点号(.​)分隔。例如,一个 JWT 可能看起来像 xxxxx.yyyyy.zzzzz​,其中第一部分是头部,第二部分是载荷(两者都是 Base64 编码的 JSON),第三部分是签名。

本质上,JWT 就像一个用户信息防篡改的密封信封:如果有人更改了里面的数据,签名检查将失败,令牌将被拒绝。而且由于载荷是 JSON 格式,您可以放入服务器可能需要的任何信息(例如用户角色或过期时间戳)。

JWT 如何用于保护端点? 一旦用户登录并获得 JWT,客户端将在每个受保护的请求中包含该令牌(通常在 HTTP Authorization​标头中,格式为 Bearer <token>​)。服务器在每个请求中都会解析 JWT,验证签名(使用它信任的密钥或公钥),如果有效,则使用令牌中的信息来识别用户并授权操作。这是一种无状态的身份验证方法:服务器不需要保存会话数据,因为令牌本身就携带了用户的身份和角色/权限。例如,一个 API 端点可以要求 JWT 包含一个声明 "role":"admin"​;如果令牌中没有这个声明(或者令牌丢失/无效),请求将被拒绝。

JWT 的优点: JWT 结合了 API 密钥和会话的一些优点,同时避免了它们的缺点。它们是无状态且自包含的,因此服务器可以在无需每次请求都进行数据库查找的情况下验证请求。这使得 JWT 非常适合可扩展的微服务架构和 API(其中每个调用理想情况下都应该是独立的,并且每次都发送凭证)。JWT 还可以嵌入用户角色或范围,从而无需再次查找即可实现细粒度的授权。由于 JWT 只是字符串,因此它们可以轻松地跨不同域以及在移动或物联网设备中使用(不像 Cookie 那样与浏览器绑定)。而且因为它们是经过签名的,客户端无法在不被服务器检测到的情况下更改其内容(例如,提升权限)。

JWT 的缺点: JWT 并非万能处方。一个主要问题是被盗后的安全性——JWT 是一个持有者令牌,意味着任何拥有它的人都可以使用它。如果攻击者获取了您的 JWT,他们可以在令牌过期前冒充您。与服务器端会话不同,没有简单的方法可以立即撤销已颁发的 JWT(因为服务器没有关于它的记录)。JWT 在其过期时间之前一直有效——它不能被发行者在中途撤销或更改。(一种变通方法是在服务器上维护一个已泄露令牌的黑名单,但这会重新引入状态和复杂性。)此外,由于 JWT 可以存储数据,因此存在过度信任这些数据的风险。令牌是在某个时间点颁发的,可能会变得过时——例如,如果用户的角色被更改或帐户被撤销,现有的令牌可能仍具有旧的声明,从而在不应该允许访问时允许了访问。因此,令牌应具有较短的生命周期。另一个缺点是:JWT 比简单的会话 ID 或 API 密钥更大——它们携带更多数据(通常约为 300 字节或更多),这对大多数情况来说是次要的,但在每个请求中仍然是开销。最后,虽然 JWT 经过签名以防止篡改,但其载荷默认情况下并未加密。任何拦截令牌或检查它的人(甚至有专门解码 JWT 的网站)都可以读取其内容。这意味着不应将敏感数据(如密码或个人信息)放入 JWT 声明中,除非您对 JWT 载荷进行加密。

使用 JWT:访问令牌和刷新令牌

在使用 JWT 保护 API 时(尤其是在通过 OAuth2 或类似方式时),您通常会处理访问令牌 (access tokens)刷新令牌 (refresh tokens) 。理解它们之间的区别以及它们如何协同工作以平衡安全性和可用性非常重要。

为什么不让访问令牌长期有效并跳过刷新令牌呢?原因是安全性:如果一个访问令牌有效期为(比如说)30 天并且泄露了,攻击者将拥有 30 天的访问权限。通过使用短期有效的访问令牌和刷新令牌,您可以限制攻击窗口。如果访问令牌泄露,它很快就会过期。如果刷新令牌存储得更安全(例如,HttpOnly Cookie 或安全存储)并且仅发送到身份验证服务器,则它不太可能被攻击者拦截。即使刷新令牌被盗,某些系统也会实施保护措施(例如轮换刷新令牌并在使用时使旧令牌失效、IP/设备检查等)以减轻滥用。

使用访问令牌和刷新令牌的身份验证流程: 当用户登录(或通过 OAuth2 进行身份验证)时,服务器会向客户端颁发访问令牌和刷新令牌。客户端通常将访问令牌存储在内存或短期存储中,并将刷新令牌存储在更安全、长期的存储中(因为它有效期更长)。

图:一个基于 JWT 的身份验证流程示例。用户登录并且服务器验证其凭证后,会返回一个包含 JWT(访问令牌)的 API 响应。然后,客户端在后续对受保护端点的请求的`Authorization`标头中包含此 JWT。服务器在每个请求中检查令牌的签名和声明(例如,过期时间、用户角色),如果一切有效,则处理该请求。这种无状态过程取代了传统的会话查找——JWT 本身告诉服务器用户是谁以及请求已通过身份验证。
图:一个基于 JWT 的身份验证流程示例。用户登录并且服务器验证其凭证后,会返回一个包含 JWT(访问令牌)的 API 响应。然后,客户端在后续对受保护端点的请求的`Authorization`标头中包含此 JWT。服务器在每个请求中检查令牌的签名和声明(例如,过期时间、用户角色),如果一切有效,则处理该请求。这种无状态过程取代了传统的会话查找——JWT 本身告诉服务器用户是谁以及请求已通过身份验证。
Authorization

访问令牌会在短时间内过期。假设其生命周期为 15 分钟。用户继续使用应用程序,15 分钟后,下一个 API 调用失败(服务器响应“未授权”或指示令牌已过期的错误代码)。此时,客户端可以自动使用刷新令牌获取新的访问令牌,而不会打扰用户。它向身份验证服务器发出后台请求(例如,一个 POST /auth/refresh​端点),并附带刷新令牌。身份验证服务器验证刷新令牌(检查其是否有效且未过期或撤销)。如果一切正常,它会响应一个新的访问令牌(通常还有一个新的刷新令牌)。然后,客户端将此新的访问令牌用于将来的请求,循环继续。如果刷新令牌本身已过期或无效,则客户端将需要让用户再次登录。

图:令牌刷新工作流程。在此序列中,客户端首先尝试使用过期的访问令牌调用 API 并收到未授权错误。然后,它向授权服务器发送一个请求(步骤 7),其中包含刷新令牌以获取新的访问令牌。授权服务器验证刷新令牌并向客户端返回一个新的访问令牌(通常还有一个新的刷新令牌)。这允许会话继续而无需强制用户重新进行身份验证。
图:令牌刷新工作流程。在此序列中,客户端首先尝试使用过期的访问令牌调用 API 并收到未授权错误。然后,它向授权服务器发送一个请求(步骤 7),其中包含刷新令牌以获取新的访问令牌。授权服务器验证刷新令牌并向客户端返回一个新的访问令牌(通常还有一个新的刷新令牌)。这允许会话继续而无需强制用户重新进行身份验证。

关于刷新令牌,有几点需要注意:由于它们功能强大(任何持有有效刷新令牌的人都可以继续获取新的访问令牌,可能永远如此),因此必须努力保护它们。通常,刷新令牌存储在 HttpOnly Cookie 中或保留在服务器端的会话存储中,尤其是在基于浏览器的应用程序中,这样恶意 JavaScript 就无法窃取它们。一些框架采用刷新令牌轮换机制——每次使用刷新令牌时,服务器都会颁发一个新的并使旧的失效。这样,如果攻击者窃取了刷新令牌并尝试重用它,服务器会注意到它已被使用过并拒绝它,从而有效地将窃贼注销。此外,刷新令牌通常具有固定的生命周期或使用限制(例如,刷新令牌可能在 30 天后或一定次数的使用后过期)。所有这些措施都有助于降低滥用长期凭证的风险。

使用 JWT 的最佳实践

在实施基于 JWT 的安全性时,请牢记以下最佳实践,以保持高水平的安全性:

需要避免的常见 JWT 安全陷阱

最后,让我们强调一些在使用 JWT 保护 HTTP 端点时常见的错误或陷阱(以及如何避免它们):

借助 JWT 和可靠的身份验证设计,我们的 API 可以既安全又用户友好,使授权用户能够与您的服务进行交互,同时将不良行为者拒之门外。

你可能也感兴趣