我正在使用 Spring Security OAuth2 和 JWT 令牌。我的问题是:如何撤销 JWT 令牌?
正如这里提到的 http://projects.spring.io/spring-security-oauth/docs/oauth2.html,撤销是通过刷新令牌完成的。但这似乎不起作用。
一般来说,最简单的答案是说您不能撤销 JWT 令牌,但这根本不是真的。诚实的答案是,支持 JWT 撤销的成本足够大,以至于在大多数情况下都不值得,或者明确地重新考虑 JWT 的替代方案。
话虽如此,在某些情况下,您可能需要 JWT 和立即撤销令牌,所以让我们来看看它需要什么,但首先我们将介绍一些概念。
JWT (Learn JSON Web Tokens) 只是指定了一种令牌格式,这个吊销问题也适用于通常称为自包含或按值令牌的任何格式。我喜欢后一种术语,因为它与引用标记形成了很好的对比。
按值令牌 - 相关信息,包括令牌生命周期,包含在令牌本身中,并且可以验证信息是否源自可信来源(救援的数字签名) 引用令牌 - 相关信息保存在服务器端然后使用令牌值作为键获得的存储;作为服务器端存储,相关信息是隐式信任的
在 JWT Big Bang 之前,我们已经在身份验证系统中处理了令牌;应用程序通常会在用户登录时创建一个会话标识符,然后使用该标识符,这样用户就不必每次都重复登录过程。这些会话标识符被用作服务器端存储的关键索引,如果这听起来与您最近阅读的内容相似,那么您是对的,这确实归类为按引用标记。
使用相同的类比,理解引用令牌的撤销是微不足道的;我们只是删除映射到该密钥的服务器端存储,下次提供该密钥时它将无效。
对于按价值代币,我们只需要执行相反的操作。当您请求撤销令牌时,您存储的内容允许您唯一标识该令牌,以便下次收到它时,您可以另外检查它是否已被撤销。如果您已经在考虑这样的事情不会扩展,请记住,您只需要存储数据直到令牌过期,并且在大多数情况下,您可能只存储令牌的哈希值,因此它总是是已知大小的东西。
最后一点,并将其集中在 OAuth 2.0 上,按值访问令牌的撤销目前尚未标准化。尽管如此,OAuth 2.0 令牌撤销明确指出,只要授权服务器和资源服务器都同意自定义处理方式,它仍然可以实现:
在前一种情况下(自包含令牌),当需要立即撤销访问令牌时,可以使用授权服务器和资源服务器之间的一些(当前非标准化)后端交互。
如果您同时控制授权服务器和资源服务器,这很容易实现。另一方面,如果您将授权服务器角色委托给像 Auth0 这样的云提供商或像 Spring OAuth 2.0 这样的第三方组件,您很可能需要以不同的方式处理事情,因为您可能只会获得已经标准化的内容。
一个有趣的参考
本文解释了另一种方法:Blacklist JWT 它包含一些有趣的实践和模式,然后是 RFC7523
JWT 不能被撤销。
但这里有一个替代解决方案,称为 JWT old for new exchange schema。
因为我们不能在过期时间之前使颁发的令牌失效,所以我们总是使用短时间令牌,例如 30 分钟。当令牌过期时,我们用旧令牌交换一个新令牌。关键是一个旧代币只能换一个新代币。
在中心认证服务器中,我们维护一个这样的表:
table auth_tokens(
user_id,
jwt_hash,
expire
)
JWT 字符串中包含的 user_id。 jwt_hash 是整个 JWT 字符串的哈希值,如 SHA256。过期字段是可选的。
以下是工作流程:
用户使用用户名和密码请求登录API,认证服务器颁发一个令牌,并注册令牌(在表中添加一行。)当令牌过期时,用户使用旧令牌请求交换API。首先,auth 服务器正常验证旧令牌,除了过期检查,然后创建令牌哈希值,然后通过用户 id 查找上表:如果找到的记录与 user_id 和 jwt_hash 匹配,则发出新令牌并更新表。如果找到记录,但 user_id 和 jwt_hash 不匹配,则表示之前有人使用过该令牌交换了新令牌。令牌被黑客入侵,通过 user_id 删除记录并响应警报信息。如果没有找到记录,用户需要重新登录或只输入密码。当使用更改密码或登出时,按用户ID删除记录。
要持续使用token,合法用户和黑客都需要不断交换新的token,但只有一个可以成功,当一个失败时,都需要在下次交换时间重新登录。
所以如果黑客拿到了token,可以使用很短的时间,但是如果是合法用户下次换新的,就不能换新的了,因为token的有效期很短。这种方式更安全。
如果没有黑客,普通用户也需要定期交换新令牌,比如每 30 分钟一次,这就像自动登录一样。额外的负载并不高,我们可以为我们的应用程序调整过期时间。
来源:http://www.jianshu.com/p/b11accc40ba7
这并不能完全回答您关于 Spring 框架的问题,但这里有一篇文章讨论了为什么如果您需要撤销 JWT 的能力,您可能不想首先使用 JWT,而是使用常规,不透明的承载令牌。
https://www.dinochiesa.net/?p=1388
撤销 JWT 的一种方法是利用分布式事件系统,当刷新令牌被撤销时通知服务。当刷新令牌被撤销并且其他后端/服务侦听该事件时,身份提供者会广播一个事件。当接收到事件时,后端/服务会更新本地缓存,该缓存维护一组刷新令牌已被撤销的用户。
每当验证 JWT 以确定是否应撤销 JWT 时,都会检查此缓存。这一切都基于 JWT 的持续时间和各个 JWT 的到期时间。
这篇文章 Revoking JWTs 说明了这个概念,并在 Github 上提供了一个示例应用程序。
对于 Google 员工:
如果您实施纯无状态身份验证,则无法撤销令牌,因为令牌本身是唯一的事实来源
如果您在服务器上保存已撤销的令牌 ID 列表并根据列表检查每个请求,那么它本质上是有状态身份验证的变体
像 Cognito 这样的 OAuth2 提供者提供了一种“退出”用户的方法,但是,它只真正撤销了刷新令牌,该令牌通常是长期存在的,并且可以多次使用以生成新的访问令牌,因此必须被撤销;现有的访问令牌在过期之前仍然有效
存储 JWT 令牌并将其引用给数据库中的用户怎么样?通过在执行 JWT 比较后使用额外的数据库连接扩展后端应用程序中的警卫/安全系统,您实际上可以通过从数据库中删除或软删除它来“撤销”它。
一般来说,关于引用代币与价值代币的答案已经确定了。对于那些将来偶然发现这个空间的人。
如何在 RS 端实现吊销:TL;DR:获取对所有正在验证令牌的后端服务实例可见的缓存或数据库。当一个新的令牌到达撤销时,如果它是一个有效的,(即根据你的 jwt 验证算法进行验证),获取 exp 和 jti 声明,并将 jti 保存到缓存,直到达到 exp。然后一旦 unixNow 变为 > exp,缓存中的 jti 就会过期。
然后在其他端点上授权时,您每次检查给定的 jti 是否与此缓存中的某些内容匹配,如果是,则错误 403 表示令牌已撤销。一旦过期,您的验证算法中就会出现常规的 Token Expired 错误。
PS 通过仅将 jti 保存在缓存中,您可以使这些数据对任何人都无用,因为它只是一个唯一的令牌标识符。
JWT 撤销的最佳解决方案是缩短 exp 窗口、刷新并将已发布的 JWT 令牌保存在共享的近线缓存中。以 Redis 为例,这特别容易,因为您可以将缓存键设置为令牌本身(或令牌的哈希),并指定过期时间以便自动驱逐令牌。
我找到了一种解决问题的方法,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() 方法),则返回令牌无效(在这种情况下,我们实际上并没有使令牌过期,但我们停止授予对该令牌的访问权限)。
这就是我解决问题的方法。
不定期副业成功案例分享
(jti, exp)
并分发所有这些对,而服务器可以独立于任何集中式存储在exp
上继续删除它们。可以很好地扩展,但在这些撤销jti
id 方面仍然有状态的“反会话”。