对于我正在从事的一个新的node.js项目,我正在考虑从基于cookie的会话方法(我的意思是,将id存储到用户浏览器中包含用户会话的键值存储中)切换到使用JSON Web Tokens (jwt)的基于令牌的会话方法(没有键值存储)。

这个项目是一个利用socket的游戏。IO——在一个会话(web和socket.io)中有多个通信通道的情况下,有一个基于令牌的会话会很有用。

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

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

所以,假设我有以下内容(改编自this和this):

会话存储登录:

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 使用指定的令牌创建数据库。

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


当前回答

我来晚了一点,但我想我有个不错的解决办法。

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

其他回答

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

1)简单地从客户端删除令牌

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

2)创建一个令牌区块列表

您可以将无效令牌存储到它们最初的到期日期,并将它们与传入的请求进行比较。这似乎否定了首先完全基于令牌的原因,因为每个请求都需要访问数据库。存储大小可能会更小,因为您只需要存储注销时间和到期时间之间的令牌(这是一种直觉,而且肯定取决于上下文)。

3)保持代币到期时间短,并经常轮换

如果您将令牌过期时间保持在足够短的间隔内,并让运行中的客户端保持跟踪并在必要时请求更新,那么第1条将有效地作为一个完整的注销系统。这种方法的问题是,它不可能在客户端代码关闭之间保持用户登录(取决于您设置的过期间隔多长时间)。

应急计划

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

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

关于使用令牌的攻击的相似/不同之处,这篇文章解决了这个问题:https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies-vs-token.markdown

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

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

无论如何,在大多数情况下都需要db访问以获取用户记录,因此这不会给验证过程增加太多开销。与维护黑名单不同,在黑名单中,由于需要使用连接或单独调用、清除旧记录等,数据库负载非常重要。

我是这样做的:

生成一个唯一的散列,然后将其存储在redis和JWT中。这可以称为会话 我们还将存储特定JWT发出的请求数量——每次JWT被发送到服务器时,我们将请求增加整数。(这是可选的)

因此,当用户登录时,将创建一个唯一的散列,存储在redis中并注入到JWT中。

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

我们可以以此为基础,让我们的JWT更加安全,如下所示:

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

这意味着JWT是不断变化的,并防止过时的JWT被黑客攻击、窃取或其他东西。

我来晚了一点,但我想我有个不错的解决办法。

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

A good approach to invalidating a token would still need database trips. For a purpose that includes when some parts of the user record change, for example changing roles, changing passwords, email, and more. One can add a modified or updated_at field in the user record, which records the time of this change, and then you include this in the claims. So when a JWT is authenticated, you compare the time in the claims with the one recorded in the DB, if that of the claim was before, then token is invalid. This approach is also similar to storing the iat in the DB.

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