如何撤销JWT令牌?


77

我正在使用Spring Security OAuth2和JWT令牌。我的问题是:如何撤销JWT令牌?

如此处 http://projects.spring.io/spring-security-oauth/docs/oauth2.html所述,吊销是通过刷新令牌完成的。但这似乎不起作用。


3
除非您建立授权服务器上发布的令牌的一些记录,否则您不能这样做。消耗令牌的资源API还必须检查令牌是否被吊销。
肖恩·绵羊

您是否找到任何东西?另外,如果我要建立令牌记录,我认为Oauth不是无状态的?
阿曼·维尔玛,2016年

对Shaun的评论+1,并补充说,这样做通常会击败让JWT(或按值)令牌这样做的观点。
Hans Z.

2
肖恩的评论是错误的,或者至少不是完全正确的。您必须区分访问令牌和刷新令牌。尽管使访问令牌无效没有意义,但是您可以使用刷新令牌来实现。如果您的访问令牌的到期时间为15,而刷新令牌的有效时间为一周,那么您可以轻松地看到使刷新令牌无效可以实现的目标。这样,您不必将任何内容传播到资源服务器,也不会失去无状态性。
击剑者'18

...当然,这假定您仅在身份验证服务器上刷新访问令牌。
击剑者

Answers:


98

通常,最简单的答案是说您无法撤消JWT令牌,但这不是真的。诚实的答案是,支持JWT撤销的成本足够大,以致于大多数时候都不值得,或者明确地重新考虑JWT的替代方案。

话虽如此,在某些情况下,您可能需要JWT和立即的令牌吊销,因此让我们仔细研究一下它会发生的情况,但是首先我们将介绍一些概念。

JWT(学习JSON Web令牌)仅指定令牌格式,此吊销问题也将应用于通常称为自包含或按值令牌的任何格式。我喜欢后一种术语,因为它与副引用标记形成了很好的对比。

按值令牌-令牌本身包含相关信息,包括令牌生存期,并且可以验证该信息是否源自受信任的源(用于救援的数字签名)

引用令牌-关联信息保存在服务器端存储中,然后使用令牌值作为键获得该信息;作为服务器端存储,关联的信息被隐式信任

在JWT大爆炸之前,我们已经在身份验证系统中处理过令牌。应用程序通常在用户登录时创建会话标识符,然后使用该会话标识符,这样用户不必每次都重复登录过程。这些会话标识符被用作服务器端存储的关键索引,如果这听起来与您最近阅读的内容相似,那么您是对的,的确将其归为按引用标记。

使用相同的类比,了解按引用令牌的撤销是微不足道的。我们只是删除映射到该密钥的服务器端存储,而下次提供该密钥将无效。

对于按值代币,我们只需要执行相反的操作即可。当您请求吊销令牌时,会存储一些东西,使您可以唯一地标识该令牌,以便下次收到令牌时,您还可以检查它是否已被撤销。如果您已经在考虑像这样的事情将无法扩展,请记住,您只需要存储数据,直到令牌到期为止,在大多数情况下,您可能只需要存储令牌的哈希值,这样就可以始终是已知大小的东西。

最后要注意的是,以OAuth 2.0为中心,按值访问令牌的撤销目前尚未标准化。但是,OAuth 2.0令牌撤销明确指出,只要授权服务器和资源服务器都同意使用自定义方式来处理此问题,仍可以实现:

在前一种情况(自包含令牌)中,当需要立即访问令牌吊销时,可以使用授权服务器和资源服务器之间的某些(当前未标准化)后端交互。

如果您同时控制授权服务器和资源服务器,则非常容易实现。另一方面,如果将授权服务器角色委派给Auth0之类的云提供商或Spring OAuth 2.0之类的第三方组件,则您很可能需要采用不同的方法,因为您可能只会得到已经标准化的内容。

一个有趣的参考

本文介绍了另一种实现方法:黑名单JWT 包含一些有趣的习惯和模式,后跟RFC7523


而不是为每个未过期的令牌存储一个哈希值(尽管这是可管理的,但可能会有很多哈希值),您是否可以为每个已撤销的令牌存储一个哈希值,并检查它是否存在而不是存在?
乔纳森·哈特利

2
文字内容不是很清楚,但是是的,我是在谈论只为被撤消的按值代币存储一些东西,也只能存储直到它们的正常到期为止。每个令牌都会像往常一样经过验证,然后再进行一次额外的检查,以查看是否未被吊销。
若昂·安杰洛

22

JWT无法撤销。

但是,这里有一个称为JWT的替代解决方案,用于新的交换模式

由于我们无法在到期时间之前使已发行的令牌失效,因此我们始终使用短时令牌,例如30分钟。当令牌过期时,我们使用旧令牌交换新令牌。关键的一点是一个老令牌可以换取一个新的令牌

在中心身份验证服务器中,我们维护一个像这样的表:

table auth_tokens(
    user_id,
    jwt_hash,
    expire
)

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

以下是工作流程:

  1. 用户使用用户名和密码请求登录API,身份验证服务器颁发一个令牌,然后注册该令牌(在表中添加一行)。
  2. 当令牌过期时,用户请求使用旧令牌的交换API。首先,身份验证服务器会验证除过期检查外的旧令牌是否正常,然后创建令牌哈希值,然后按用户ID在表上方查找:
    • 如果找到的记录与user_id和jwt_hash匹配,则发出新令牌并更新表。
    • 如果找到记录,但user_id和jwt_hash不匹配,则表示某人之前使用过交换新令牌的令牌。该令牌将被黑客入侵,通过user_id删除记录,并通过警报信息进行响应。
    • 如果找不到记录,则用户需要再次登录或仅输入密码。
  3. 使用时更改密码或注销,请按用户ID删除记录。

要连续使用令牌,合法用户和黑客都需要连续交换新令牌,但是只有一个可以成功,如果一个失败,则都需要在下一次交换时再次登录。

因此,如果黑客获得了令牌,那么令牌可以使用很短的时间,但是如果合法用户下次交换新令牌,则该令牌就不能使用,因为令牌的有效期很短。这样更安全。

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

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


1
该解决方案的主要缺点是,同时只能有一个用户登录。
MichałStochmal

1
如果用户停止使用系统并且黑客交换了新令牌怎么办?听起来黑客会继续使用和交换令牌,直到用户再次登录为止。uck
克里斯·H

这是不应被否决的答案。如前所述,建议的答案将黑名单与刷新混在一起,这意味着令牌现在既是“有状态的”又是“单身的”(仅单次登录),两者都完全违反了JWT原则。然而,所提出的解决方案仍然存在取决于到期时间的问题(留下主要缺点),同时引入了新的解决方案(单一登录)。因此,这比拥有简单的auth jwt +刷新令牌和黑名单机制还要糟糕。太老实了:它也不是最简单的实现(可能的错误)。
David Zwart


11

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

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

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


2
该“正在撤销JWT”链接现在返回404。
DenverCoder20年

1

对于Google员工:

  • 如果您实施纯无状态身份验证,则无法撤消令牌,因为令牌本身是事实的唯一来源
  • 如果您在服务器上保存了一个吊销的令牌ID列表,并对照该列表检查每个请求,那么它实际上是有状态认证的一种
  • 像Cognito这样的OAuth2提供程序提供了一种“注销”用户的方法,但是,它只能真正撤消刷新令牌,该刷新令牌通常是长期存在的,可以多次使用以生成新的访问令牌,因此必须撤消;现有访问令牌在到期之前仍然有效

0

如何存储JWT令牌并将其引用给数据库中的用户?在执行JWT比较之后,通过使用附加的数据库联接扩展后端应用程序中的Guards / Security Systems,您实际上可以通过从数据库中删除或软删除来“撤消”它。


您必须像对待口令一样对待令牌,因此与其存储令牌本身,不如将其哈希存储为la bcrypt,scrypt等。但是,您将引入一个额外的令牌验证步骤,该步骤需要集中化,取反JWT的优势之一,那么您当时真的想要JWT吗?实际上,有人链接到下面的文章,更详细地表达了相同的观点:dinochiesa.net/?p=1388
Chris H.

-3

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

在这种情况下,我们需要使用任何数据库或内存,

第1步:首次为用户生成令牌后,立即将其与令牌一起存储在db中,并且该时间为“ issueAt() ”。

我以JSON格式将其存储在数据库中,

例如: { "username" : "username", "token" : "token", "issuedAt" : "issuedAt"}

第2步:一旦您获得了具有要验证的令牌的同一用户的Web服务请求,便从令牌中获取“ issueAt() ”时间戳,并将其与存储的(DB /内存)发布的时间戳进行比较。

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

这就是我解决问题的方式。


这就像在数据库中存储密码。不要那样做 您可以存储散列,而不是la bcrypt,scrypt等。但是重点是要像对待密码一样对待令牌。
克里斯·H

如果您不考虑存储用户名/令牌,这不是有效的方法吗?假设您只是在用户记录上存储了一个namedAt时间戳,则可以使用它来使针对数据库进行检查时在最新令牌之前发布的所有先前令牌失效。唯一的缺点是只有最新的令牌才有效,但是您可以在定义的时间(而不是每次生成令牌时)在数据库中设置新的issedAt,并且在该时间之后生成的所有令牌都将有效。
迈克尔·布鲁克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.