使用JWT令牌身份验证时,刷新令牌是否真的必要?

103

我参考了另一个关于使用JWT的refresh tokens的SO帖子。

JWT(JSON Web Token)到期时间的自动延长

我有一个非常常见的应用程序架构,我的客户端(Web和移动)与REST API通信,然后再与服务层和数据层交互。

enter image description here

我理解JWT令牌身份验证,但我有点困惑如何使用refresh tokens。

我希望我的JWT身份验证具有以下属性:

  1. JWT令牌的过期时间为2小时。

  2. 令牌每小时由客户端刷新。

  3. 如果用户令牌未被刷新(用户处于非活动状态且应用未打开)并且已过期,则他们需要重新登录以恢复登录状态。

我看到很多人声称使用refresh token来使这个过程更好,但我没有看到它的优势。似乎添加了一个管理复杂性。

我的问题如下:

  1. 如果我要使用refresh token,对于该token良好的实践,长期过期时间是否仍然有益?
  2. 如果我要使用refresh token,那么该token会与userId和/或JWT token一起持久化吗?
  3. 当我每隔1小时更新我的token时,这是如何工作的?我需要创建一个接受我的JWT token或refresh token的端点吗?这会更新我的原始JWT token的到期日期还是创建一个新的token?
  4. 鉴于这些细节,是否需要refresh token?似乎如果用户只是使用JWT token来获取新的token(按照上面的链接),那么refresh token就不再需要了。
4个回答

217
稍后我会回答你的问题,现在先讨论一下刷新令牌的整个目的。
情况如下:
用户打开应用并提供登录凭据。现在,应用程序很可能正在与REST后端服务进行交互。REST是无状态的,没有一种方法来授权访问APIs。因此,在讨论中到目前为止,没有办法检查授权用户正在访问APIs还是只是一些随机请求。
现在为了能够解决这个问题,我们需要一种方法来知道请求是否来自授权用户。所以,我们引入了一个叫做访问令牌的东西来解决这个问题。现在,一旦用户成功验证身份,就会发放一个访问令牌。该令牌应该是一个长且高度随机的令牌(以确保它不能被猜测)。这就是JWT的作用所在。现在您可能希望在JWT令牌中存储任何用户特定的详细信息,也可能不希望这样做。理想情况下,您只想在JWT中存储非常简单、极其不敏感的详细信息。 JWT库本身会处理JWT哈希的操纵以检索其他用户的详细信息(IDOR等)。
因此,我们现在解决了关于授权访问的问题。
现在我们讨论攻击场景。假设使用上述所有内容,用户Alice通过应用程序获得了授权的访问令牌,现在她的应用程序可以根据自己的授权向所有API发出请求并检索数据。假设Alice“某种方式”失去了访问令牌,或者换句话说,入侵者Bob获得了访问Alice访问令牌的权限。现在,尽管未经授权,Bob也可以向Alice被授权的所有APIs发出请求。
我们理想情况下不希望发生这种事情。
解决这个问题的方法是:
1. 检测此类事件是否正在发生。 2. 缩短攻击窗口本身。
仅使用访问令牌很难实现以上第一种条件,因为无论是Alice还是Bob,都使用相同的授权令牌,因此两个用户的请求是无法区分的。
因此,我们尝试实现上述第二种条件,因此将有效期限添加到访问令牌的有效性中,例如,访问令牌在“t”(短暂)时间内有效。
它如何有助于?即使Bob拥有访问令牌,他也只能在有效期内使用它。一旦它过期,他就必须再次获取它。现在,当然,您可以说他可以用与第一次获得它相同的方式再次获得它。但是又有什么东西是100%安全的呢!
上述方法仍然存在一个问题,在某些情况下是无法接受的。当访问令牌过期时,需要用户重新输入登录凭据并再次获取授权访问令牌,这至少在移动应用程序的情况下是不好的(不可接受的)用户体验。 解决方案:这就是刷新令牌的作用。它是一个随机且不可预测的令牌,与访问令牌一起首次发放给应用程序。该刷新令牌是一个非常长期的特殊令牌,确保在访问令牌过期后立即向服务器请求新的访问令牌,从而无需用户重新输入登录凭据以检索新的已授权访问令牌,一旦现有的访问令牌过期。

现在您可能会问,Bob也可以访问刷新令牌,就像他损害了访问令牌一样。是的,他可以。但是,现在很容易识别此类情况,这在只使用访问令牌的情况下是不可能的,并采取必要措施减少造成的损害。

如何实现?

对于每个经过身份验证的用户(通常是移动应用程序),都会向应用程序发放一对一映射的刷新令牌和访问令牌。因此,在任何时候,对于单个经过身份验证的用户,将只有一个与刷新令牌对应的访问令牌。现在假设Bob已经破坏了刷新令牌,他会使用它生成访问令牌(因为只有访问令牌才被授权通过API访问资源)。由于Alice的访问令牌仍然有效,当Bob(攻击者)使用新生成的访问令牌请求时,服务器将看到这是一个异常情况,因为对于单个刷新令牌,一次只能有一个已授权的访问令牌。识别出该异常,服务器将销毁有问题的刷新令牌以及其所有相关联的访问令牌。从而防止任何进一步访问,无论是真实的还是恶意的,以获取任何需要授权的资源。 用户Alice将需要再次使用她的凭据进行身份验证并获取有效的刷新和访问令牌。

当然,您仍然可以争辩说Bob可能再次获得刷新和访问令牌并重复上述整个故事,从而可能导致对实际的真正客户Alice进行拒绝服务攻击,但是没有什么像100%安全的东西。

作为良好的做法,刷新令牌应该具有到期时间,尽管非常长。


12
这是一个很好的回答,但它也让我产生了一些问题。如果Bob没有访问Alice的手机并且令牌仅通过HTTPS发送,他可能通过什么方式窃取访问令牌?你说:“对于每个经过身份验证的用户(通常是移动应用程序),将向应用程序发放一对一映射的刷新令牌和访问令牌对。”这是否意味着Alice不能在她的手机和台式机上使用同一个令牌?如果是这样,那么Bob在另一台机器上使用同一个令牌就相当于Alice在不同的设备上使用该令牌,对吗? - nomad
31
在以下情况下:"一旦Bob(攻击者)使用新生成的访问令牌发出请求,由于Alice(真正的用户)的访问令牌仍然有效,服务器将会将其视为异常情况,因为对于单个刷新令牌,一次只能有一个授权的访问令牌",服务器如何知道这是异常情况?因为现有的访问令牌尚未过期吗?如果是这样,那与合法的过期前刷新调用有什么区别吗?服务器如何知道这是异常情况呢?因为现有的访问令牌仍然有效,所以当攻击者Bob使用新生成的访问令牌发出请求时,服务器就会认为这是一个异常情况。这与合法的过期前刷新调用不同之处在于,合法的刷新调用会产生一个新的、唯一的访问令牌,并且原来的访问令牌将被废除。 - georaldc
5
已登录的用户只有一个访问令牌和一个刷新令牌。如果攻击者获取了刷新令牌并创建了另一个访问令牌,那么身份验证服务器应该检测到用户存在两个不同的访问令牌,并吊销刷新令牌和这两个访问令牌,强制用户重新发送其凭据以获取新的访问令牌和新的刷新令牌。 - Stephane
16
如果Alice长时间没有登录以刷新她的访问令牌怎么办?假设Alice在晚上签出并且她的访问令牌自然过期了,但她的刷新令牌仍然有效几天。在这种情况下,Bob是否可以使用Alice的刷新令牌来生成一个新的访问令牌?因为由于自然过期,与刷新令牌配对的有效访问令牌不再存在于数据库中。我可能误解了最后一次检查,但听起来好像唯一的方法是通过检查请求时是否仅存在有效的访问令牌来确定是否有人获取了您的刷新令牌。 - BLang
6
回答有一些缺陷。"辨识出异常后,服务器将销毁相关的刷新令牌及其关联的所有访问令牌"。这并不是自动发生的。无效化刷新令牌并不意味着访问令牌会被销毁。访问令牌会在到期后失效。"因为对于单个刷新令牌,同时只能存在一个授权的访问令牌"。在访问令牌到期之前,可以预先请求更多的访问令牌。因此,这看起来不正确。 - bornfree
显示剩余14条评论

25
我认为在这种情况下,您可以仅使用访问令牌,为客户提供更加便捷的生活,同时保持刷新令牌的安全性优势。
具体实现如下:
1. 当用户使用凭据(用户名/密码)登录时,返回一个短期JWT。还需要创建一个数据库记录,其中包含:
- JWT ID - 用户ID - IP地址 - 用户代理 - 有效标志(默认为TRUE) - 创建时间 - 更新时间
2. 您的客户端在每个请求中提交JWT。只要JWT未过期,它就可以访问资源。如果JWT过期,则在幕后刷新并返回新的JWT和附加的X-JWT头部。
3. 当客户端收到带有X-JWT头的响应时,废弃旧JWT并在以后的请求中使用新JWT。
JWT刷新在服务器上的工作方式:
1. 使用JWT ID查找相应的数据库记录。 2. 检查有效标志是否仍为true,否则拒绝。 3. 可选地,您可以将请求IP地址和用户代理与存储的IP地址和用户代理进行比较,并在出现可疑情况时拒绝。 4. 可选地,您可以检查数据库记录的创建时间或更新时间字段,并在时间过长时决定不刷新。 5. 更新数据库记录中的updatedAt字段。 6. 返回新的JWT(基本上是过期的JWT的副本,但具有更长的过期时间)。
此设计还可以为您提供撤销用户的所有令牌的选项(例如,如果用户丢失手机或更新密码)。
优点:
- 您的客户端永远不必检查过期时间或进行刷新令牌。 请求库功能仅限于在响应头检查X-JWT标头。
  • 您可以根据IP地址、用户代理、最大令牌年龄或这些组合添加自定义刷新逻辑。
  • 您可以撤销某个或所有用户的令牌。

  • 顺便提一下:如果我们正在进行CORS请求,则自定义的X-JWT标头将不可用。 - tuler
    如果您想在CORS中公开自定义的X-JWT标头,您需要将其包含在“Access-Control-Expose-Headers”标头中。另一个选项是将其作为元数据包含在响应正文中。 - alexishevia
    为什么要返回新的JWT(基本上是过期JWT的副本)?难道不是它的整个目的就是更改令牌,给用户一个新的吗? - Nick
    @alexishevia 为什么要_Return新的JWT(基本上是过期JWT的副本)?难道不是整个目的就是更改令牌,给用户一个新的吗? - Nick

    10

    如果我使用刷新令牌,对于该令牌的长期过期时间进行良好实践是否仍然有益?

    刷新令牌具有长期生命周期,访问令牌具有短期生命周期。

    如果我使用刷新令牌,该令牌是否将与userId和/或JWT令牌一起持久化?

    它将作为单独的令牌与JWT一起存储在客户端上,但不在JWT内部。UserID / UID可以存储在JWT令牌本身中。

    当我每小时更新我的令牌时,它是如何工作的?我需要创建一个接受我的JWT令牌或刷新令牌的终端吗?这会更新我原始JWT令牌的过期日期还是创建一个新令牌?

    是的,您需要一个独立的服务来发放和刷新令牌。它不会更新现有JWT令牌的到期时间。令牌只是base64编码的JSON字段值对。因此更改数据会更改输出结果。令牌还具有发行日期,每次发行(刷新)时至少会更改,因此每个令牌都是唯一且新的。旧令牌将自动过期,因此所有访问令牌都需要设置过期时间,否则它们将永远存在。

    这里的另一个答案声称,当您发出新令牌时,旧令牌会被销毁。但这并不正确。令牌无法被销毁。实际上,您可以通过不断联系授权服务器并使用刷新令牌请求新的有效令牌来获取数百个令牌。每个访问令牌都将在其到期前有效。因此,有效期是必要的,而且应该很短。
    鉴于以上信息,是否真的需要刷新令牌呢?看起来如果用户只是使用JWT令牌来获取新令牌(参考上面提供的链接),那么刷新令牌就没有用了。
    JWT令牌具有客户端声明。例如,JWT令牌上的“is_manager:true”声明可能允许访问经理级别功能。现在,如果您决定将用户从经理降级为承包商,则不会立即生效。用户可能仍在使用旧令牌。最终,当该令牌过期时,他会访问认证服务器以刷新令牌。认证服务器会发放一个新的没有管理声明的令牌,用户将无法再访问管理功能。这会创建一个窗口,在该窗口期间,用户的声明与服务器不同步。这再次解释了为什么访问令牌应该是短暂的,以便可以经常进行同步。

    实质上,您每15分钟更新一次授权检查,而不是在每个请求上都进行检查(这是典型的基于会话的身份验证的工作方式)。 如果您想要实时权限而不是每15分钟刷新一次,则 JWT可能不适合


    5
    "令牌无法被摧毁。"谢谢。我不敢相信其他答案会得到这么多票. . . JWT的整个重点在于您不需要数据库来检查哪些令牌是有效的或无效的。它应该是无状态的. . - johnsimer
    1
    实际上,我会为管理员访问实现双重检查。如果isManager为true,则意味着要检查数据库以获取管理员访问权限。如果标志为false,则用户将立即被拒绝访问资源。 - Gary
    你可以采用混合方法,其中内存中保存了“未过期但无效”的令牌列表(如果您的应用程序用户数量较少)。实际的“未过期但无效”令牌以其到期时间为TTL保存在Redis中 - 它们每秒在后台从Redis中刷新 - 这样您还可以为用户提供注销功能。 - DMin
    虽然不需要为您的令牌使用中央数据库,但您可以通过更新用于创建它们的密钥来有效地使令牌失效。当用户更新其凭据或使设备失效时,这是一个好主意。 - WOLVERTRON

    0

    根据情况而定

    JWT的解剖


    • 应包含非敏感信息
    • 应有一个时间范围
    • 应有一个唯一的密钥

    注意 关于JWT,你需要时刻记住一件事,那就是在安全性、便利性和性能之间总会存在一种权衡(在某些情况下可以忽略不计的性能损失)。

    有状态(安全性) vs 无状态(便利性和性能)

    有状态令牌

    你希望在令牌泄露或落入错误的手中时能够维护令牌的状态。有不同的方法来保持令牌的状态。

    白名单:将令牌ID“jwt.kid”存储在数据库或缓存系统(如Redis或memcached)中,并设置自动过期时间。每次收到请求时,验证令牌,然后检查存储中对应的“jwt.kid”值。如果未找到,则拒绝请求。这样可以更好地控制令牌。您可以通过从存储中删除令牌来在任何时间点上撤销令牌。但是,正如您所见,每次请求时访问存储可能会增加开销。
    黑名单:将令牌ID永久存储在某些持久性存储中,如果在那里找到令牌ID,则拒绝该令牌。
    无状态令牌
    不维护任何状态意味着任何人都可以使用令牌进行身份验证请求。您无法撤销无状态令牌。您唯一的希望是令牌的过期时间。
    身份验证流程
    单个令牌:访问令牌

    根据使用情况,可以是长期有效或短期有效。一旦过期或被撤销,需要使用凭据进行身份验证。

    多个令牌:访问令牌和刷新令牌

    将访问令牌和额外令牌结合起来,这种组合确实是为了用户体验。这里的情况是,访问令牌的有效期较短,可以用于执行各种请求。刷新令牌仅在访问令牌过期后的一段时间内有效,它本身也有一个过期时间。时间限制防止使用刷新令牌通过请求新的令牌对销毁对应的访问令牌。刷新令牌不能用于其他任何操作,只能用于请求新的访问令牌,而新的访问令牌会返回一对新的访问令牌和刷新令牌,从而使发出请求的刷新令牌失效。

    因此,即使两个令牌都泄漏了,只有访问令牌被视为有效,直到被撤销或过期。刷新令牌只有在访问令牌自然过期并在其自身使用期内才有效。


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