使JSON Web令牌无效

时间:2014-02-24 04:02:29

标签: javascript node.js session session-cookies jwt

对于我正在研究的新node.js项目,我正在考虑从基于cookie的会话方法切换(我的意思是,将id存储到包含键值的商店用户会话在用户的浏览器中)使用JSON Web令牌(jwt)进行基于令牌的会话方法(无键值存储)。

该项目是一个利用socket.io的游戏 - 在一个会话中将有多个通信通道(web和socket.io)的情况下,基于令牌的会话将非常有用

如何使用jwt方法从服务器提供令牌/会话失效?

我还想了解在这种范例中我应该注意哪些常见(或不常见)的陷阱/攻击。例如,如果此范例容易受到与基于会话存储/ cookie的方法相同/不同类型的攻击。<​​/ p>

所以,说我有以下内容(改编自thisthis):

会话商店登录:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

基于令牌的登录:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

-

会话存储方法的注销(或无效)需要更新KeyValueStore 具有指定标记的数据库。

似乎这种机制在基于令牌的方法中不存在,因为令牌本身将包含通常存在于键值存储中的信息。

32 个答案:

答案 0 :(得分:278)

我也一直在研究这个问题,虽然下面的想法都不是完整的解决方案,但它们可能会帮助其他人排除想法或提供更多想法。

1)只需从客户端

中删除令牌即可

显然,这对服务器端安全没有任何作用,但它确实通过删除令牌来阻止攻击者(即,他们必须在注销之前窃取令牌)。

2)创建令牌黑名单

您可以将无效令牌存储到其初始到期日期,并将它们与传入请求进行比较。这似乎否定了首先完全基于令牌的原因,因为您需要为每个请求触摸数据库。但是,存储大小可能会更低,因为您只需要存储在注销和退出之间的令牌。到期时间(这是一种直觉,绝对取决于背景)。

3)只需将令牌到期时间缩短并经常旋转

如果您以足够短的时间间隔保持令牌到期时间,并让正在运行的客户端跟踪并在必要时请求更新,则数字1将有效地用作完整的注销系统。这种方法的问题在于,它无法让用户在关闭客户端代码之间保持登录状态(取决于您到达时间间隔的时间长度)。

应急计划

如果发生紧急情况或用户令牌遭到入侵,您可以做的一件事是允许用户使用其登录凭据更改基础用户查找ID。这将使所有关联的令牌无效,因为将无法再找到关联的用户。

我还想注意,最后一个登录日期包含令牌是一个好主意,这样您就可以在一段遥远的时间后强制执行重新登录。

就使用令牌的攻击的相似性/差异而言,这篇文章解决了这个问题:http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/

答案 1 :(得分:70)

上面发布的想法很好,但是让所有现有JWT无效的简单方法就是改变秘密。

如果您的服务器创建JWT,请使用机密(JWS)对其进行签名,然后将其发送到客户端,只需更改密钥即可使所有现有令牌无效,并要求所有用户获取新令牌以作为其旧令牌进行身份验证根据服务器变得无效。

它不需要对实际令牌内容(或查找ID)进行任何修改。

显然,这仅适用于您希望所有现有令牌到期的紧急情况,因为每个令牌到期需要上述解决方案之一(例如短令牌到期时间或令牌中存储的密钥无效)。

答案 2 :(得分:54)

这主要是一篇长篇评论,支持并建立在@mattway

的答案之上

鉴于:

此页面上的其他一些建议的解决方案主张在每个请求上访问数据存储区。如果您点击主数据存储区以验证每个身份验证请求,那么我认为使用JWT而不是其他已建立的令牌身份验证机制的理由更少。如果你每次都去数据存储区,你基本上使JWT成为有状态,而不是无状态。

(如果您的网站收到大量未经授权的请求,那么JWT会拒绝它们而不会访问数据存储区,这很有用。可能还有其他用例。)

假设:

对于典型的真实世界网络应用程序,无法实现真正​​的无状态JWT身份验证,因为无状态JWT无法为以下重要提供直接安全支持用例:

用户的帐户被删除/阻止/暂停。

用户密码已更改。

用户的角色或权限已更改。

用户已由admin注销。

站点管理员更改了JWT令牌中的任何其他应用程序关键数据。

在这些情况下,您无法等待令牌过期。令牌失效必须立即发生。此外,您不能相信客户不要保留和使用旧令牌的副本,无论是否有恶意。

因此:  我认为@ matt-way,#2 TokenBlackList的答案是将所需状态添加到基于JWT的身份验证的最有效方式。

您有一个黑名单,其中包含这些令牌,直到其到期日期为止。与用户总数相比,令牌列表将非常小,因为它只需要保留列入黑名单的令牌,直到它们到期为止。我通过在redis,memcached或其他支持在密钥上设置过期时间的内存数据存储中放置无效标记来实现。

您仍然需要为每个通过初始JWT身份验证的身份验证请求调用内存数据库,但您不必为其中的整个用户组存储密钥。 (对于给定的网站,这可能是也可能不是什么大问题。)

答案 3 :(得分:36)

我会在用户模型上记录jwt版本号。新的jwt令牌会将其版本设置为此。

验证jwt时,只需检查它的版本号是否等于用户当前的jwt版本。

任何时候你想让旧的jwts无效,只需碰撞用户的jwt版本号。

答案 4 :(得分:25)

尚未尝试过,并且根据其他一些答案使用了大量信息。这里的复杂性是避免每次请求用户信息的服务器端数据存储调用。大多数其他解决方案需要对用户会话存储的每个请求进行数据库查找。这在某些情况下很好,但这是为了避免此类调用而创建的,并使所需的服务器端状态变得非常小。 您将最终重新创建服务器端会话,无论多小,都可以提供所有强制失效功能。但如果你想这样做,那就是要点:

<强>目标:

  • 缓解数据存储的使用(无状态)。
  • 强制退出所有用户的能力。
  • 可以随时强制退出任何个人。
  • 能够在一定时间后要求重新输入密码。
  • 能够与多个客户合作。
  • 当用户单击从特定客户端注销时强制重新登录的能力。 (为防止有人在用户离开后“取消删除”客户端令牌 - 请参阅注释以获取更多信息)

解决方案:

  • 使用短期(&lt; 5m)访问令牌与存储时间较长(几小时)的客户端refresh-token配对。
  • 每个请求都会检查auth或刷新令牌到期日的有效性。
  • 当访问令牌过期时,客户端使用刷新令牌刷新访问令牌。
  • 在刷新令牌检查期间,服务器会检查一个小的用户ID黑名单 - 如果找到则拒绝刷新请求。
  • 当客户端没有有效(未过期)刷新或身份验证令牌时,用户必须重新登录,因为所有其他请求都将被拒绝。
  • 在登录请求中,检查用户数据存储是否禁止。
  • 注销时 - 将该用户添加到会话黑名单中,以便他们必须重新登录。您必须存储其他信息,以便不在多设备环境中将其记录到所有设备之外,但可以通过添加设备来完成字段到用户黑名单。
  • 在x时间后强制重新进入 - 在身份验证令牌中维护上次登录日期,并根据请求进行检查。
  • 强制注销所有用户 - 重置令牌哈希密钥。

这要求您在服务器上维护黑名单(状态),假设用户表包含禁止的用户信息。无效会话黑名单 - 是用户ID列表。仅在刷新令牌请求期间检查此黑名单。只要刷新令牌TTL,就必须存在条目。刷新令牌到期后,用户将需要重新登录。

<强>缺点:

  • 仍需要对刷新令牌请求执行数据存储查找。
  • 无效的令牌可能会继续为访问令牌的TTL操作。

<强>优点:

  • 提供所需的功能。
  • 在正常操作下,用户将隐藏刷新令牌操作。
  • 仅需要对刷新请求而不是每个请求执行数据存储查找。即每15分钟1次,而不是每秒1次。
  • 将服务器端状态最小化为非常小的黑名单。

使用此解决方案,不需要像reddis那样的内存数据存储,至少不需要用户信息,因为服务器每隔15分钟左右只进行一次数据库调用。如果使用reddis,那么在那里存储有效/无效的会话列表将是一个非常快速和简单的解决方案。无需刷新令牌。每个身份验证令牌都有一个会话ID和设备ID,它们可以在创建时存储在reddis表中,并在适当时无效。然后,他们将在每个请求中进行检查,并在无效时被拒绝。

答案 5 :(得分:13)

我一直在考虑的方法是在JWT中始终拥有iat(已发布)值。然后,当用户注销时,将该时间戳存储在用户记录中。验证JWT时,只需将iat与上次注销的时间戳进行比较即可。如果iat较旧,则无效。是的,您必须转到数据库,但如果JWT无效,我总是会提取用户记录。

我看到的主要缺点是,如果他们在多个浏览器中,或者也有移动客户端,则会将其记录在所有会话中。

这也可能是一个很好的机制,可以使系统中的所有JWT无效。部分检查可能是针对上一个有效iat时间的全局时间戳。

答案 6 :(得分:8)

我在这里有点晚了,但我认为我有一个不错的解决方案。

我有一个&#34; last_password_change&#34;我的数据库中的列,用于存储上次更改密码的日期和时间。我还将发行日期/时间存储在JWT中。在验证令牌时,我会检查在发出令牌后密码是否已更改,以及令牌是否被拒绝,即使它尚未过期。

答案 7 :(得分:5)

您的数据库上可以在用户的​​文档/记录上添加“last_key_used”字段。

当用户使用user登录并传递时,生成一个新的随机字符串,将其存储在last_key_used字段中,并在签名时将其添加到有效负载中。

当用户使用令牌登录时,请检查数据库中的last_key_used以匹配令牌中的last_key_used。

然后,当用户进行注销时,或者如果要使令牌无效时,只需将“last_key_used”字段更改为另一个随机值,任何后续检查都将失败,从而迫使用户使用用户登录再次通过。

答案 8 :(得分:2)

Kafka邮件队列和本地黑名单

我考虑过使用像kafka这样的消息传递系统。让我解释一下:

例如,您可以拥有一个微服务(称为 userMgmtMs 服务),该微服务负责loginlogout并生成JWT令牌。然后,此令牌将传递给客户端。

现在,客户可以使用此令牌调用不同的微服务(将其称为 pricesMs ),在priceMs范围内,将没有数据库检查到初始令牌的users表创建被触发。该数据库仅存在于userMgmtM中。此外,JWT令牌应包含权限/角色,以便pricesM无需从数据库中查找任何内容即可使spring安全性正常工作。

JwtRequestFilter可以提供一个由JWT令牌中提供的数据创建的UserDetails对象(显然没有密码),而不是使用priceMs来访问数据库。

那么,如何注销令牌或使令牌无效?由于我们不想在每次请求priecesMs时都调用userMgmtMs数据库(这会引入很多不必要的依赖关系),因此,一种解决方案是使用此令牌黑名单。

我建议不要使用黑名单,而是要依赖所有微服务的一个表,而建议使用kafka消息队列。

userMgmtMs仍然负责logout,一旦完成,它就会将其放入自己的黑名单(微服务之间不共享的表)。此外,它将带有此令牌内容的kafka事件发送到内部kafka服务,所有其他微服务都已订阅到该内部kafka服务。

其他微服务一旦收到kafka事件,就会将其也放入内部黑名单中。

即使某些微服务在注销时已关闭,它们最终仍将再次启动,并在以后的状态下接收消息。

自从开发了kafka以来,客户就可以参考自己阅读过的消息,从而确保没有任何客户端(正常运行或正常运行)会丢失任何无效令牌。

我唯一能想到的问题是,kafka消息传递服务将再次引入单点故障。但这是相反的,因为如果我们有一个全局表,其中保存了所有无效的JWT令牌,并且此数据库或微服务已关闭,则无效。使用kafka方法+对于普通用户注销,客户端删除JWT令牌后,在大多数情况下,kafka的停机时间甚至不会引起注意。由于黑名单是作为内部副本分布在所有微服务之间的。

在关闭状态下,您需要使被黑客入侵的用户无效,并且kafka处于关闭状态,这是问题开始的地方。在这种情况下,将秘密作为最后的手段可以有所帮助。或者只是在做之前确保kafka已经启动。

免责声明:我尚未实现此解决方案,但是我不知为何,大多数提议的解决方案都通过中央数据库查找来否定JWT令牌的想法。所以我在考虑另一种解决方案。

请让我知道您的想法,这有意义还是有明显的原因不能做到?

答案 9 :(得分:2)

  1. 为代币提供1天的到期时间
  2. 维护每日黑名单。
  3. 将invalidated / logout令牌放入黑名单
  4. 对于令牌验证,首先检查令牌到期时间,如果令牌未过期,则检查黑名单。

    对于长会话需求,应该有一种延长令牌到期时间的机制。

答案 10 :(得分:2)

晚会结束后,经过一番研究后,我得到了两分钱。 在注销期间,请确保发生以下事情......

清除客户端存储/会话

分别在登录或注销时更新用户表的上次登录日期时间和注销日期时间。因此,登录日期时间总是应该大于logout(如果当前状态是登录但尚未注销,则保持注销日期为null)

这比保留额外的黑名单表和定期清除要简单得多。多设备支持需要额外的表来保持登录,注销日期以及一些其他详细信息,如操作系统或客户端详细信息。

答案 11 :(得分:2)

保留这样的内存列表

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

如果令牌在一周内到期,请清除或忽略早于该记录的记录。还仅保留每个用户的最新记录。 列表的大小取决于您保留令牌的时间以及用户吊销令牌的频率。 仅在表更改时才使用db。当您的应用程序启动时,将表加载到内存中。

答案 12 :(得分:1)

不使用刷新JWT ...

有2种攻击场景。一种是关于受损的登录凭据。另一个是JWT的实际盗窃。

对于受损的登录凭据,当发生新登录时,通常会向用户发送电子邮件通知。因此,如果客户不同意登录,则建议他们重置凭据,这应该将密码的上次设置时间保存到数据库/缓存中(在下次登录时也要设置此时间)用户在初始注册时设置密码)。每当授权用户操作时,应从数据库/缓存中获取用户更改密码的日期时间,并将其与生成给定JWT的日期时间进行比较,并禁止JWT操作在所述凭证的日期时间重置之前生成的凭证,因此实质上使此类JWT变得无用。这意味着将JWT的生成日期时间保存为JWT本身的声明。在ASP.NET Core中,可以使用策略/要求进行此比较,并且在失败时,将禁止客户端。因此,每当凭据重置完成时,就会在全局范围内注销后端的用户。

对于JWT的实际盗窃... JWT的盗窃并不容易发现,但过期的JWT可以轻松解决。但是,在JWT到期之前可以采取什么措施阻止攻击者呢?这是实际的全局注销。它类似于上面针对凭证重置所述的内容。为此,通常将用户发起全局注销的日期时间保存在数据库/缓存中,并在授权用户操作时将其获取,并将其与给定JWT的生成日期时间进行比较 >,并禁止对在上述全局注销日期时间之前生成的JWT执行操作,因此实质上使此类JWT变得无用。如前所述,可以使用ASP.NET Core中的策略/要求来完成此操作。

现在,您如何检测JWT的盗窃?我现在的答案是偶尔提醒用户全局注销并再次登录,因为肯定会将攻击者注销。

答案 13 :(得分:1)

使用JWT刷新...

我实际采用的一种方法是存储刷新令牌(可以是GUID)和对应的刷新令牌ID (不会更改(无论完成多少刷新),并在生成用户的JWT时为用户将它们添加为声明。可以使用数据库的替代方案,例如。内存缓存。但是我在这个答案中使用数据库。

然后,创建一个JWT刷新Web API端点,客户端可以在JWT到期之前调用它。调用刷新后,请从JWT中的声明中获取刷新令牌。

在对JWT刷新端点的任何调用上,请在数据库上将当前刷新令牌和刷新令牌ID验证为一对。生成一个新的刷新令牌,并使用该刷新令牌ID来替换数据库上的旧刷新令牌。请记住,它们是可以从JWT中提取的声明

从当前的JWT中提取用户的声明。开始生成新的JWT的过程。用新生成的刷新令牌替换旧的刷新令牌声明的值,该刷新令牌也已新保存在数据库中。有了这些,生成新的JWT并将其发送给客户端。

因此,在使用刷新令牌之后,无论是由目标用户还是攻击者使用,任何其他尝试使用数据库上未配对的a /刷新令牌及其刷新令牌ID的其他尝试都不会导致生成新的JWT,从而阻止具有该刷新令牌ID的任何客户端再能够使用后端,从而导致此类客户端(包括合法客户端)的完全注销。

这说明了基本信息。

接下来要添加的内容是有一个刷新JWT的时间窗口,这样该窗口之外的任何事物都会成为可疑活动。例如,窗口可以是JWT到期前的10分钟。生成JWT的日期时间可以保存为该JWT本身中的声明。并且当发生这种可疑活动时,即当其他人尝试在窗口内或窗口内使用刷新令牌ID后,该刷新令牌ID已在窗口中使用时,应将刷新令牌ID标记为无效。因此,即使刷新令牌ID的有效所有者也必须重新登录。

在数据库上找不到与提供的刷新令牌ID配对的刷新令牌意味着该刷新令牌ID应该无效。因为闲置的用户可能会尝试使用攻击者已经使用的刷新令牌。

如先前所述,在目标用户之前,被攻击者窃取并使用的JWT也将被标记为无效。

唯一无法解决的情况是,即使攻击者可能已经窃取了客户端,客户端也从未尝试刷新其JWT。但是,这种情况不太可能发生在没有受到攻击者监护(或类似)的客户端上,这意味着攻击者无法就客户端何时停止使用后端来预测该客户端。

如果客户端启动常规注销。应该注销以从数据库中删除刷新令牌ID和相关记录,从而防止任何客户端生成刷新JWT。

答案 14 :(得分:1)

以下方法可以提供两全其美的解决方案:

让“立即”表示“〜1分钟”。

情况:

  1. 用户尝试成功登录:

    A。在令牌中添加一个“发出时间”字段,并根据需要保留到期时间。

    B。存储用户密码的哈希值 或在用户表中创建一个新字段,例如 tokenhash 。 将 tokenhash 存储在生成的令牌中。

  2. 用户访问网址:

    A。如果“发布时间”在“立即”范围内,请正常处理令牌。不要更改“发出时间”。根据“立即”的持续时间,这是持续时间 一个人很容易受到伤害。但是一分钟之类的短暂时间不应该 太冒险了。 (这是性能和安全性之间的平衡)。三个不需要在这里打数据库。

    B。如果令牌不在“立即”范围内,请对照数据库检查 tokenhash 。如果还可以,请更新“发出时间”字段。如果不能,请不要处理该请求(安全性最终得到实施)。

  3. 用户更改令牌哈希以保护帐户。在“即时”的将来,该帐户将受到保护。

我们将数据库查找保存在“立即”范围内。 如果在“立即”持续时间内客户端发出大量请求,这将是最有益的。

答案 15 :(得分:1)

------------------------这个答案有点迟了,但可能对某人有帮助------- -----------------

从客户端,最简单的方法是从浏览器的存储中删除令牌。

但是,如果您想销毁节点服务器上的令牌,该怎么办-

JWT软件包的问题在于它没有提供任何方法或方法来销毁令牌。 关于JWT,您可以使用上述不同的方法。但是在这里,我选择了jwt-redis。

因此,为了销毁服务器端的令牌,您可以使用 jwt-redis软件包而不是JWT

此库(jwt-redis)完全重复了库jsonwebtoken的全部功能,但有一个重要的补充。 Jwt-redis允许您将令牌标签存储在redis中以验证有效性。 Redis中没有令牌标签会使令牌无效。要销毁jwt-redis中的令牌,有一种destroy方法

它以这种方式工作:

1)从npm安装jwt-redis

2)要创建-

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3)要验证-

jwtr.verify(token, secret);

4)要摧毁-

jwtr.destroy(token)

注意:您可以像在JWT中提供的一样,在令牌登录期间提供expiresIn。

也许这会对某人有帮助

答案 16 :(得分:1)

如果您希望能够撤消用户令牌,则可以跟踪数据库中所有已颁发的令牌,并在类似会话的表上检查它们是否有效(存在)。 缺点是您会在每次请求时访问数据库。

我还没有尝试过,但是我建议使用以下方法允许令牌撤销,同时将数据库命中率保持在最低水平-

要降低数据库检查率,请根据确定性关联将所有已发行的JWT令牌划分为X组(例如,以用户ID的第一位数字为10组)。

每个JWT令牌将保存组ID和创建令牌时创建的时间戳。例如{ "group_id": 1, "timestamp": 1551861473716 }

服务器会将所有组ID保留在内存中,并且每个组都有一个时间戳,指示何时是属于该组的用户的最后一次注销事件。 例如{ "group1": 1551861473714, "group2": 1551861487293, ... }

具有JWT令牌且组时间戳较旧的请求将被检查有效性(数据库命中),如果有效,则将发出具有新时间戳的新JWT令牌以供客户将来使用。 如果令牌的组时间戳是新的,则我们信任JWT(无数据库命中)。

所以-

  1. 我们仅在令牌具有旧的组时间戳的情况下才使用DB验证JWT令牌,而将来的请求只有在用户组中的某人退出后才会得到验证。
  2. 我们使用组来限制时间戳更改的次数(例如,没有用户登录,就像没有明天一样-只会影响有限数量的用户,而不是每个人)
  3. 我们限制了组的数量以限制保存在内存中的时间戳数量
  4. 使令牌无效很容易-只需将其从会话表中删除并为用户组生成新的时间戳即可。

答案 17 :(得分:1)

如果“从所有设备注销”选项是可接受的(在大多数情况下是这样):

  • 将令牌版本字段添加到用户记录。
  • 将此字段中的值添加到JWT中存储的声明中。
  • 每次用户注销时增加版本。
  • 在验证令牌时,将其版本声明与用户记录中存储的版本进行比较,如果不相同,则拒绝。

无论如何,在大多数情况下都需要进行一次数据库访问以获取用户记录,因此这不会增加验证过程的开销。与维护黑名单不同,在黑名单中,由于需要使用联接或单独的调用而导致数据库负载很大,因此清理旧记录等。

答案 18 :(得分:1)

我是通过以下方式做到的:

  1. 生成unique hash,然后将其存储在 redis JWT 中。这可以称为会话
    • 我们还会存储特定 JWT 所做的请求的数量 - 每次将jwt发送到服务器时,我们都会增加请求整数。 (这是可选的)
  2. 因此,当用户登录时,会创建一个唯一的哈希值,以redis格式存储并注入 JWT

    当用户尝试访问受保护的端点时,您将从 JWT 中获取唯一会话哈希,查询redis并查看它是否匹配!

    我们可以从此扩展并使我们的 JWT 更加安全,具体如下:

    每个 X 请求特定的 JWT ,我们生成一个新的唯一会话,将其存储在我们的 JWT 中,然后将其列入黑名单前一个。

    这意味着 JWT 会不断变化并停止陈旧 JWT 被黑客入侵,被盗或其他内容。

答案 19 :(得分:1)

为什么不直接使用jti声明(nonce)并将其作为用户记录字段存储在列表中(db依赖,但至少可以使用逗号分隔列表)?不需要单独查找,正如其他人所指出的那样,大概你想要获取用户记录,这样你就可以为不同的客户端实例拥有多个有效令牌(&#34;在任何地方注销&#34;可以将列表重置为空)

答案 20 :(得分:0)

如果不对每个令牌验证都进行DB查找,这似乎很难解决。我能想到的另一种方法是在服务器端保留一个无效令牌的黑名单。每当发生更改以在重新启动后持久保留更改时,都应在数据库上更新该更新,方法是使服务器在重新启动后检查数据库以加载当前黑名单。

但是,如果将其保存在服务器内存中(某种全局变量),则如果使用多个服务器,则无法在多台服务器上进行扩展,因此在这种情况下,可以将其保存在共享的Redis缓存中,应该设置它以在必须重新启动的情况下将数据持久存储在某个位置(数据库?文件系统?),并且每次启动新服务器时,都必须订阅Redis缓存。

替代黑名单,使用相同的解决方案,您可以使用每个会话的redis中保存的哈希值来完成此操作,正如其他answer指出的那样(不确定许多用户登录后这样做会更有效虽然)。

听起来听起来很复杂吗?它对我有用!

免责声明:我尚未使用Redis。

答案 21 :(得分:0)

如果您正在使用axios或类似的基于Promise的http请求库,则只需在.then()部分内部的前端销毁令牌即可。用户执行此功能后,它将在response .then()部分中启动(来自服务器端点的结果代码必须正确,200)。用户在搜索数据时单击此路由后,如果数据库字段user_enabled为假,将触发销毁令牌,并且用户将立即注销并停止访问受保护的路由/页面。当用户永久登录时,我们不必等待令牌过期。

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

答案 22 :(得分:0)

我将回答在使用JWT时是否需要从所有设备功能中注销。这种方法将对每个请求使用数据库查找。因为即使服务器崩溃,我们也需要持久性安全状态。在用户表中,我们将有两列

  1. LastValidTime(默认值:创建时间)
  2. 已登录(默认:true)

无论何时有用户注销请求,我们都会将LastValidTime更新为当前时间,将Logged-In更新为false。如果有登录请求,我们将不会更改LastValidTime,但Logged-In将设置为true。

当我们创建JWT时,我们将在有效负载中包含JWT创建时间。授权服务后,我们将检查3个条件

  1. JWT有效吗
  2. JWT有效负载创建时间是否大于用户LastValidTime
  3. 是否已登录用户

让我们看看实际的情况。

用户X有两个设备A,B。他在晚上7点使用设备A和设备B登录到我们的服务器。(假设JWT的到期时间为12小时)。 A和B都具有创建时间为7pm的JWT

晚上9点,他丢失了设备B。他立即从设备A注销。这意味着现在我们的数据库X用户条目的LastValidTime为“ ThatDate:9:00:xx:xxx”,而已登录为“ false” “。

在9:30,Thief先生尝试使用设备B登录。即使Logged-In为false,我们也会检查数据库,因此我们不允许。

晚上10点,X先生从他的设备A登录。现在设备A的JWT创建时间为:晚上10点。现在,数据库登录已设置为“ true”

晚上10:30,Thief先生尝试登录。即使Logged-In为true,也是如此。数据库中的LastValidTime是晚上9点,但是B的JWT已将时间创建为晚上7点。因此,将不允许他访问该服务。因此,在没有密码的情况下使用设备B时,一台设备注销后,他将无法使用已创建的JWT。

答案 23 :(得分:0)

像Keycloak这样的IAM解决方案(我已经研究过)提供了Token Revocation终结点,如

令牌吊销端点 /realms/{realm-name}/protocol/openid-connect/revoke

如果只想注销一个useragent(或一个用户),也可以调用一个端点(这将使令牌无效)。同样,对于Keycloak,依赖方只需要呼叫端点

/realms/{realm-name}/protocol/openid-connect/logout

Link in case if you want to learn more

答案 24 :(得分:0)

一种替代方法是只为关键的API端点提供一个中间件脚本。
如果管理员使令牌无效,则此中间件脚本将检入数据库。
对于不需要立即完全阻止用户访问的情况,此解决方案可能很有用。

答案 25 :(得分:0)

在此示例中,我假设最终用户也有一个帐户。如果不是这种情况,则其余方法不太可能起作用。

创建JWT时,将其持久保存在与登录帐户相关联的数据库中。这确实意味着仅从JWT中就可以提取有关用户的其他信息,因此根据环境的不同,这可能会有所不同。或可能不行。

在之后的每个请求中,您不仅要执行(我希望)所使用的框架附带的标准验证(验证JWT是否有效),还包括诸如用户ID或其他令牌之类的东西(即需要与数据库中的匹配)。

注销时,删除cookie(如果使用)并使数据库中的JWT(字符串)无效。如果无法从客户端删除cookie,那么至少注销过程将确保令牌被销毁。

我发现这种方法与另一个唯一的标识符(因此数据库中有2个持久项并可供前端使用)相结合,会话非常灵活

答案 26 :(得分:0)

每个用户字符串唯一,并且全局字符串哈希值

用作JWT秘密部分,允许单个和全局令牌失效。在请求身份验证期间以db查找/读取为代价的最大灵活性。也很容易缓存,因为它们很少变化。

答案 27 :(得分:0)

以下是无需每次请求都调用数据库的方法:

  • 在内存缓存中保留有效令牌的哈希图(例如,大小有限的 LRU)
  • 检查令牌时:如果令牌在缓存中,则立即返回结果,不需要数据库查询(大多数情况)。否则执行完整检查(查询数据库,检查用户状态和无效令牌......)。然后更新缓存。
  • 当令牌失效时:将其添加到数据库中的黑名单,然后更新缓存,如果需要,向所有服务器发送信号。

请记住,缓存应该具有有限的大小,例如 LRU,否则可能会耗尽内存。

答案 28 :(得分:0)

一种使令牌无效的好方法,仍然需要数据库访问。用于包括用户记录的某些部分何时更改的目的,例如更改角色、更改密码、电子邮件等。可以在用户记录中添加 modifiedupdated_at 字段,用于记录此更改的时间,然后您将其包含在声明中。因此,当 JWT 通过身份验证时,您将声明中的时间与数据库中记录的时间进行比较,如果声明的时间在之前,则令牌无效。这种方法也类似于将 iat 存储在数据库中。

注意:如果您使用 modifiedupdated_at 选项,那么您还必须在用户登录和退出时更新它。

答案 29 :(得分:-1)

如果您需要快速、高效且优雅的 logout 功能用于基于 JWT 的登录(这实际上是使 JWT 无效的主要原因),方法如下:用过期的令牌替换当前令牌1 秒。

详情:

  1. 当您从附加了有效 JWT 的客户端发出 /logout 请求时,删除您保留在本地存储中的 JWT(如果您保留了任何用于“记住我”功能的 JWT)。

  2. 然后当请求到达服务器 /logout 路由处理程序时:

    • 如果传入的 JWT 有效,则创建在 1 秒后到期的新 JWT 并将其发送回客户端。
    • 您可以使用 redirect 响应和响应正文中的标记来实现。重定向可以指向任何公共路由(例如 /home)。或者,您可以使用正常的非重定向响应进行响应,以便稍后在客户端上进行所需的重定向。
  3. 现在在客户端

    • 当您收到响应时,将新的 JWT 保存在本地存储中,您刚刚在第 1 步中删除了旧的 JWT。现在,您可以根据需要在客户端重定向,以免使用来自服务器的重定向响应。
    • 由于新令牌已经过期(因为过期时间设置为 1 秒),任何重定向到受保护路由(使用已经过期的令牌)的尝试都应重定向到注册/登录页面。您必须自己在受服务器身份验证保护的路由上提供此行为。

就这么简单。您刚刚使用 JWT 获得了通常的按需注销功能。

是的,它不会使原始 JWT 失效。如果有人(攻击者)在他仍然可以使用它之前劫持并保存了它,直到它过期。然而,他的机会之窗正在随着时间的推移而缩小。

但是,解决方案的第二部分来了:一个非常短暂(10 分钟?)的原始 JWT 和寿命更长的刷新令牌保存在数据库中,实际上正在从数据库中撤销或删除。

或者除了将原始 JWT 列入黑名单/白名单之外,还可以在此处使用上述类似方法。

快速解决方案可用于失败者的安全要求。对于更严格的安全需求情况,可以添加刷新令牌/黑名单/白名单部分。

无论如何,这是一种更简单且可按需扩展的方法。

答案 30 :(得分:-2)

我只是将令牌保存到users表,当用户登录时我将更新新令牌,当auth等于用户当前jwt时。

我认为这不是最佳解决方案,但这对我有用。

答案 31 :(得分:-3)

如果您只是从服务器生成一个新令牌,该怎么办? expiresIn:0 并将其交还给客户并将其存储在cookie中?