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

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


当前回答

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

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

其他回答

上面的想法很好,但是有一种非常简单的方法可以使所有现有的jwt无效,那就是简单地改变秘密。

如果您的服务器创建了JWT,用一个秘密(JWS)对其进行签名,然后将其发送给客户端,简单地更改秘密将使所有现有的令牌无效,并要求所有用户获得一个新的令牌进行身份验证,因为他们的旧令牌突然根据服务器变得无效。

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

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

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

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

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

任何时候你想要让旧的jwt失效,只要改变用户的jwt版本号。

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

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

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

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

没有使用jwt的刷新…

我想到了两种袭击的场景。一个是关于登录凭证的泄露。另一个是对JWT的盗窃。

For compromised login credentials, when a new login happens, normally send the user an email notification. So, if the customer doesn't consent to being the one who logged in, they should be advised to do a reset of credentials, which should save to database/cache the date-time the password was last set (and set this too when user sets password during initial registration). Whenever a user action is being authorized, the date-time a user changed their password should be fetched from database/cache and compared to the date-time a given JWT was generated, and forbid the action for JWTs that were generated before the said date-time of credentials reset, hence essentially rendering such JWTs useless. That means save the date-time of generation of a JWT as a claim in the JWT itself. In ASP.NET Core, a policy/requirement can be used to do do this comparison, and on failure, the client is forbidden. This consequently logs out the user on the backend, globally, whenever a reset of credentials is done.

For actual theft of JWT... A theft of JWT is not easy to detect but a JWT that expires easily solves this. But what can be done to stop the attacker before the JWT expires? It is with an actual global logout. It is similar to what was described above for credentials reset. For this, normally save on database/cache the date-time a user initiated a global logout, and on authorizing a user action, get it and compare it to the date-time of generation of a given JWT too, and forbid the action for JWTs that were generated before the said date-time of global logout, hence essentially rendering such JWTs useless. This can be done using a policy/requirement in ASP.NET Core, as previously described.

现在,你如何发现JWT被盗?目前我对此的回答是,偶尔提醒用户全局注销并重新登录,因为这肯定会让攻击者注销。