JWT 刷新令牌流程

303

我正在构建一个移动应用程序,并使用JWT进行身份验证。

似乎最好的方法是将JWT访问令牌与刷新令牌配对,以便我可以随意地频繁过期访问令牌。

  1. 刷新令牌是什么样子的?它是一个随机字符串吗?那个字符串是否被加密?它是另一个JWT吗?
  2. 在这种情况下,刷新令牌应该存储在用户模型上的数据库中,是吗?看起来应该加密它。
  3. 用户登录后,我是否发送刷新令牌,并让客户端访问另一个路由来检索访问令牌?

8
注意,如果您正在使用刷新令牌,应该提供用户在UI上使其失效的功能。建议如果一个月内未使用,则自动过期。 - Vilmantas Baranauskas
5个回答

290
以下是撤销JWT访问令牌的步骤:
  1. 登录时,向客户端响应发送两个令牌(Access token和Refresh token)。
  2. 访问令牌的过期时间较短,刷新令牌的过期时间较长。
  3. 客户端(前端)将刷新令牌存储在httponly cookie中,将访问令牌存储在本地存储中。
  4. 客户端将使用访问令牌调用API。但当它过期时,您可以调用auth server API获取新的令牌(刷新令牌会自动添加到http请求中,因为它存储在cookie中)。
  5. 您的身份验证服务器将公开一个API,接受刷新令牌并检查其有效性,并返回新的访问令牌。
  6. 一旦刷新令牌到期,用户将被注销。
如果您需要更多细节,请告诉我,我也可以分享代码(Java + Spring Boot)。
对于您的问题: Q1:它是另一个JWT,其中包含少量声明和长时间的到期时间。 Q2:它不会在数据库中存储。后端不会在任何地方存储。他们只需使用私钥/公钥解密令牌,并通过其到期时间验证它。 Q3:是的,正确。

78
我认为 JWT 应该存储在“localStorage”中,而“refreshToken”应该存储在“httpOnly”中。 “refreshToken” 可用于获取新的 JWT,因此必须格外小心处理。 - Tnc Andrei
51
使用刷新令牌的好处我不太清楚,是否将访问令牌的有效期延长也能达到同样的效果呢? - user2010955
7
根据微软开发者网络的说法,HttpOnly是包含在Set-Cookie HTTP响应头中的一个附加标志。生成Cookie时使用HttpOnly标志可以帮助减轻客户端脚本访问受保护Cookie的风险(如果浏览器支持的话)。 - ns15
112
#2的内容高度不准确。刷新令牌必须存储在服务器端。您不应该利用JWT的“自包含”属性来作为刷新令牌。这样做会导致您无法撤销刷新令牌,除非更改私钥。 - Jai Sharma
6
如果服务器存储了刷新令牌,当客户端没有刷新令牌时,客户端如何请求新的访问令牌来替换过期的令牌?现在我们需要另一个API来获取已登录用户的刷新令牌吗?请说明在这种情况下该过程将如何工作。 - Subbu
显示剩余19条评论

82

假设这是关于OAuth 2.0的,因为它涉及JWT和刷新令牌...

  1. 就像访问令牌一样,原则上刷新令牌可以是任何东西,包括您描述的所有选项;当授权服务器想要无状态或希望对呈现它的客户端施加某种“持有证明”语义时,可以使用JWT;请注意,刷新令牌与访问令牌不同,因为它只提供给最初颁发它的授权服务器,而不是提供给资源服务器,所以JWT作为访问令牌的自包含验证优化不适用于刷新令牌

  2. 这取决于数据库的安全性/访问权限;如果其他方/服务器/应用程序/用户可以访问数据库,则可以(但是在何处以及如何存储加密密钥可能会因人而异...)

  3. 授权服务器可以同时颁发访问令牌和刷新令牌,具体取决于客户端用于获取它们的授权,规范包含每个标准授权的详细信息和选项。


75
在数据库中存储刷新令牌的哈希值,然后将用户的刷新令牌哈希值与您存储的哈希值进行比较。这里遵循“不要在数据库中存储明文密码”的规则。将令牌视为为用户生成的随机密码。 - tim-phillips
11
另外,如果您想提供更多的安全性,请执行刷新令牌轮换。这个重要性已经在ITEF RFC 6749中提到。如果正确实现,这也可以帮助识别令牌盗窃场景,即攻击者窃取了刷新令牌。如果您正在寻找更好的解释,请访问此链接 - Bhumil Sarvaiya
2
我建议您跟随此链接来保护前端的JWT令牌和刷新令牌。 - Fouad
@Rohmer 我明白为什么需要将刷新令牌视为密码。但我想知道使用刷新令牌而不是使用凭据(用户名和密码)获取新令牌的优势是什么? - Álvaro García
@ÁlvaroGarcía - 这样做起来好像更好,不是吗?你更愿意传递一个令牌还是用户名/密码?我投票支持令牌。 - undefined

49
刷新令牌流程在OAuth 2.0规范文档中有详细描述。
  +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+
  
  
  (A)  The client requests an access token by authenticating with the
       authorization server and presenting an authorization grant.
  
  (B)  The authorization server authenticates the client and validates
       the authorization grant, and if valid, issues an access token
       and a refresh token.
  
  (C)  The client makes a protected resource request to the resource
       server by presenting the access token.
  
  (D)  The resource server validates the access token, and if valid,
       serves the request.
  
  (E)  Steps (C) and (D) repeat until the access token expires.  If the
       client knows the access token expired, it skips to step (G);
       otherwise, it makes another protected resource request.
  
  (F)  Since the access token is invalid, the resource server returns
       an invalid token error.
  
  (G)  The client requests a new access token by authenticating with
       the authorization server and presenting the refresh token.  The
       client authentication requirements are based on the client type
       and on the authorization server policies.
  
  (H)  The authorization server authenticates the client and validates
       the refresh token, and if valid, issues a new access token (and,
       optionally, a new refresh token).

关于你的问题:
1. 这是另一个JWT。 2. 刷新令牌可以存储在客户端后端,以防止用户访问。否则,必须进行加密。 3. 在登录后,您会获得刷新令牌和访问令牌。当访问令牌过期时,您将发送刷新令牌到服务器以获取新的刷新和访问令牌。

3
在资源服务器上同时处理访问令牌和刷新令牌会带来哪些安全风险? - ssamko
@ssamko 这种方式不够安全:如果 access_token 被盗,它会很快过期;如果 refresh_token 被盗,那么有人就能够生成许多新的 access_token。因此,如果资源服务器由于任何原因被黑客攻击,有人就能够创建新的 access_token。 - Dennis Meissel
1
@ssamko,但这里不仅涉及到安全性,还包括了一个概念:资源服务器只检查客户端是否可以访问资源,而客户端负责刷新。此外,资源服务器不必连接到认证服务器。它可以连接,也可以不连接。 - Dennis Meissel
1
你能解释一下为什么刷新令牌必须存储在服务器端吗?你是指它必须存储在数据库中吗?如果是的话,为什么?为什么不能使用私钥/公钥解密令牌呢? - Saba
1
@Saba,你说得对,感谢你的评论。刷新令牌不应该被用户直接访问。如果是这样,它必须加密。我已经更新了第二点以消除歧义。 - Dennis Meissel
显示剩余3条评论

35

基于这个使用Node.js实现JWT与刷新令牌的实例:

  1. 在这种情况下,他们使用一个uid而不是JWT。当他们刷新令牌时,他们发送刷新令牌和用户。如果您将其实现为JWT,则不需要发送用户,因为它已经包含在JWT中了。

  2. 他们在单独的文档(表格)中实现此功能。对我来说很有意义,因为用户可以登录不同的客户端应用程序,并且每个应用程序可能都有一个刷新令牌。如果用户失去安装有某个应用的设备,则可以无效该设备的刷新令牌,而不影响其他登录的设备。

  3. 在此实现中,它用访问令牌和刷新令牌同时响应登录方法。对我来说看起来是正确的。


你所说的“1)在这种情况下,他们使用uid…”是指UUID吗? - ozanmuyes
这个更简单的实现怎么样:
  • 发出JWT
  • 在想要刷新时发送旧的JWT
  • (你可以通过窗口检查'iat')
  • 基于先前的JWT重新发放一个新的
- adonese
如果您只发送包含refresh_token的JWT,@adonese是这个意思吗?如果是这样的话,OAuth RFC 6749明确规定不要将refresh_token发送到资源服务器(而JWT将被发送到资源服务器):https://tools.ietf.org/html/rfc6749#section-1.5 - Brenno Costa

5

JWT存在两个问题:

  1. 规范不好

  2. 很难撤销(用于身份验证时)

第一个问题可以通过使用自己的JWT实现来解决:将想要的任何内容放入JSON中,使用AES加密 - 没问题 - 用于身份验证(如果需要授权:在JSON中添加角色)。

超简洁的JWT {"id" : "<id>"}

第二个问题需要澄清。对于存储在服务器端的常规会话,不存在撤销问题:服务器随时可以使会话失效。但是,常规会话存在可扩展性和性能问题,因此使用JWT。

解决撤销问题的常见方法是使用刷新令牌

以下是如何完成该操作:

  1. 刷新令牌可以与访问令牌完全相同:自定义JSON加密并进行Base64编码。结果字符串可以直接复制。如果访问令牌包含大量数据(例如角色),则刷新令牌可能不同,因为它只需要用户ID。不管是访问令牌还是刷新令牌都没有硬编码的到期时间。
  2. 它们都存储在https_only cookie中,但是访问令牌 cookie的过期时间为2分钟,而刷新令牌 cookie的过期时间为30分钟
  3. 刷新令牌存储在数据库(用户表)中,可以通过从数据库中删除来轻松撤销/使其无效。
  4. 服务器在请求中查找访问令牌:如果存在且有效(可以解密)-则处理请求;
  5. 如果不存在访问令牌(cookie已过期),则服务器会查找刷新令牌:如果存在-则通过将其与存储在数据库中的令牌进行比较并基于来自刷新令牌和数据库的信息生成新的访问令牌,然后处理请求。
  6. 如果没有刷新令牌,则服务器会查找以下内容之一:用户名 -密码对,第三方身份提供者令牌(Google、Facebook等),第三方身份管理系统令牌(Cognito、Okta、JumpCloud)。如果找到任何一个:处理请求并生成新的访问和刷新令牌
  7. 如果什么都没有找到,则服务器发送身份验证错误并强制客户端重新登录用户。

@ValentineShi 是的,对于常规应用程序来说,30分钟可能太短了。这个数字来自于Java企业(银行)的最佳实践。你可以将其设置为30天或其他时间。但是你没有正确描述刷新令牌是多余的。因为刷新令牌存储在数据库中(你可能错过了这一部分),所以它可以随时失效,例如对于被禁止的用户。如果你从方案中删除刷新令牌并在数据库中存储访问令牌,则需要在每个请求中检查它。刷新令牌实际上可以使你的应用程序免于针对每个请求检查访问令牌。 - yurin
整个事情的期望结果是提高用户体验(减少频繁的用户登录)。大多数人类用户API请求通过DB获取数据。每个请求花费额外的2ms进行到带有令牌的用户会话表的旅程不成问题(无论使用访问令牌还是刷新令牌)。刷新令牌不增加安全性,在任何一种方法中,长期存在的令牌都可能被窃取并用于冒充用户。那么我认为刷新令牌没有用处。 - Valentine Shi
2毫秒是明显且故意夸大的())。您的数据库服务器最好与应用程序服务器分开。因此,它将更多,比如50毫秒。然后让我们看看Ebay产品页面-它发送约20个XHRequests。 Ebay页面每秒被访问数千次。因此,通过使用刷新令牌,可以避免每秒数万次的数据库访问。 - yurin
此外,JWT身份验证并不是为了改善用户体验,而是为了无状态服务器和可扩展性。并非每个应用程序都能从中受益。整个过程正在受到合理的批评,许多优秀的开发人员决定不使用JWT进行身份验证,就像您一样。但是,我的答案是关于如何使用JWT进行身份验证的,因为根据我的经验,它比传统的Java方法快得多。 - yurin
同一数据中心内不同服务器之间的速度为0.5毫秒。虽然2毫秒到数据库的行程相当长,但可以配置数据库加载到内存中。我们的工作始终与UX和认证有关,此外还涉及安全性。回到问题上 - 我清楚地表明了刷新令牌在您描述的方案中是多余的 - 既不增加UX也不增加安全性。 PS:延迟数字 - Valentine Shi
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接