如何在注销时销毁 JWT 令牌?

How to destroy JWT Tokens on logout?

我在 hapijs 中使用 jwt 插件和策略。

我可以在登录用户时创建 jwt 令牌,并通过 'jwt' 策略使用相同的令牌验证其他 API。

我将 request.state.USER_SESSION 中的令牌设置为 cookie,其中 USER_SESSION 是令牌名称。另外,我没有将这些令牌保存在数据库中。

但是如何在注销时销毁jwt令牌?

请推荐一个方法。

JWT 存储在浏览器中,因此在客户端删除令牌删除 cookie

如果您还需要在服务器端使令牌在到期时间之前失效,例如帐户 deleted/blocked/suspended、密码已更改、权限已更改、用户已被管理员注销,请查看 Invalidating JSON Web Tokens 一些常用技术,例如创建黑名单或旋转令牌

您无法在令牌创建后手动使其过期。因此,您不能像在会话中那样在服务器端使用 JWT 注销。

JWT 是无状态的,这意味着您应该将所需的一切存储在有效负载中,并跳过对每个请求执行数据库查询。但是如果你打算有一个严格的注销功能,不能等待令牌自动过期,即使你已经从客户端清除了令牌,那么你可能需要忽略无状态逻辑并进行一些查询。那么解决方案是什么?

  • 设置合理的代币到期时间

  • 注销时从客户端删除存储的令牌

  • 针对每个授权请求黑名单查询提供的令牌

黑名单

所有不再有效且尚未过期的令牌的“黑名单”。您可以在文档上使用具有 TTL 选项的数据库,该选项将设置为令牌过期前剩余的时间。

Redis

Redis 是 blacklist 的一个很好的选择,它将允许在内存中快速访问列表。然后,在针对每个授权请求运行的某种中间件中,您应该检查提供的令牌是否在 Blacklist 中。如果是,你应该抛出一个未经授权的错误。如果不是,则放手,JWT 验证将处理它并确定它是否已过期或仍然有效。

有关详细信息,请参阅 How to log out when using JWT。作者 Arpy Vanyan(来源和参考)

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

但是,如果你想销毁节点服务器上的令牌怎么办 -

JWT 包的问题是它没有提供任何销毁令牌的方法或方式。

因此,为了在服务器端销毁令牌,您可以使用 jwt-redis 包而不是 JWT

这个库 (jwt-redis) 完全重复了库 jsonwebtoken 的全部功能,但有一个重要的补充。 Jwt-redis 允许将令牌标签存储在redis 中以验证有效性。 redis 中缺少令牌标签会导致令牌无效。在jwt-redis中销毁token,有一个destroy方法

它是这样工作的:

1) 从 npm 安装 jwt-redis

2) 创建 -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) 验证 -

jwtr.verify(token, secret);

4) 毁灭 -

jwtr.destroy(token)

注意 : 你可以在令牌登录期间提供 expiresIn ,就像在 JWT 中提供的一样。

虽然其他答案针对各种设置提供了详细的解决方案,但这可能会对那些只是寻找一般答案的人有所帮助。

共有三个通用选项,选择一个或多个:

  1. 在客户端,使用javascript从浏览器中删除cookie。

  2. 在服务器端,将cookie值设置为空字符串或无用的东西(例如"deleted"),并将cookie过期时间设置为过去的时间。

  3. 在服务器端,更新存储在数据库中的刷新令牌。使用此选项从用户登录的所有设备注销用户(他们的刷新令牌将失效,他们必须重新登录)。

您可以为令牌添加“发布时间”并为服务器上的每个用户维护“上次注销时间”。当您检查令牌有效性时,还要检查“发布时间”是否在“上次注销时间”之后。

好的所以我尝试了一些我想分享的东西我认为这是一个非常简单有效的方法所以基本上我们可以简单地在随机索引的中间附加一个随机值而不是破坏你的令牌或黑名单即使在它的末尾,像一个随机数(或随机哈希数),使任何人都更难逆转它并获得以前有效的令牌,这样做会使该令牌无效,因此用户不会去任何地方,也不会离开前端你可以重定向用户再次登录(或者甚至从后端登录,但我更喜欢前端这样做)所以用户注销他们被重定向到登录页面,这一切都很好,这是我的代码.首先,我有一个身份验证中间件,如果令牌(密码和用户名)没问题,它会将令牌附加到 req.token 所以每当我调用这个中间件时,用户的令牌将被保存到 req.token

router.post('/logout', auth, async(req, res) => {
    try{
        let randomNumberToAppend = toString(Math.floor((Math.random() * 1000) + 1));
        let randomIndex = Math.floor((Math.random() * 10) + 1);
        let hashedRandomNumberToAppend = await bcrypt.hash(randomNumberToAppend, 10);
    
        // now just concat the hashed random number to the end of the token
        req.token = req.token + hashedRandomNumberToAppend;
        return res.status(200).json('logout');
    }catch(err){
        return res.status(500).json(err.message);
    }
});

现在它将把散列的随机数连接到令牌的末尾,这意味着它不再有效,因此用户将不得不重新登录,因为他们将被重定向到登录页面

如果您只想删除令牌,只需将其从前端应用程序中删除即可,在这种情况下,请清除存储令牌的 cookie

另一方面,如果您想让令牌失效,有几种方法可以做到,下面是一些方法

(1) 如果生成的所有token都存储在后端,那么清除存储就很简单了,如果token已经映射到用户就可以清除特定用户的令牌。

(2) 您可以添加日期字段,例如“invalidate_before”以及 user 应在更改密码、从所有设备注销等事件中更新。 只需将此类事件的 invalidate_before 更新为 currentTime() 即可。 每次创建新令牌时,将创建时间添加到令牌负载中, 要在传入请求上验证令牌,只需检查有效负载中的创建时间是否大于 invalidate_before 该用户在 db

中的时间

(3) 当您创建一个新用户时,为该用户创建一个秘密,然后您可以使用该特定秘密为每个用户令牌签名,就像在 (2) 更改密码、从所有设备注销等事件,应该创建一个新的秘密。 这样你也可以通过检查令牌签名来使无效。

(2)(3) 的开销是,验证将是一个两步过程,它涉及数据库读取

编辑:对于(3),您可以改用盐(最终秘密将是公共秘密+特定用户的盐) ,这样你就有办法通过更改盐来使单个用户的令牌无效,或者通过更改公共秘密来使所有用户的令牌无效