用expressjs、jwt 和redis 实现一个账号只能登录一个客户端,同时登录就会被踢下线的功能
为了实现一个账号只能登录一个客户端且同时登录会踢下线的功能。no bb show the code
, 以下是一个更完整的示例:
const express = require('express');
const redis = require('redis');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const redisClient = redis.createClient();
const app = express();
const port = 3000;
const JWT_SECRET = 'your-jwt-secret-key';
app.use(bodyParser.json());
app.post('/login', (req, res) => {
const { username } = req.body;
// Check if the user is already logged in on another client
redisClient.get(username, (err, reply) => {
if (err) {
console.error('Redis error:', err);
res.status(500).json({ error: 'Internal Server Error' });
} else if (reply) {
const storedToken = reply.toString();
jwt.verify(storedToken, JWT_SECRET, (jwtErr, decoded) => {
if (jwtErr) {
// Token is invalid or expired, proceed with login
const token = jwt.sign({ username }, JWT_SECRET);
redisClient.set(username, token);
res.json({ token, message: 'Logged in successfully.' });
} else {
// Invalidate previous token and issue a new token
redisClient.del(username, () => {
const newToken = jwt.sign({ username }, JWT_SECRET);
redisClient.set(username, newToken);
res.json({ newToken, message: 'Logged in successfully. Previous session invalidated.' });
});
}
});
} else {
// If user is not logged in, simply create a new token
const token = jwt.sign({ username }, JWT_SECRET);
redisClient.set(username, token);
res.json({ token, message: 'Logged in successfully.' });
}
});
});
app.post('/logout', (req, res) => {
const { token } = req.body;
if (!token) {
return res.status(400).json({ error: 'Token is required.' });
}
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token.' });
}
const username = decoded.username;
// Remove the user's token and mark them as logged out
redisClient.del(username);
res.json({ message: 'Logged out successfully.' });
});
});
app.get('/verify/:token', (req, res) => {
const token = req.params.token;
jwt.verify(token, JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token.' });
}
const username = decoded.username;
redisClient.get(username, (redisErr, reply) => {
if (redisErr) {
console.error('Redis error:', redisErr);
res.status(500).json({ error: 'Internal Server Error' });
} else if (reply && reply.toString() === token) {
res.json({ message: 'User is logged in.' });
} else {
res.json({ message: 'User is not logged in.' });
}
});
});
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
上面的代码实现了一个基于 JSON Web Tokens (JWT) 和 Redis 的用户会话管理系统,流程如下:
-
用户登录: 当用户尝试登录时,系统会首先检查 Redis 数据库,看是否已存在该用户的登录记录。如果存在,表示用户已经在另一个客户端登录,此时系统会对已存在的令牌进行验证。如果验证失败(令牌无效或已过期),系统会为当前登录的客户端创建一个新的 JWT 令牌并存储在 Redis 中,表示该客户端已成功登录。如果验证成功,表示用户已经在其他客户端登录,系统会作废之前的令牌并为当前客户端创建一个新的 JWT 令牌。
-
用户注销: 用户在注销时,需要提供当前客户端的 JWT 令牌。系统会对令牌进行验证,确保它是有效的,并从 Redis 数据库中删除与该用户相关的令牌记录,表示用户已经注销。
-
令牌有效性验证: 为了验证令牌的有效性,系统在
/verify/:token
路由中提供了一个接口。当用户访问此接口并提供一个 JWT 令牌时,系统会首先对令牌进行验证,如果令牌验证通过并且与 Redis 中的记录匹配,则表示用户在其他客户端登录了。如果令牌验证通过但与 Redis 中的记录不匹配,说明之前的令牌已被作废,用户可能在其他客户端登录,但不能确定是否在当前客户端登录。 -
Redis 存储: 为了实现只能登录一个客户端的功能,系统使用 Redis 数据库来存储用户和相关令牌的映射关系。当用户登录时,系统将用户名和 JWT 令牌存储在 Redis 中。在注销或者重新登录时,系统会根据用户名从 Redis 中删除令牌记录。这样,在用户尝试登录时,系统可以查看 Redis 中是否已存在相关记录,以判断用户是否已经在其他客户端登录。
-
错误处理: 代码中包含了基本的错误处理,如 Redis 错误、JWT 验证错误等。这有助于保持应用程序的稳定性,并对客户端提供有意义的错误消息。
-
中间件验证: 为了确保安全性,代码中使用中间件来验证用户提供的 JWT 令牌是否有效。在
/logout
路由中,用户需要提供有效的 JWT 令牌才能注销。
总之,该代码示例结合了 JWT 和 Redis,实现了基本的用户会话管理功能,确保了只能登录一个客户端且同时登录会踢下线的需求。然而,在实际生产环境中,仍需进一步的安全性和错误处理措施,以及与前端的配合和其他优化。