如何撤销 JWT 令牌?
Posted
技术标签:
【中文标题】如何撤销 JWT 令牌?【英文标题】:How can I revoke a JWT token? 【发布时间】:2015-11-02 08:07:30 【问题描述】:我正在使用 Spring Security OAuth2 和 JWT 令牌。我的问题是:
正如这里提到的 http://projects.spring.io/spring-security-oauth/docs/oauth2.html,撤销是通过刷新令牌完成的。但这似乎不起作用。
【问题讨论】:
你不能,除非你在授权服务器上建立了一些令牌的记录。使用令牌的资源 API 还必须检查令牌是否被撤销。 你有没有发现什么??另外,如果我要建立令牌记录,我认为 Oauth 不是无状态的?? +1 到 Shaun 的 cmets,并补充说它通常会破坏使用 JWT(或按值)令牌这样做的意义。 Shaun 的评论是错误的,或者至少不完全正确。您必须区分访问令牌和刷新令牌。虽然使访问令牌无效没有意义,但您可以使用刷新令牌来做到这一点。在您的访问令牌到期时间为例如 15 并且刷新令牌可能为一周的情况下,您可以轻松地看到通过使刷新令牌无效可以实现的目标。这样您就不必向资源服务器传播任何内容,也不会失去无状态。 ...这当然假设您只刷新身份验证服务器上的访问令牌。 【参考方案1】:一般来说,关于引用标记与价值标记的答案已经确定了。对于那些将来偶然发现这个空间的人。
RS端如何实现撤销: TL;博士: 获取对所有正在验证令牌的后端服务实例可见的缓存或数据库。当一个新的令牌到达撤销时,如果它是一个有效的,(即根据你的 jwt 验证算法进行验证),获取 exp 和 jti 声明,并将 jti 保存到缓存,直到达到 exp。然后一旦 unixNow 变为 > exp,就使缓存中的 jti 过期。
然后在其他端点上进行授权时,您每次都检查给定的 jti 是否与此缓存中的某些内容匹配,如果是,则会出现 403 错误,表示令牌已撤销。一旦过期,您的验证算法中就会出现常规的 Token Expired 错误。
附:通过将 only jti 保存在缓存中,您可以使这些数据对任何人都无用,因为它只是一个唯一的令牌标识符。
【讨论】:
我想评论 w.r.t. “在授权在其他端点上,你检查每次如果一个给定的jti匹配这个缓存中的东西”它在分发撤销列表时增加了撤销令牌的一些一致性同步延迟. JWT 并不神奇,它们只是颠倒了会话的概念,成为它的概念“补充”。尽管如此,仍需要审计跟踪来强制撤销并查看使用了哪些 IP:port 以及哪些凭据集。有趣的东西! 确实很有趣。我的想法是你仍然可以避免延迟。大多数令牌将在验证算法阶段丢弃,这就是您实施的方式。然后对已撤销的令牌进行廉价的缓存查询。那么如果有人真的坚持撤销令牌,你的情报系统应该找出并隔离这种情况。【参考方案2】:对于 Google 员工:
如果您实施纯无状态身份验证,则无法撤销令牌,因为令牌本身是唯一的真实来源 如果您在服务器上保存已撤销令牌 ID 的列表并根据该列表检查每个请求,那么它本质上是有状态身份验证的一种变体 像 Cognito 这样的 OAuth2 提供程序提供了一种“退出”用户的方法,但是,它只真正撤销了刷新令牌,该令牌通常是长期存在的,并且可以多次使用以生成新的访问令牌,因此必须被撤销;现有的访问令牌在过期之前仍然有效【讨论】:
【参考方案3】:一般来说,最简单的答案是说您不能撤销 JWT 令牌,但事实并非如此。诚实的答案是,支持 JWT 撤销的成本足够大,以至于在大多数情况下不值得,或者明确地重新考虑 JWT 的替代方案。
话虽如此,在某些情况下,您可能需要 JWT 和立即撤销令牌,所以让我们来看看它需要做什么,但首先我们将介绍一些概念。
JWT (Learn JSON Web Tokens) 只是指定了一种令牌格式,这个撤销问题也适用于通常称为自包含或按值令牌的任何格式。我喜欢后一种术语,因为它与引用标记形成了很好的对比。
按值令牌 - 相关信息,包括令牌生命周期,包含在令牌本身中,并且可以验证信息是否来自受信任的来源(救援的数字签名)
by-reference token - 相关信息保存在服务器端存储中,然后使用令牌值作为键获得;作为服务器端存储,相关信息是隐式可信的
在 JWT Big Bang 之前,我们已经在身份验证系统中处理了令牌;应用程序通常会在用户登录时创建会话标识符,然后使用该标识符,这样用户就不必每次都重复登录过程。这些会话标识符被用作服务器端存储的关键索引,如果这听起来与您最近阅读的内容相似,那么您是对的,这确实被归类为按引用令牌。
使用相同的类比,理解引用标记的撤销是微不足道的;我们只是删除映射到该密钥的服务器端存储,下次提供该密钥时它将无效。
对于按值标记,我们只需执行相反的操作。当您请求撤销令牌时,您存储的内容允许您唯一标识该令牌,以便下次收到它时,您可以另外检查它是否已被撤销。如果您已经在考虑这样的事情不会扩展,请记住,您只需要存储数据直到令牌过期,并且在大多数情况下,您可能只存储令牌的哈希值,因此它总是是已知大小的东西。
作为最后一点,并以 OAuth 2.0 为中心,按值访问令牌的撤销目前尚未标准化。尽管如此,OAuth 2.0 令牌撤销明确指出,只要授权服务器和资源服务器都同意自定义处理方式,它仍然可以实现:
在前一种情况下(自包含令牌),当需要立即撤销访问令牌时,可以使用授权服务器和资源服务器之间的一些(当前非标准化)后端交互。
如果您同时控制授权服务器和资源服务器,这很容易实现。另一方面,如果您将授权服务器角色委派给像 Auth0 这样的云提供商或像 Spring OAuth 2.0 这样的第三方组件,您很可能需要以不同的方式处理事情,因为您可能只会获得已经标准化的内容。
一个有趣的参考
这篇文章解释了另一种方法:Blacklist JWT 它包含一些有趣的实践和模式,后面跟着RFC7523
【讨论】:
不是为每个未过期的令牌存储一个哈希(虽然可以管理,但可能有很多哈希),您是否可以只为每个撤销的令牌存储一个哈希,并检查它是否存在而不是存在? 文本不是很清楚,但是是的,我说的是只为撤销的按值令牌存储一些东西,并且只在它们没有达到正常到期之前存储一些东西。每个令牌都会像往常一样进行验证,然后会进行额外检查以查看它是否未被撤销。 +2/2 (= 100) 用于引入术语“按值”和“按引用”:) “按值”令牌确实与会话概念相反,您在会话“删除”时存储对(jti, exp)
并分发所有这些对,而服务器可以继续在 @ 上删除它们987654325@ 独立于任何集中式存储。扩展性很好,但在这些撤销jti
ids 方面仍然有状态的“反会话”。【参考方案4】:
如何存储 JWT 令牌并将其引用给数据库中的用户?通过在执行 JWT 比较后使用额外的数据库连接扩展后端应用程序中的警卫/安全系统,您实际上可以通过从数据库中删除或软删除它来“撤销”它。
【讨论】:
您必须像对待密码一样对待令牌,因此与其存储令牌本身,不如存储它的哈希值,例如 bcrypt、scrypt 等。但是,您将引入一个额外的需要集中化的令牌验证步骤,否定了 JWT 的优势之一,那么你真的想要 JWT 吗?实际上,有人链接到以下文章更详细地表达了相同的观点:dinochiesa.net/?p=1388【参考方案5】:无法撤销 JWT。
但是这里是一种替代解决方案,称为 JWT old for new exchange schema。
因为我们不能在过期时间之前使颁发的令牌失效,所以我们总是使用短时间令牌,例如 30 分钟。 当令牌过期时,我们用旧令牌交换一个新令牌。关键是一个旧代币只能兑换一个新代币。
在中心认证服务器中,我们维护一个这样的表:
table auth_tokens(
user_id,
jwt_hash,
expire
)
user_id 包含在 JWT 字符串中。 jwt_hash 是整个 JWT 字符串的哈希值,如 SHA256。 expire 字段是可选的。
以下是工作流程:
-
用户使用用户名和密码请求登录API,认证服务器颁发一个令牌,并注册令牌(在表格中添加一行。)
当令牌过期时,用户使用旧令牌请求交换 API。首先身份验证服务器正常验证旧令牌,除了过期检查,然后创建令牌哈希值,然后通过用户 id 查找上表:
如果找到的记录与 user_id 和 jwt_hash 匹配,则发出新令牌并更新表。
如果找到记录,但user_id 和jwt_hash 不匹配,则表示之前有人使用过该token 交换了新token。令牌被黑客入侵,按 user_id 删除记录并以警报信息响应。
如果没有找到记录,用户需要重新登录或只输入密码。
当用户更改密码或登出时,按用户ID删除记录。
要继续使用token,合法用户和黑客都需要不断交换新的token,但只有一个可以成功,一个失败则需要在下次交换时间重新登录。
所以如果黑客拿到了token,可以短时间使用,但是如果是合法用户下次换新的,就不能换新了,因为token有效期很短。这种方式更安全。
如果没有黑客,普通用户也需要定期交换新令牌,比如每30分钟一次,这就像自动登录一样。额外的负载并不高,我们可以为我们的应用程序调整过期时间。
来源:http://www.jianshu.com/p/b11accc40ba7
【讨论】:
这个解决方案的主要缺点是同时只能有一个用户登录。 如果用户停止使用系统,黑客换取新令牌怎么办?听起来黑客可以继续使用和交换令牌,直到用户再次登录。糟糕。 这是一个不应该被赞成的答案。如前所述,提议的答案将黑名单与刷新混合在一起,这意味着令牌现在既是“有状态的”又是“单例的”(仅限单次登录),这两者都完全违反了 JWT 原则。然而,提议的解决方案仍然存在依赖到期(留下一个主要弱点)的问题,同时引入了一个新的解决方案(单次登录)。因此,这比拥有简单的 auth jwt + 刷新令牌和黑名单机制还要糟糕。老实说:实现起来也不是最简单的(可能存在错误)。【参考方案6】:我找到了一种解决问题的方法,How to expire already generated existing JWT token using Java?
在这种情况下,我们需要使用任何数据库或内存中,
第 1 步: 首次为用户生成令牌后,将其与令牌一起存储在数据库中,它是“issuedAt()”时间。
我以这种 JSON 格式将其存储在数据库中,
例如: "username" : "username",
"token" : "token",
"issuedAt" : "issuedAt"
第 2 步:一旦您收到针对同一用户的 Web 服务请求并带有要验证的令牌,请从令牌中获取“issuedAt()”时间戳并进行比较已存储(数据库/内存中)发布的时间戳。
第 3 步: 如果存储的发布时间戳是新的(使用 after()/before() 方法),则返回令牌无效(在这种情况下,我们实际上并没有使令牌过期,但我们停止授予对该令牌的访问权限)。
这就是我解决问题的方法。
【讨论】:
这就像在数据库中存储密码。不要那样做。您可以存储哈希而不是 la bcrypt、scrypt 等,但关键是要像对待密码一样对待令牌。 如果您不考虑存储用户名/令牌,这不是一种有效的方法吗?假设您只是在用户记录上存储了一个issuedAt 时间戳,您可以使用它来使在最近一个令牌之前发布的任何先前令牌在针对数据库进行检查时无效。唯一的缺点是只有最近的令牌有效,但您可以在定义的时间而不是每次生成令牌时在数据库中设置新的 issedAt,并且在该时间之后生成的任何令牌都将有效。跨度> 问题是关于一个自包含的令牌,而不是一个有状态的令牌。【参考方案7】:撤销 JWT 的一种方法是利用分布式事件系统,当刷新令牌被撤销时通知服务。当刷新令牌被撤销并且其他后端/服务侦听该事件时,身份提供者会广播一个事件。当接收到事件时,后端/服务会更新本地缓存,该缓存维护一组刷新令牌已被撤销的用户。
每当验证 JWT 以确定是否应撤销 JWT 时,都会检查此缓存。这一切都基于 JWT 的持续时间和各个 JWT 的到期时间。
这篇文章Revoking JWTs 说明了这个概念,并在 Github 上有一个示例应用程序。
【讨论】:
“撤销 JWT”链接现在返回 404。 @DenverCoder9 可能令牌已成功撤销【参考方案8】:这并不能完全回答您有关 Spring 框架的问题,但这里有一篇文章讨论了为什么如果您需要撤销 JWT 的能力,您可能不想一开始就使用 JWT,而是使用常规的、不透明的 Bearer 令牌。
https://www.dinochiesa.net/?p=1388
【讨论】:
为什么不使用缓存或 rdbm 与 cron 定期删除每个条目或类似的东西而不是等待?以上是关于如何撤销 JWT 令牌?的主要内容,如果未能解决你的问题,请参考以下文章
每次使用 django-graphql-jwt 生成新令牌时,如何撤销 JWT?
如何在 express.js/passport-http-bearer 中撤销 express-jwt 令牌