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

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


当前回答

I ended up with access-refresh tokens, where refresh tokens uuids stored in database and access tokens uuids stored in cache server as a whitelist of valid access tokens. For example, I have critical changes in user data, for example, his access rights, next thing I do - I remove his access token from cache server whitelist and by the next access to any resource of my api, auth service will be asked for token's validity, then, if it isn't present in cache server whitelist, I will reject user's access token and force him to reauthorize by refresh token. If I want to drop user's session or all of his sessions, I simply drop all his tokens from whitelist and remove refresh tokens from database, so he musts re-enter credentials to continue accessing resources.

我知道,我的身份验证不再是无状态的,但公平地说,我为什么还要无状态的身份验证呢?

其他回答

下面是如何做到这一点,而不必对每个请求都调用数据库:

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

请记住,缓存的大小应该是有限的,就像LRU一样,否则可能会耗尽内存。

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

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

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

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

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

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

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

应急计划

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

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

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

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

清除客户端存储/会话

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

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

另一种选择是为关键的API端点提供一个中间件脚本。 如果管理员使令牌失效,此中间件脚本将检入数据库。 这种解决方案可能适用于不需要立即完全阻止用户访问的情况。

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

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