likes
comments
collection
share

用expressjs、jwt 和redis 实现一个账号只能登录一个客户端,同时登录就会被踢下线的功能

作者站长头像
站长
· 阅读数 11

为了实现一个账号只能登录一个客户端且同时登录会踢下线的功能。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 的用户会话管理系统,流程如下:

  1. 用户登录: 当用户尝试登录时,系统会首先检查 Redis 数据库,看是否已存在该用户的登录记录。如果存在,表示用户已经在另一个客户端登录,此时系统会对已存在的令牌进行验证。如果验证失败(令牌无效或已过期),系统会为当前登录的客户端创建一个新的 JWT 令牌并存储在 Redis 中,表示该客户端已成功登录。如果验证成功,表示用户已经在其他客户端登录,系统会作废之前的令牌并为当前客户端创建一个新的 JWT 令牌。

  2. 用户注销: 用户在注销时,需要提供当前客户端的 JWT 令牌。系统会对令牌进行验证,确保它是有效的,并从 Redis 数据库中删除与该用户相关的令牌记录,表示用户已经注销。

  3. 令牌有效性验证: 为了验证令牌的有效性,系统在 /verify/:token 路由中提供了一个接口。当用户访问此接口并提供一个 JWT 令牌时,系统会首先对令牌进行验证,如果令牌验证通过并且与 Redis 中的记录匹配,则表示用户在其他客户端登录了。如果令牌验证通过但与 Redis 中的记录不匹配,说明之前的令牌已被作废,用户可能在其他客户端登录,但不能确定是否在当前客户端登录。

  4. Redis 存储: 为了实现只能登录一个客户端的功能,系统使用 Redis 数据库来存储用户和相关令牌的映射关系。当用户登录时,系统将用户名和 JWT 令牌存储在 Redis 中。在注销或者重新登录时,系统会根据用户名从 Redis 中删除令牌记录。这样,在用户尝试登录时,系统可以查看 Redis 中是否已存在相关记录,以判断用户是否已经在其他客户端登录。

  5. 错误处理: 代码中包含了基本的错误处理,如 Redis 错误、JWT 验证错误等。这有助于保持应用程序的稳定性,并对客户端提供有意义的错误消息。

  6. 中间件验证: 为了确保安全性,代码中使用中间件来验证用户提供的 JWT 令牌是否有效。在 /logout 路由中,用户需要提供有效的 JWT 令牌才能注销。

总之,该代码示例结合了 JWT 和 Redis,实现了基本的用户会话管理功能,确保了只能登录一个客户端且同时登录会踢下线的需求。然而,在实际生产环境中,仍需进一步的安全性和错误处理措施,以及与前端的配合和其他优化。