如何使用黑名单使 JWT 失效
本文将向您展示如何使用令牌黑名单方法使 JWT 失效。令牌黑名单方法用于创建注销系统。这是在注销请求时使 JWT 失效的方法之一。
JWT 的主要特性之一是无状态,存储在客户端而不是数据库中。您无需查询数据库来验证令牌。只要签名正确且令牌未过期,它就允许用户访问受限资源。当您希望减少数据库负载时,这是最有效的。然而,缺点是,这使得使现有的、未过期的令牌失效变得困难。
为什么要列入黑名单?
您需要使令牌失效的原因之一是,当您创建注销系统并使用 JWT 作为身份验证方法时。创建黑名单是使令牌失效的众多方法之一。其背后的逻辑简单明了,易于理解和实现。
即使 JWT 从客户端删除后,其仍然有效,具体取决于令牌的到期日期。因此,使其失效可以确保它不会再次用于身份验证。
如果令牌的有效期较短,这可能不是问题。不过,如果您愿意,仍然可以创建黑名单。
创建黑名单
- 当您的 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();
});
};
然后,
// 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',
});
});
});
- 然后,对于每个需要用户身份验证的请求,您都需要检查内存数据库,以检查令牌是否已失效。然后,根据检查结果发送响应。请看下面我的方法;
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();
}
});
};
为了提高搜索效率,你可以从黑名单中移除已过期的令牌。具体操作步骤如下:
- 验证令牌的真实性
- 如果验证成功,则将用户 ID、令牌本身及其到期日期附加到请求对象。
- 将令牌及其本身的到期日期存储在 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',
});
});
然后,对于每个需要用户身份验证的请求,您都需要检查内存数据库,以查看令牌是否已失效,然后根据检查结果发送响应。请看下面我的方法。
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();
});
};
结论
这是使令牌失效的众多方法之一。我个人使用这种方法,效果很好。希望您在评论区分享您的想法。
谢谢阅读,加油。
文章来源:https://dev.to/chukwutosin_/how-to-invalidate-a-jwt-using-a-blacklist-28dl