无效的JSON Web令牌

JavaScript Node.js

Davaid阳光小卤蛋

2020-03-17

对于我正在研究的一个新的node.js项目,我正在考虑从基于cookie的会话方法切换(这意味着,将ID存储到包含用户会话的键值存储中的用户浏览器中)到使用JSON Web令牌(jwt)的基于令牌的会话方法(无键值存储)。

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

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

我还想了解我应该用这种范例寻找哪些常见(或不常见)的陷阱/攻击。例如,如果此范例易受与基于会话存储/ cookie的方法相同/不同类型的攻击的影响。

因此,说我有以下内容(适应了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数据库。

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

第1959篇《无效的JSON Web令牌》来自Winter(https://github.com/aiyld/aiyld.github.io)的站点

15个回答
Near阳光 2020.03.17

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

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

Sam斯丁 2020.03.17

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

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

为了降低数据库检查率,根据确定性关联将所有已发行的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. 使令牌无效很容易-只需将其从会话表中删除并为用户组生成新的时间戳即可。
老丝Tom前端 2020.03.17

保留这样的内存列表

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

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

6的不行 2020.03.17

每个用户字符串唯一,并且全局字符串散列在一起

作为JWT机密部分,允许单独和全局令牌无效。在请求身份验证期间以数据库查找/读取为代价的最大灵活性。由于它们很少更改,因此也易于缓存。

Stafan小小 2020.03.17

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

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

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

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

免责声明:我没有使用过Redis。

乐米亚 2020.03.17

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

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

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

西里凯 2020.03.17
  1. 给出令牌的1天到期时间
  2. 维持每日黑名单。
  3. 将无效/注销令牌放入黑名单

对于令牌验证,如果令牌未过期,请先检查令牌的过期时间,然后再检查黑名单。

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

老丝小卤蛋 2020.03.17

为什么不只使用jti声明(立即)并将其作为用户记录字段存储在列表中(取决于数据库,但至少用逗号分隔的列表就可以了)?无需单独查找,就像其他人指出的那样,无论如何您都想获取用户记录,因此您可以为不同的客户端实例使用多个有效令牌(“注销到处”可以将列表重置为空)

阿飞Jim 2020.03.17

晚了晚会,经过一些研究,下面给了我两美分。注销期间,请确保正在发生以下事情...

清除客户端存储/会话

每当分别发生登录或注销时,更新用户表的上次登录日期时间和注销日期时间。因此,登录日期时间应始终大于注销时间(或者,如果当前状态为登录且尚未注销,则使注销日期为null)

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

神奇 2020.03.17

您可以在数据库中的用户文档/记录上拥有一个“ last_key_used”字段。

当用户与用户一起登录并通过时,生成一个新的随机字符串,将其存储在last_key_used字段中,并在对令牌进行签名时将其添加到有效负载中。

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

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

猴子神无 2020.03.17

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

我的数据库中有一个“ last_password_change”列,该列存储上次更改密码的日期和时间。我还将发布的日期/时间存储在JWT中。验证令牌时,我检查密码在颁发令牌后是否已更改,并且即使令牌尚未过期也被拒绝。

AItachi 2020.03.17

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

1)只需从客户端删除令牌

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

2)创建一个令牌黑名单

您可以存储无效令牌,直到它们的初始到期日期,然后将它们与传入请求进行比较。不过,这似乎可以消除完全基于令牌的原因,因为您需要为每个请求触摸数据库。不过,存储空间可能会更小,因为您只需要存储注销和到期时间之间的令牌(这是一种直觉,并且绝对取决于上下文)。

3)保持令牌到期时间短并经常轮换

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

临时计划

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

我还想指出,在令牌中包含上次登录日期是个好主意,这样您就可以在很长一段时间后强制重新登录。

关于使用令牌进行攻击的相似性/差异性,本文讨论了以下问题:https : //github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown

神奇Tony古一 2020.03.17

我一直在考虑的一种方法是iat在JWT中始终具有一个值(发布于)。然后,当用户注销时,将该时间戳记存储在用户记录中。验证JWT时,只需将iat和上次注销的时间戳进行比较即可。如果iat年龄较大,则无效。是的,您必须去数据库,但是如果JWT有效,无论如何我都会一直拉用户记录。

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

这对于使系统中的所有JWT无效也是一种很好的机制。检查的一部分可能与最后一个有效iat时间的全局时间戳相对

小胖MandyGil 2020.03.17

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

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

每当您想使旧的jwts失效时,只需增加用户的jwt版本号即可。

乐小小猪猪 2020.03.17

上面发布的想法很好,但是使所有现有JWT失效的非常简单的方法就是更改秘密。

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

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

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

问题类别

JavaScript Ckeditor Python Webpack TypeScript Vue.js React.js ExpressJS KoaJS CSS Node.js HTML Django 单元测试 PHP Asp.net jQuery Bootstrap IOS Android