如何使用黑名单使 JWT 失效

2025-06-04

如何使用黑名单使 JWT 失效

本文将向您展示如何使用令牌黑名单方法使 JWT 失效。令牌黑名单方法用于创建注销系统。这是在注销请求时使 JWT 失效的方法之一。

JWT 的主要特性之一是无状态,存储在客户端而不是数据库中。您无需查询数据库来验证令牌。只要签名正确且令牌未过期,它就允许用户访问受限资源。当您希望减少数据库负载时,这是最有效的。然而,缺点是,这使得使现有的、未过期的令牌失效变得困难。

为什么要列入黑名单?

您需要使令牌失效的原因之一是,当您创建注销系统并使用 JWT 作为身份验证方法时。创建黑名单是使令牌失效的众多方法之一。其背后的逻辑简单明了,易于理解和实现。

即使 JWT 从客户端删除后,其仍然有效,具体取决于令牌的到期日期。因此,使其失效可以确保它不会再次用于身份验证。
如果令牌的有效期较短,这可能不是问题。不过,如果您愿意,仍然可以创建黑名单。

创建黑名单

  1. 当您的 Web 服务器收到注销请求时,请获取令牌并将其存储在内存数据库中,例如 Redis。我们之所以使用这种方法,是因为速度和效率更高,因为您不希望每次有人要注销时都访问主数据库。此外,您也不必在数据库中存储一堆无效的令牌。请参考我下面的方法;

首先,创建一个中间件来验证令牌:

const verifyToken = (request, response, next) => {

// Take the token from the Authorization header
  const token = request.header('Authorization').replace('Bearer ', '');
  if (!token) {
    response.status(403).send({
      message: 'No token provided!',
    });
  }

// Verify the token
  jwt.verify(token, config.secret, (error, decoded) => {
    if (error) {
      return response.status(401).send({
        status: 'error',
        message: error.message,
      });
    }

// Append the parameters to the request object
    request.userId = decoded.id;
    request.tokenExp = decoded.exp;
    request.token = token;
    next();
  });
};
Enter fullscreen mode Exit fullscreen mode

然后,

// This is a NodeJs example. The logic can be replicated in any language or framework.

// 1. The server recieves a logout request
// 2. The verifyToken middleware checks and makes sure the token in the request object is valid
router.post('/logout', verifyToken, (request, response) => {

// 3. take out the userId and toekn from the request
  const { userId, token } = request;

// 4. use the get method provided by redis to check with the userId to see if the user exists in the blacklist
  redisClient.get(userId, (error, data) => {
    if (error) {
      response.send({ error });
    }

// 5. if the user is on the blacklist, add the new token 
// from the request object to the list of 
// token under this user that has been invalidated.

/*
The blacklist is saved in the format => "userId": [token1, token2,...]

redis doesn't accept obejcts, so you'd have to stringify it before adding 
*/ 
    if (data !== null) {
      const parsedData = JSON.parse(data);
      parsedData[userId].push(token);
      redisClient.setex(userId, 3600, JSON.stringify(parsedData));
      return response.send({
        status: 'success',
        message: 'Logout successful',
      });
    }

// 6. if the user isn't on the blacklist yet, add the user the token 
// and on subsequent requests to the logout route the user 
// will be found and the token will be appended to the already existing list.
    const blacklistData = {
      [userId]: [token],
    };
    redisClient.setex(userId, 3600, JSON.stringify(blacklistData));
    return response.send({
        status: 'success',
        message: 'Logout successful',
    });
  });
});
Enter fullscreen mode Exit fullscreen mode
  1. 然后,对于每个需要用户身份验证的请求,您都需要检查内存数据库,以检查令牌是否已失效。然后,根据检查结果发送响应。请看下面我的方法;
module.exports = (request, response, next) => {

// 1. take out the userId and toekn from the request
  const { userId, token } = request;

// 2. Check redis if the user exists 
  redisClient.get(userId, (error, data) => {
    if (error) {
      return response.status(400).send({ error });
    }
// 3. if so, check if the token provided in the request has been blacklisted. If so, redirect or send a response else move on with the request.
    if (data !== null) {
      const parsedData = JSON.parse(data);
      if (parsedData[userId].includes(token)) {
        return response.send({
          message: 'You have to login!',
        });
      }
      return next();
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

为了提高搜索效率,你可以从黑名单中移除已过期的令牌。具体操作步骤如下:

  1. 验证令牌的真实性
  2. 如果验证成功,则将用户 ID、令牌本身及其到期日期附加到请求对象。
  3. 将令牌及其本身的到期日期存储在 Redis 中。
    // 1. The server receives a logout request
    // 2. The verifyToken middleware checks 
   // and makes sure the token in the request 
   // object is valid and it appends it to the request object, 
   // as well as the token expiration date

    router.post('/logout', verifyToken, (request, response) => {

    // 3. take out the userId, token and tokenExp from the request
      const { userId, token, tokenExp } = request;

    /** 
    4. use the set method provided by Redis to insert the token

    Note: the format being used is to combine 'blacklist_' as a prefix to the token and use it as the key and a boolean, true, as the value. We also set the expiration time for the key in Redis to the same expiration time of the token itself as stated above
    **/
      redisClient.setex(`blacklist_${token}`, tokenExp, true);

    // return  the response
      return response.send({
        status: 'success',
        message: 'Logout successful',
      });
    });
Enter fullscreen mode Exit fullscreen mode

然后,对于每个需要用户身份验证的请求,您都需要检查内存数据库,以查看令牌是否已失效,然后根据检查结果发送响应。请看下面我的方法。

module.exports = (request, response, next) => {

// 1. take out the token from the request
  const { token } = request;

// 2. Check Redis if the token exists. If so, redirect or send a response else move on with the request.
  redisClient.get(`blacklist_${token}`, (error, data) => {
    if (error) {
      return response.status(400).send({ error });
    }
    if (data !== null) {
      return response.send({
        message: 'You have to login!',
      });
    }
// 3. If not, move on with the request.
    return next();
  });
};
Enter fullscreen mode Exit fullscreen mode

结论

这是使令牌失效的众多方法之一。我个人使用这种方法,效果很好。希望您在评论区分享您的想法。

谢谢阅读,加油。

文章来源:https://dev.to/chukwutosin_/how-to-invalidate-a-jwt-using-a-blacklist-28dl
PREV
如何成为一名更好的程序员
NEXT
实用范畴论 | 第一部分:半群简介