每个用户的 JWT 和一个(!)会话/无并发会话

JWT and one(!) session per user / no concurrent sessions

我们当前的应用程序使用 HTTP 会话,我们希望将其替换为 JWT。

设置只允许每个用户一个会话。这意味着:

  1. 用户在设备 1 上登录
    • 用户在设备 1 上登录(创建新会话)
  2. 用户在设备 2 上登录
    • 用户在设备 2 上登录(创建了新会话)
    • 用户在设备 1 上登录(会话已被破坏)

之所以可行,是因为会话 ID 和用户 ID 之间存在服务器端关系。


使用 JWT 我可以想象在用户数据库中有一些计数器,每次登录都会增加,即:

  1. 用户在设备 1 上登录
    • JWT 令牌签名包含计数器+1(并将新计数器保存到数据库)
  2. 用户在设备 2 上登录
    • JWT 的签名包含 counter+1,它被增加并保存到 db。

现在,对于每个请求,我都必须检查传入的签名对于当前计数器值是否正确。

这不知何故使它有状态:(

但是... JWT 的好处之一是,无需访问任何数据库或会话存储来验证令牌。


是否有其他解决方案来防止并发登录?也许不需要数据库访问就可以工作并保持无状态的东西?

您已经非常接近解决方案了。

为此,您需要具备以下条件:
1.在token中包含iat(token发行时间)
2. 在某处存储用户上次登录的时间,例如在用户的配置文件中。

现在验证令牌时,进行额外检查:iat(颁发时间)必须等于或晚于上次登录时间。这隐含地使旧令牌无效。

这是一个不同的解决方案,在某些情况下可能更合适。

你仍然需要去数据库,但假设你的大多数用户只有一台设备,只有一些用户有第二台设备,你可以使用以下策略:

在登录(新令牌请求)期间让客户端提供设备 ID。将此与用户的 "last_device" 值进行比较。如果不同,则表示用户已更改为新设备。

发生这种情况时,请在特殊 table.
中为该用户添加一个 epoc 条目 userid:唯一参考用户(id)不为空
epoc: 时间戳

想法是这个 table 可能比完整用户 table 小得多。只有最近登录了一台以上设备的用户才会在此 table 中有一个条目。所以扫描这个 table 是有效率的。以正常方式验证令牌后,检查 iat(颁发时间)不在用户会话 epoc 之前。如果是,则该设备不是最近登录的设备。

此解决方案还有其他用途:它允许用户自己远程注销(您使用当前时间为用户创建一个新条目,这会有效地使他们的所有现有令牌无效)。

通过定期删除早于任何令牌最长生命周期的项目来维护此 table。

首先,在会话中使用 JWT 时,重要的是要定义:

  • 无状态令牌:令牌内包含所有会话数据。这个 这样你就不需要将它存储在任何地方。
  • Steteful 令牌:包含会话 ID。当服务器收到 作为令牌,他将需要检索有关会话的信息。

如果您使用有状态解决方案来使令牌失效,您可以在数据库中查询该用户的最后一个会话 ID,并与收到的令牌进行比较。
在无状态解决方案中,正如其他答案所指出的那样,您可能需要在某处保留状态。但是与有状态令牌相比,您只需要像您建议的那样存储一个计数器或像@The Tahaan 建议的那样 "last_login"。

如果您觉得使用 DB 太繁重,我建议使用像 Redis 这样的内存解决方案,它除了速度非常快之外,还可以轻松设置持久化数据的持续时间。这样,如果出现以下情况,用户将不得不重新登录:

  • 用户在另一台设备上登录。
  • 一段时间过去了。

我认为如何才能做到。 只需创建一个随机 ID(让我们称之为验证代码)并在生成 jwt 时将其存储在数据库中。 在 JWT 中对此进行编码。 每当使用 jwt 发出任何请求时,检查 jwt 中编码的验证码是否与数据库中的匹配。 如果用户尝试登录到其他设备,它将重新生成验证码,使所有其他会话失效。

关闭用户在任何其他设备上的会话如何。

怎么样。每次用户登录时,您按设备类型保存最后一次登录,并向所有连接的相同类型的设备(假设是一个)发送推送通知?

在这种情况下,在浏览器上,您可以将推送通知发送到浏览器,只需检查如果此时关闭该浏览器会发生什么情况?

如果是移动应用程序,您可以向移动应用程序发送推送通知并指示关闭