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

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


当前回答

您可以在用户的文档/记录上的DB上有一个“last_key_used”字段。

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

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

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

其他回答

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.

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

我是这样做的:

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

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

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

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

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

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

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

简单地创建添加以下对象到你的用户模式:

const userSchema = new mongoose.Schema({
{
... your schema code,
destroyAnyJWTbefore: Date
}

当你在/login上收到POST请求时,将这个文档的日期更改为date .now()

最后,在您的身份验证检查代码中,即在您的中间件中检查isauthenticated或protected或任何您使用的名称,只需添加一个检查myjwt的验证。iat大于userDoc.destroyAnyJWTbefore。

如果您想在服务器端销毁JWT,那么在安全性方面,这个解决方案是最好的。 这个解决方案不再依赖于客户端,它打破了使用jwt的主要目标,即停止在服务器端存储令牌。 这取决于您的项目上下文,但最可能的是您想要从服务器上销毁JWT。

如果您只想从客户端销毁令牌,只需从浏览器中删除cookie(如果您的客户端是浏览器),在智能手机或任何其他客户端上也可以这样做。

如果选择从服务器端销毁令牌,我建议您使用Radis通过实现其他用户提到的黑名单样式来快速执行此操作。

现在的主要问题是:jwt无用吗?上帝知道。

使用刷新jwt…

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

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

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

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

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

这解释了基本信息。

The next thing to add to that is to have a window for when a JWT can be refreshed, such that anything outside that window would be a suspicious activity. For example, the window can be 10min before the expiration of a JWT. The date-time a JWT was generated can be saved as a claim in that JWT itself. And when such suspicious activity occurs, i.e. when someone else tries to reuse that refresh token ID outside or within the window after it has already been used within the window, should mark the refresh token ID as invalid. Hence, even the valid owner of the refresh token ID would have to log in afresh.

如果在数据库上找不到与所提供的刷新令牌ID配对的刷新令牌,则表明刷新令牌ID应该无效。因为空闲用户可能会尝试使用攻击者已经使用过的刷新令牌。

如前所述,在目标用户之前被攻击者窃取和使用的JWT,在用户尝试使用刷新令牌时也会被标记为无效。

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

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