如何撤销JWT令牌?

116

6
除非您在授权服务器上构建一些令牌记录,否则无法这样做。消耗该令牌的资源 API 也必须检查该令牌是否已被撤销。 - Shaun the Sheep
你有找到任何东西吗?如果我要建立一个令牌记录,我认为Oauth不是无状态的吗? - aman verma
+1 给 Shaun 的评论,补充说明这通常会破坏使用 JWT(或值类型)令牌的目的。 - Hans Z.
2
Shaun的评论是错误的或至少不完全正确。你必须区分访问令牌和刷新令牌。虽然失效访问令牌没有意义,但可以使用刷新令牌来实现这一点。在一个场景中,例如对于访问令牌的过期时间为15分钟而刷新令牌的过期时间为一周,你可以很容易地看到通过使刷新令牌失效可以实现什么。这样你就不必把任何东西传播到资源服务器上,也不会失去无状态性。 - Fencer
当然,这假定您只在授权服务器上刷新访问令牌。 - Fencer
9个回答

150

通常来说,最简单的答案是说你不能撤销JWT令牌,但这并不是真的。

诚实的答案是,支持JWT撤销的成本足够大,大多数情况下不值得考虑,或者干脆考虑替代方案。

话虽如此,在某些情况下,您可能需要同时使用JWT和立即令牌撤销,因此我们将介绍需要采取的步骤,但首先我们将介绍一些概念。

JWT(学习JSON Web Tokens)只是指定了一个令牌格式,这个撤销问题也适用于通常称为自包含或按值令牌的任何格式。我喜欢后一种术语,因为它与按引用令牌形成良好对比。

按值令牌 - 关联信息(包括令牌生存期)包含在令牌本身中,可以验证该信息源自可信源(数字签名解救了我们)

按引用令牌 - 关联信息存储在服务器端存储中,然后使用令牌值作为键进行获取;由于是服务器端存储,所以关联信息被隐式信任

在JWT大爆炸之前,我们已经在我们的认证系统中处理了令牌; 应用程序通常会在用户登录时创建一个会话标识符,然后将其用于使用户无需每次重复登录过程。这些会话标识符被用作服务器端存储的关键索引,如果这听起来像您最近读到的某些内容,那么您是正确的,这确实属于按引用令牌。
使用相同的类比,理解按引用令牌的撤销是微不足道的; 我们只需删除与该密钥映射的服务器端存储,下一次提供该密钥时它将无效。
对于按值令牌,我们只需要实现相反的操作。当您请求撤销令牌时,您存储了允许您唯一标识该令牌的内容,以便下次接收到该令牌时,您还可以检查它是否被撤销。如果您已经想到这样的事情不会扩展,那么请记住,您只需要存储数据直到令牌过期的时间,并且在大多数情况下,您可能只需存储令牌的哈希值,以便始终是已知大小的东西。
作为最后的说明并且将其集中在OAuth 2.0上,按值访问令牌的撤销目前尚未标准化。尽管如此,OAuth 2.0 Token撤销明确指出,只要授权服务器和资源服务器同意一种自定义的处理方式,就仍然可以实现它:
在前者(自包含令牌)的情况下,当需要立即撤销访问令牌时,授权服务器和资源服务器之间可能使用一些(目前未标准化的)后端交互。
如果您控制授权服务器和资源服务器,则非常容易实现此操作。另一方面,如果您将授权服务器角色委托给像Auth0这样的云提供商或Spring OAuth 2.0这样的第三方组件,则很可能需要以不同的方式处理事物,因为您可能只会得到已经标准化的内容。
一个有趣的参考:
本文介绍了另一种方法:黑名单JWT。其中包含一些有趣的实践和RFC7523遵循的模式。

不要为每个未过期的令牌存储一个哈希(虽然可管理,但可能是大量的哈希),您可以为每个已撤销的令牌存储一个哈希,并检查其缺失而不是存在。 - Jonathan Hartley
4
这段文字表达不是很清楚,但是是的,我在谈论仅针对已被撤销的按值令牌进行存储,并且仅在它们的正常到期时间未到之前进行存储。每个令牌将像通常一样进行验证,然后再进行额外的检查以确定它是否已被撤销。 - João Angelo
因为介绍了“按值传递”和“按引用传递”这两个术语,所以加2/2(=100) :) - user2297550
“按值传递”令牌确实是与会话概念相反的,当会话被“删除”时,您存储了配对的(jti,exp),并分发所有这些配对,而服务器可以独立于任何集中式存储从exp中删除它们。在可扩展性方面表现非常出色,但在吊销jti ID方面仍具有有状态的“反会话”。 - Brian Cannard

23

JWT无法被撤销。

但是这里有一种替代方案,称为JWT旧换新交换模式

因为我们不能在到期时间之前使已发行的令牌无效,所以我们总是使用短期令牌,例如30分钟。当令牌过期时,我们使用旧令牌交换一个新令牌。关键点在于一个旧令牌只能交换一个新令牌

在身份认证中心服务器上,我们维护了一个类似于这样的表:

table auth_tokens(
    user_id,
    jwt_hash,
    expire
)

JWT字符串包含user_id字段,jwt_hash是整个JWT字符串的哈希值,例如SHA256。expire字段是可选的。

以下是工作流程:

  1. 用户使用用户名和密码请求登录API,认证服务器发放一个令牌,并在表中注册该令牌(添加一行)。
  2. 当令牌过期时,用户使用旧令牌请求交换API。首先,认证服务器按照正常方式验证旧令牌,但不检查到期时间,然后创建令牌哈希值,再通过user id在上述表中查找:
    • 如果找到匹配的记录并且user_id和jwt_hash相符,则发出新令牌并更新表格。
    • 如果找到记录,但user_id和jwt_hash不匹配,则意味着有人在之前使用令牌交换了新令牌。该令牌被盗,删除与user_id相关的记录并响应警报信息。
    • 如果没有找到记录,则用户需要重新登录或只输入密码。
  3. 当用户更改密码或注销登录时,按用户ID删除记录。

为了持续使用令牌,合法用户和黑客都需要持续交换新令牌,但只有一个人能成功,一个失败后,两者都需要在下次交换时重新登录。

因此,如果黑客获得了令牌,它可以在短时间内使用,但如果合法用户在下次交换时交换了新令牌,则无法交换新令牌,因为令牌有效期很短。这样更安全。

如果没有黑客,正常用户也需要定期交换新令牌,例如每30分钟一次,这就像自动登录一样。额外的负载不高,我们可以调整应用程序的到期时间。

来源:http://www.jianshu.com/p/b11accc40ba7


1
该解决方案的主要缺点是,在同一时间只能有一个用户登录。 - Michał Stochmal
2
如果用户停止使用系统而黑客替换了新令牌怎么办?听起来像是黑客可以继续使用和交换令牌直到用户再次登录。糟糕。 - Chris H.
2
这个回答不应该被点赞。如上所述,所提出的答案混合了黑名单和刷新,这意味着令牌现在既是“有状态”的又是“单例”的(仅限单一登录),这两者都完全违反JWT原则。然而,所提出的解决方案仍然存在依赖到期时间(留下了一个主要弱点)并引入了一个新问题(单一登录)。因此,这比具有简单身份验证JWT + 刷新令牌和黑名单机制还要糟糕。说实话:这也不是最简单的实现方式(可能存在漏洞)。 - David Zwart

16

这并不完全回答了关于Spring框架的问题,但以下文章讨论了为什么如果您需要撤销JWT,则可能不想一开始就使用JWT,而是使用常规的不透明Bearer令牌。

https://www.dinochiesa.net/?p=1388


1
为什么不使用缓存或带有cron的关系型数据库,并定期删除每个条目,而不是等待呢? - Narcisse Doudieu Siewe

12

撤销JWT的一种方法是利用分布式事件系统,当刷新令牌被撤销时通知服务。身份提供者在撤销刷新令牌时广播一个事件,其他后端/服务监听该事件。当收到事件时,后端/服务将更新本地缓存,以维护已撤销刷新令牌的用户集合。

每当验证JWT时,就会检查此缓存以确定是否应撤销JWT。这完全基于JWT的持续时间和各个JWT的过期时间。

本文Revoking JWTs说明了这个概念,并在Github上提供了一个示例应用程序。


2
“撤销JWT”链接现在返回404错误。 - DenverCoder9
7
@DenverCoder9 可能令牌已成功撤销。 - Jan Ciołek
撤销 JWT - https://fusionauth.io/learn/expert-advice/tokens/revoking-jwts - Raja S

12

针对谷歌员工:

  • 如果您实现了纯粹的无状态身份验证,那么没有任何撤销令牌的方法,因为令牌本身是唯一的真相来源。
  • 如果在服务器上保存已吊销令牌ID列表并检查每个请求是否与此列表匹配,那么它本质上就是有状态身份验证的一种变体。
  • 像Cognito这样的OAuth2提供程序提供了一种“登出”用户的方式,但是它只会真正吊销刷新令牌,刷新令牌通常具有长期性,并且可以多次使用以生成新的访问令牌,因此必须吊销;现有的访问令牌仍然有效,直到它们过期。

2
有没有替代JWT的方案,既能支持令牌吊销,又能用于实现纯状态认证? - Gandalf

0

关于按引用和按值传递令牌的答案已经很好地解决了这个问题。对于那些将来会遇到这个问题的人们。

如何在RS端实现撤销:

简而言之:

获取一个缓存或数据库,该缓存或数据库对所有验证令牌的后端服务实例都可见。当收到新的令牌以进行撤销时,如果它是有效的(即根据您的jwt验证算法进行验证),则获取exp和jti声明,并将jti保存到缓存中,直到达到exp。然后在unixNow变成> exp后,使缓存中的jti过期。

然后在其他端点上进行授权时,每次检查给定的jti是否与此缓存中的某些内容匹配,如果是,则出现403错误,表示令牌已被撤销。一旦它过期,您的验证算法就会出现常规的令牌过期错误。

附注:通过仅保存jti在缓存中,您使这些数据对任何人都无用,因为它只是一个唯一的令牌标识符。


1
我想评论一下关于“在其他端点上进行授权时,您每次都要检查给定的jti是否与此缓存中的某些内容匹配”的问题,它会在分发撤销列表时增加一些一致性同步延迟。JWT并不神奇,它只是颠倒了会话的概念,是其概念上的“补充”。尽管如此,审计跟踪仍然需要强制撤销并查看使用了哪个IP:端口和哪组凭据。有趣的东西! - Brian Cannard
1
确实很有趣。我的想法是,您仍然可以避免延迟。大多数令牌将在验证算法阶段被丢弃,这就是您的实现方式。然后为吊销的令牌进行廉价缓存查询。 如果有人坚持使用已吊销的令牌,您的智能系统应该能够找出并隔离这种情况。 - bigkahunaburger

0

JWT吊销的最佳解决方案是使用短期过期时间、刷新并将已发行的JWT令牌保存在共享的离线缓存中。例如,使用Redis特别容易,因为您可以将缓存键设置为令牌本身(或令牌的哈希值),并指定到期时间,以便自动驱逐令牌。


0
那么将 JWT 令牌存储并将其与数据库中的用户进行关联如何呢?通过在执行 JWT 比较后在后端应用程序中扩展卫兵/安全系统并进行额外的 DB 连接,您将能够通过从数据库中删除或软删除它来实际上“撤销”它。

您必须像对待密码一样处理 token,因此不要存储 token 本身,而是存储其哈希值(如 bcrypt、scrypt 等)。但是,您将引入一个额外的 token 验证步骤,需要集中化,这将抵消 JWT 的一个优点,那么在那种情况下,您真的想要 JWT 吗?实际上,有人链接到以下文章,更详细地表达了相同的观点:https://www.dinochiesa.net/?p=1388. - Chris H.

-5

我找到了解决问题的一种方法,如何使用Java过期已生成的现有JWT令牌?

在这种情况下,我们需要使用任何DB或内存,其中:

步骤1:当为用户第一次生成令牌时,请将其与令牌及其“issuedAt()”时间一起存储在db中。

我以JSON格式将其存储在DB中,如下所示:

Ex: {"username" : "username", "token" : "token", "issuedAt" : "issuedAt" }

步骤2:一旦您收到同一用户的Web服务请求以验证令牌,请从令牌中提取“issuedAt()”时间戳,并将其与存储的(DB /内存)发行时间戳进行比较。

步骤3:如果存储的发行时间戳是新的(使用after() / before()方法),则返回该令牌无效(在这种情况下,我们实际上没有使令牌过期,但我们停止使用该令牌访问权限)。

这是我解决问题的方法。


这就像在数据库中存储密码一样。不要这样做。你可以存储哈希值,比如bcrypt、scrypt等,但关键是要像处理密码一样处理令牌。 - Chris H.
如果不考虑存储用户名/令牌,这不是一种有效的方法吗?比如只在用户记录中存储一个issuedAt时间戳,当检查它与数据库中的数据时,您可以使用它来使之前发行的任何令牌无效。唯一的缺点是只有最近的令牌才有效,但您可以在数据库中定义一个时间来设置新的issuedAt,而不是每次生成令牌时都设置,此后生成的任何令牌都将有效。 - Michael Brook
问题是关于自包含令牌而非有状态令牌的。 - Metalhead

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