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

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


当前回答

没有使用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被盗?目前我对此的回答是,偶尔提醒用户全局注销并重新登录,因为这肯定会让攻击者注销。

其他回答

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

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

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

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

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

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

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

应急计划

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

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

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

If you are using axios or a similar promise-based http request lib you can simply destroy token on the front-end inside the .then() part. It will be launched in the response .then() part after user executes this function (result code from the server endpoint must be ok, 200). After user clicks this route while searching for data, if database field user_enabled is false it will trigger destroying token and user will immediately be logged-off and stopped from accessing protected routes/pages. We don't have to await for token to expire while user is permanently logged on.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

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选项,那么您还必须在用户登录和退出时更新它。

在内存中做一个这样的列表

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

如果您的令牌在一周内过期,则清除或忽略更早的记录。同时只保留每个用户的最新记录。 列表的大小取决于您保留令牌的时间以及用户撤销令牌的频率。 仅当表发生变化时才使用db。在应用程序启动时将表加载到内存中。

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

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