在 node.js 中更改密码和注销时使 JWT 无效的最佳实践?

Best practices to invalidate JWT while changing passwords and logout in node.js?

我想知道在更改 password/logout 时不影响数据库的情况下使 JWT 无效的最佳实践。

我有下面的想法,通过访问用户数据库来处理以上两种情况。

1.Incase 密码更改,我检查存储在用户数据库中的密码(散列)。

2.Incase 注销,我在用户数据库中保存了上次注销时间,因此通过比较令牌创建时间和注销时间,我可以使这种情况无效。

但这两种情况的代价是每次用户点击 api 时都会点击用户数据库。感谢任何最佳实践。

更新: 我不认为我们可以在不访问 db 的情况下使 JWT 失效。所以我想出了一个解决方案。我已经把我的答案贴出来了,如果有什么问题欢迎留言

据我所知,没有任何方法可以在不涉及数据库的情况下任意使令牌无效。

如果您的服务可以在多个设备上访问,请谨慎使用方法 2。考虑以下场景...

  • User signs in with iPad, Token 1 issued and stored.
  • User signs in on website. Token 2 issued. User logs out.
  • User tries to use iPad, Token 1 was issued before user logged out from website, Token 1 now considered invalid.

您可能想看看 refresh tokens 的想法,尽管这些也需要数据库存储。

另见 here 关于类似问题的良好 SO 讨论,特别是 IanB 的解决方案,它可以节省一些数据库调用。

建议的解决方案 就个人而言,这就是我处理它的方式......用户身份验证,使用短期到期(比如 15 分钟)的访问令牌和一个有效期更长或无限期的刷新令牌颁发。将此刷新令牌的记录存储在数据库中。

每当用户 'active' 时,每次都会发出一个新的授权令牌(每次有效期为 15 分钟)。如果用户超过 15 分钟没有活动,然后发出请求(因此使用过期的 jwt),请检查刷新令牌的有效性。如果有效(包括数据库检查),则发出新的身份验证令牌。

如果用户 'logs out' 在设备上或通过网站销毁客户端的访问刷新令牌,重要的是撤销所使用的刷新令牌的有效性。如果用户在任何设备上更改密码,则撤销所有刷新令牌,迫使他们在访问令牌过期后立即重新登录。这确实会留下一个 'window of uncertainty',但如果每次都不访问数据库,这是不可避免的。

使用这种方法还可以让用户在需要时 'revoke' 访问特定设备,正如许多主要网络应用程序所见。

不使用刷新令牌时:

1.While修改密码:当用户修改密码时,在用户db中记下修改密码时间,所以当修改密码时间大于令牌创建时间,则令牌无效。因此,剩余的会话将很快注销。

2.When 用户注销: 当用户注销时,将令牌保存在单独的数据库中(例如:InvalidTokenDB 并在令牌过期时从数据库中删除令牌).因此,用户从相应的设备注销,他在其他设备上的会话不受干扰。

因此,在使 JWT 失效时,我遵循以下步骤:

  1. 检查令牌是否有效。
  2. 如果有效,请检查它是否存在于 invalidTokenDB(一个数据库,注销的令牌在过期前都存储在该数据库中)。
  3. 如果不存在,则在用户数据库中检查令牌创建时间和更改密码时间。
  4. 如果更改密码时间<令牌创建时间,则令牌有效。

关注以上方法:

  1. 对于每个 api 请求,我都需要执行上述所有步骤,这可能会影响性能。

使用刷新令牌时:访问令牌有效期为1天,刷新令牌为终身有效期

1.更改密码时: 当用户更改密码时,更改用户的刷新令牌。因此,剩余的会话将很快注销。

2。当用户注销时:当用户注销时,将令牌保存在单独的数据库中(例如:InvalidTokenDB 并在令牌过期时从数据库中删除令牌)。因此,用户从相应的设备注销,他在其他设备上的会话不受干扰。

因此,在使 JWT 失效时,我遵循以下步骤:

  1. 检查令牌是否有效
  2. 如果有效,请检查令牌是否存在于 InvalidTokenDB 中。
  3. 如果不存在,请使用 userDB 中的刷新令牌检查刷新令牌。
  4. 如果等于,那么它是一个有效的令牌

关注以上方法:

  1. 对于每个 api 请求,我都需要执行上述所有步骤,这可能会影响性能。
  2. 如何使刷新令牌失效,因为刷新令牌没有有效性,如果它被黑客使用,仍然是有效的身份验证,请求将始终成功。

注意:尽管 Hanz 在 Using Refesh Token in Token-based Authentication is secured? 中提出了一种保护刷新令牌的方法,但我无法理解他在说什么。感谢任何帮助。

所以如果大家有好的建议,欢迎大家提出意见。

更新: 我正在添加答案,以防您的应用程序不需要生命周期到期的刷新令牌。这个答案是由 Sudhanshu (https://whosebug.com/users/4062630/sudhanshu-gaur) 给出的。谢谢苏丹舒。所以我相信这是最好的方法,

当不需要刷新令牌且访问令牌没有过期时:

当用户登录时,在他的用户数据库中创建一个没有过期时间的登录令牌。

因此,在使 JWT 失效时,请按照以下步骤操作,

  1. 检索用户信息并检查令牌是否在他的用户数据库中。如果允许的话。
  2. 当用户注销时,仅从他的用户数据库中删除此令牌。
  3. 当用户更改密码时,从他的用户数据库中删除所有令牌并要求他重新登录。

因此,使用这种方法,您既不需要在注销令牌过期之前将其存储在数据库中,也不需要在上述情况下更改密码时存储令牌创建时间。但是,我相信这种方法只有在您的应用有不需要刷新令牌且令牌没有过期的要求时才有效。

如果有人对这种方法有疑虑,请告诉我。欢迎您发表意见:)

我完全同意@gopinath 的回答只是想添加一件事,当你所有的令牌都过期时,你还应该删除更改密码的时间,例如假设你已经为每个令牌设置了 3 天的到期时间,现在改为过期只是通常在数据库中保存更改密码时间,您还可以将其过期时间设置为 3 天,因为显然在此之前的令牌将过期,因此无需再次检查每个令牌的过期时间是否大于更改密码时间

如果用户正在更改他们的密码,您将在那里访问数据库。但是不想访问数据库以获得授权?

我发现了存储每个用户字符串的好处,并且散列在一起的全局共享字符串为我们的 JWT 实现提供了最大的灵活性。在这种特殊情况下,我将存储密码的散列值以与全局字符串一起使用,并将它们散列在一起以获得 JWT 秘密。

我不确定我是否遗漏了什么,但我发现接受的答案比必要的更复杂。

我看到必须命中 db 才能为每个 api 请求验证或使令牌无效,但是整个过程本可以像我在这里看到的那样更简单。

每当创建 jwt 时,即在登录或 change/reset 密码期间,将带有用户 ID 的 jwt 插入 table 并为每个 jwt 维护一个 jti(基本上是一个 uuid 编号)。相同的 jti 也进入 jwt 有效负载。 jti 有效地唯一标识了一个 jwt。当从多个设备或浏览器访问帐户时,用户可以同时拥有多个 jwts,在这种情况下,jti 区分设备或用户代理。

所以 table 模式是,jti |用户身份。 (当然还有一个主键)

对于每个 api,检查 jti 是否在 table 中,这意味着 jwt 是有效的。

当用户更改或重置密码时,从数据库中删除该用户ID 的所有jti。创建一个带有新 jti 的新 jwt 并将其插入 table。这将使来自所有其他设备和浏览器的所有会话无效,但更改或重置密码的除外。

当用户注销时,删除该用户的特定 jti,但不是全部。将有一次登录,但没有一次注销。因此,当用户注销时,他不应该从所有设备上注销。但是,删除所有 jtis 也会从所有设备注销。

所以这将是一个 table 并且没有日期比较。如果使用或不使用刷新令牌,情况也是一样的。

然而,为了最大限度地减少数据库干扰和可能的延迟,使用缓存肯定有助于缓解处理时间方面的问题。

注意:如果您投反对票,请说明原因。