79

我想知道在更改密码/注销时使 JWT 无效而不击中 db 的最佳实践。

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

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

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

但这两种情况的代价是每次用户点击 api 时都会点击用户 db。任何最佳实践都值得赞赏。

更新: 我认为我们不能在不击中 db 的情况下使 JWT 无效。所以我想出了一个解决方案。我已经发布了我的答案,如果您有任何疑问,欢迎您。

4

5 回答 5

83

不使用刷新令牌时:

1.修改密码时:用户修改密码时,注意用户db中修改密码时间,当修改密码时间大于token创建时间时,token无效。因此,剩余的会话将很快被注销。

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

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

  1. 检查令牌是否有效。
  2. 如果有效,请检查它是否存在于 invalidTokenDB(已注销的令牌存储到其到期时间的数据库)中。
  3. 如果它不存在,则检查用户 db 中的令牌创建时间和更改密码时间。
  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 中提出了一种保护刷新令牌的方法,但它是安全的吗?,我无法理解他在说什么。任何帮助表示赞赏。

所以如果有人有好的建议,欢迎您的评论。

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

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

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

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

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

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

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

于 2015-03-02T06:46:46.890 回答
17

我不知道在不以某种方式涉及数据库的情况下任意使令牌无效。

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

  • 用户使用 iPad 登录,颁发并存储令牌 1。
  • 用户在网站上登录。代币 2 已发行。用户注销。
  • 用户尝试使用 iPad,令牌 1 是在用户从网站注销之前发出的,令牌 1 现在被视为无效。

您可能想看看刷新令牌的想法,尽管这些也需要数据库存储。

另请参阅此处以获取有关类似问题的良好 SO 讨论,特别是 IanB 的解决方案,该解决方案将节省一些数据库调用。

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

每当用户“活跃”时,每次都发出一个新的身份验证令牌(每次有效期为 15 分钟)。如果用户超过 15 分钟未处于活动状态,然后发出请求(因此使用过期的 jwt),请检查刷新令牌的有效性。如果它有效(包括数据库检查),则发出一个新的身份验证令牌。

如果用户在设备上或通过网站“注销”,则销毁客户端的访问刷新令牌,重要的是撤销使用的刷新令牌的有效性。如果用户在任何设备上更改密码,则撤销所有刷新令牌,强制他们在访问令牌过期后立即重新登录。这确实留下了“不确定性之窗”,但如果不每次都击中数据库,这是不可避免的。

使用这种方法还可以让用户在需要时“撤销”对特定设备的访问权限,正如许多主要网络应用程序所看到的那样。

于 2015-02-27T08:35:34.487 回答
9

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

我看到必须点击 db 来验证或使每个 api 请求的令牌无效,但是正如我在这里看到的那样,整个过程可能会更简单。

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

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

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

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

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

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

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

注意:如果您投反对票,请说明理由。

于 2016-12-06T06:34:12.817 回答
0

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

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

于 2016-08-30T15:04:43.387 回答
-3

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

于 2016-01-11T17:32:55.273 回答