【问题标题】:How to destroy JWT Tokens on logout?如何在注销时销毁 JWT 令牌?
【发布时间】:2016-10-23 22:02:07
【问题描述】:

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

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

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

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

请提出一种方法。

【问题讨论】:

  • 我看到您只需将该令牌存储在某个地方,例如在数据库中,这样您就有一个唯一的令牌,例如在注销时您可以删除它,我推荐 Redis 跨度>

标签: node.js jwt hapijs


【解决方案1】:

JWT 存储在浏览器上,所以在客户端删除 cookie 删除令牌

如果您还需要在过期时间之前使服务器端的令牌失效,例如帐户删除/阻止/暂停、密码更改、权限更改、用户由管理员注销,请查看Invalidating JSON Web Tokens 了解一些常见问题创建黑名单或轮换令牌等技术

【讨论】:

  • 我同意你的回答。我通过 'reply.state('USER_SESSION', { jwtToken});' 在 cookie 中设置 jwt 令牌其中 USER_SESSION 是 cookie 名称。那么你能建议我运行什么命令来清除 hapijs 中的状态的 cookie 吗?
  • 您在服务器端,不能强制浏览器删除cookie。但是您可以将该值设置为空并包含expires 字段以使cookie 值无效。见stackoverflow.com/questions/5285940/…。您还可以使用 javascript var delete_cookie = function(name) { document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; }; 在客户端使 cookie 无效
  • 如果令牌作为 HTTP-only cookie 存储在浏览器上怎么办?然后我在客户端不能删除它,但我也不想在服务器端使其失效
  • @ChenNi,删除 HTTP-Only cookie 可以在服务器端完成(服务器发送一个带有新值的 set-cookie 标头)。令牌不会失效但无法访问
  • @pedrofb 是的,这很有道理!谢谢你:)
【解决方案2】:

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

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

  • 为令牌设置合理的过期时间

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

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

黑名单

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

Redis

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

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

【讨论】:

【解决方案3】:

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

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

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

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

这个库 (jwt-redis) 完全重复了库 jsonwebtoken 的全部功能,但有一个重要的补充。 jwt-redis 允许您将令牌标签存储在 redis 中以验证有效性。 redis中缺少token标签,导致token无效。销毁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)

注意:你可以在 token 的登录过程中提供 expiresIn,就像在 JWT 中提供的一样。

【讨论】:

  • jwt.destroy 不是函数
  • jwt没有提供任何销毁的方法,我上面已经详细解释过了,所以我使用了jwt-redis package.so,如果你看代码,我写的是jwtr.destroy( )
  • 为什么不在默认数据库中创建一个单独的位置来存储列入黑名单的令牌?为什么要引入 Redis?
  • 如果您将所有后备令牌存储在数据库中,那么您将如何在不访问数据库的情况下销毁令牌或验证其有效性。然后,对于每个带有令牌的传入请求,您必须先使用该令牌(如果存在或不存在)到达数据库,然后相应地发送错误,但我的问题是为什么不必要地访问数据库或存储根本不需要的令牌, 所以这里来了 jwt-redis,它将令牌标识符存储在 redis 中,然后从那里销毁它。销毁后无法验证。
  • 对于#4的破坏正确使用是:jwtr.destroy(token.jti)
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

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

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

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

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

    【讨论】:

      【解决方案6】:

      如果你只是想删除令牌,就像从前端应用程序中删除它一样简单,在你的情况下清除存储令牌的cookie

      另一方面,如果您要使令牌无效,有几种方法可以做到这一点,以下是一些方法

      (1) 如果生成的所有令牌都存储在后端,这就像清除存储一样简单,如果令牌已映射到用户,您只需清除特定用户的令牌.

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

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

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

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

      【讨论】:

        【解决方案7】:

        好的,所以我尝试了一些我想分享的东西它的结尾就像一个随机数(或随机散列数),以使任何人都更难反转它并获得以前有效的令牌,这样做会使该令牌无效,因此用户不会从前面去任何地方-最后,您可以将用户重定向到再次登录(或者甚至从后端,但如果前端这样做,我更喜欢)所以用户注销他们会被重定向到登录页面,这一切都很好,这是我的代码。首先,我有一个身份验证中间件,如果令牌(密码和用户名)正常,它会将令牌附加到 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);
            }
        });
        

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

        【讨论】:

        • 令牌验证中间件长什么样子?
        • const jwt=require("jsonwebtoken"); const User=require("../models/usersModel"); const auth=async(req,res,next)=>{ try{ const token = req.headers.authorization.replace("Bearer",""); const decode = jwt.verify(token,"secret"); const user=await User.findOne({ _id:decode._id }); if(!user){ throw new Error() } req.token=token;请求用户=用户; next() }catch(error){ return res.status(401).json('Unauthorized access'); } } module.exports=auth
        • 看起来标头中的令牌已放入请求对象中。但是请求对象仅在处理特定请求时才存在。如果下一个 HTTP 请求带有相同的令牌会发生什么?
        猜你喜欢
        • 2022-06-12
        • 2019-10-30
        • 2020-02-20
        • 2015-12-31
        • 1970-01-01
        • 2021-09-30
        • 2015-11-02
        • 2019-09-24
        相关资源
        最近更新 更多