对于我正在从事的一个新的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 使用指定的令牌创建数据库。

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


当前回答

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

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

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

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

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

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

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

应急计划

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

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

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

其他回答

即使从存储中删除令牌,它仍然有效,但只是在短时间内有效,以降低它被恶意使用的可能性。

您可以创建一个拒绝列表,一旦从存储中删除令牌,就可以将令牌添加到该列表中。如果你有一个微服务,所有其他使用这个令牌的服务都必须添加额外的逻辑来检查这个列表。这将集中您的身份验证,因为每个服务器都必须检查一个集中的数据结构。

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

我认为这不是最好的解决方案,但对我来说是可行的。

在经过一些研究后,我的观点如下。 在登出期间,确保发生了以下事情…

清除客户端存储/会话

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

这远比保持额外的黑名单表和定期清洗简单。多设备支持需要额外的表来保存loggedIn、注销日期和一些额外的详细信息,如操作系统或客户端详细信息。

代币的有效期为1天 每天建立一个黑名单。 将无效/注销令牌放入黑名单

对于令牌验证,首先检查令牌的过期时间,如果令牌没有过期,则检查黑名单。

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

我是这样做的:

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

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

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

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

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

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