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

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


当前回答

这主要是一个很长的评论,支持并建立在@mattway的回答上

考虑到:

本页上提出的其他一些解决方案提倡对每个请求都访问数据存储。如果您使用主数据存储来验证每个身份验证请求,那么我认为使用JWT而不是其他已建立的令牌身份验证机制的理由就更少了。如果每次都访问数据存储,那么实际上JWT是有状态的,而不是无状态的。

(如果您的站点接收到大量未经授权的请求,那么JWT将在不访问数据存储的情况下拒绝它们,这很有帮助。可能还有其他类似的用例。)

考虑到:

真正的无状态JWT身份验证无法在典型的、真实的web应用程序中实现,因为无状态JWT无法为以下重要用例提供即时和安全的支持:

用户帐号被删除/屏蔽/挂起。 完成用户密码的修改。 用户角色或权限发生变更。 用户被admin注销。 JWT令牌中的任何其他应用程序关键数据都由站点管理员更改。

在这些情况下,您不能等待令牌到期。令牌失效必须立即发生。此外,您不能相信客户端不会保留和使用旧令牌的副本,无论是否是出于恶意。

因此:

我认为来自@mat -way的答案,#2 TokenBlackList,将是向基于JWT的身份验证添加所需状态的最有效方法。

您有一个黑名单保存这些令牌,直到它们的过期日期。与用户总数相比,代币列表将非常小,因为它只需要保留黑名单上的代币直到到期。我将通过在redis、memcached或其他支持设置密钥过期时间的内存数据存储中放置无效的令牌来实现。

对于每个通过初始JWT身份验证的身份验证请求,仍然需要调用内存中的数据库,但不必将整个用户集的密钥存储在其中。(对于一个特定的网站来说,这可能是也可能不是什么大问题。)

其他回答

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

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

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

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

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

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

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);
    });
}

---------------- 这个答案一点迟到但可能会帮助别人 ----------------

从客户端,最简单的方法是从浏览器的存储中删除令牌。

但是,如果您想销毁节点服务器上的令牌-

JWT包的问题是它没有提供任何方法或方法来销毁令牌。 您可以使用上面提到的关于JWT的不同方法。但是这里我用的是jwt-redis。

所以为了在服务器端销毁令牌,你可以使用JWT -redis包而不是JWT

这个库(jwt-redis)完全重复了库jsonwebtoken的全部功能,只增加了一个重要的功能。Jwt-redis允许您将tokenIdentifier存储在redis中以验证有效性。redis中缺少tokenIdentifier使得令牌无效。要销毁jwt-redis中的令牌,有一个destroy方法

它是这样工作的:

从npm安装jwt-redis 创建:

Var redis = require('redis'); var JWTR = require('jwt-redis').default; var redisClient = redis.createClient(); var jwtr = new jwtr (redisClient); Const secret = 'secret'; const tokenIdentifier = 'test'; const payload = {jti: tokenIdentifier};//你也可以在payload中放入其他数据 jwtr。号(载荷、秘密) 不要犹豫((令牌)= > { //你的代码 }) .catch((错误)= > { //错误处理 });

验证:

jwtr。验证(令牌,秘密);

摧毁:

//如果jti在token的签名过程中传递,那么tokenIdentifier else token jwtr.destroy(tokenIdentifier或token)

注意:

1).你可以在token的登录过程中提供expiresIn,就像JWT中提供的一样。

2).如果在token的签名过程中没有传递jti,那么jti将由库随机生成。

也许这能帮到你或其他人。谢谢。

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

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