You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express.js+MongoDB应用:用户账号删除后仍保持登录状态的问题

管理员删除用户后,用户仍保持登录状态的问题

我在基于Express.js开发、使用MongoDB存储用户数据的应用中遇到如下问题:当管理员删除用户账号后,该用户仍保持登录状态,可继续访问系统,即便删除操作执行时用户正处于活跃登录状态。

复现步骤

  • 在一个浏览器标签页以用户身份登录
  • 在另一个标签页打开管理员页面
  • 使用管理员功能删除该用户账号
  • 返回用户登录的标签页并刷新页面
  • 发现用户仍处于登录状态,可正常访问系统

相关代码

app.js

// app.js 

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const exphbs = require('express-handlebars');
const connectDB = require('./config/connection');
const session = require('express-session');
const MongoStore = require('connect-mongo');

const userRoutes = require('./routes/userRoutes');
const adminRoutes = require('./routes/adminRoutes');

const app = express();

// View engine setup with Handlebars
app.set('views', path.join(__dirname, 'views'));
app.engine('.hbs', exphbs.engine({
    extname: '.hbs',
    defaultLayout: 'main',
    layoutsDir: path.join(__dirname, 'views', 'layout'),
    partialsDir: path.join(__dirname, 'views', 'partials'),
    runtimeOptions: {
        allowProtoPropertiesByDefault: true,
        allowProtoMethodsByDefault: true
    }
}));
app.set('view engine', '.hbs');

// Middleware setup
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(session({
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: true,
    store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/your-db' }),
    cookie: { sameSite: 'strict', maxAge: 180 * 60 * 1000 }
}));


// Connect to the database
connectDB();

// Routes
app.use('/admin', adminRoutes);
app.use('/', userRoutes);

adminController.js

// adminController.js

const Admin = require('../models/admin');
const User = require('../models/users');
const bcrypt = require('bcrypt');

exports.showLogin = (req, res) => {
  if (req.session.admin) {
    res.redirect('/admin/view-users');
  } else {
    res.render('admin/login', { loginErr: req.session.adminLoginErr, isAdmin: true });
    req.session.adminLoginErr = false;
  }
};

exports.login = async (req, res) => {
  try {
    const { username, password } = req.body;
    const admin = await Admin.findOne({ username });

    if (admin) {
      const isMatch = await bcrypt.compare(password, admin.password);
      if (isMatch) {
        req.session.admin = { username: admin.username, loggedIn: true, isAdmin: true };
        res.redirect('/admin/view-users');
      } else {
        req.session.adminLoginErr = true;
        res.redirect('/admin');
      }
    } else {
      req.session.adminLoginErr = true;
      res.redirect('/admin');
    }
  } catch (err) {
    console.error('Error in admin login:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.viewUsers = async (req, res) => {
  try {
    const users = await User.find().lean();
    const successMessage = req.session.successMessage;
    req.session.successMessage = null;
    res.render('admin/view-users', { isAdmin: req.isAdmin, admin: req.session.admin, users, successMessage });
  } catch (err) {
    console.error('Error fetching users:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showAddUserForm = (req, res) => {
  res.render('admin/add-users', { isAdmin: req.isAdmin, admin: req.session.admin });
};

exports.addUser = async (req, res) => {
  try {
    const { username, email, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = new User({ username, email, password: hashedPassword });
    await newUser.save();
    req.session.successMessage = 'User added successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error adding user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showEditUserForm = async (req, res) => {
  try {
    const user = await User.findById(req.params.id).lean();
    res.render('admin/edit-user', { isAdmin: req.isAdmin, admin: req.session.admin, user });
  } catch (err) {
    console.error('Error fetching user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.editUser = async (req, res) => {
  try {
    const { username, email, password } = req.body;
    const updatedUser = { username, email };
    if (password) {
      updatedUser.password = await bcrypt.hash(password, 10);
    }
    await User.findByIdAndUpdate(req.params.id, updatedUser);
    req.session.successMessage = 'User updated successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error updating user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.searchUsers = async (req, res) => {
  try {
    const query = req.query.query;
    const users = await User.find({
      $or: [
        { username: { $regex: query, $options: 'i' } },
        { email: { $regex: query, $options: 'i' } }
      ]
    }).lean();

    res.render('admin/view-users', {
      isAdmin: req.isAdmin,
      admin: req.session.admin,
      users,
      successMessage: `Search results for '${query}'`
    });
  } catch (err) {
    console.error('Error searching users:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.deleteUser = async (req, res) => {
  try {
    await User.findByIdAndDelete(req.params.id);
    req.session.successMessage = 'User deleted successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error deleting user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.logout = (req, res) => {
  req.session.admin = null;
  res.redirect('/admin');
};

userController.js

// userController.js

const User = require('../models/users');
const bcrypt = require('bcrypt');

exports.index = (req, res) => {
    res.render('user/index', { user: req.session.user });
  };
  

exports.showLogin = (req, res) => {
  if (req.session.user) {
    res.redirect('/');
  } else {
    res.render('user/login', { loginErr: req.session.userLoginErr });
    req.session.userLoginErr = false;
  }
};

exports.login = async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });

    if (user) {
      const isMatch = await bcrypt.compare(password, user.password);
      if (isMatch) {
        req.session.user = { username: user.username, loggedIn: true };
        res.redirect('/');
      } else {
        req.session.userLoginErr = true;
        res.redirect('/login');
      }
    } else {
      req.session.userLoginErr = true;
      res.redirect('/login');
    }
  } catch (err) {
    console.error('Error in user login:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

exports.showSignup = (req, res) => {
  res.render('user/signup', { signupErr: req.session.signupErr });
  req.session.signupErr = null;
};

exports.signup = async (req, res) => {
    try {
      const { username, email, password } = req.body;
  
      // Validation: Check for empty or whitespace-only fields
      if (!username.trim() || !email.trim() || !password.trim()) {
        req.session.signupErr = 'All fields are required and cannot be empty.';
        return res.redirect('/signup');
      }
  
      // Additional Validation: Check if the username is valid
      const usernameRegex = /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9_.-]+$/;
      if (!usernameRegex.test(username)) {
        req.session.signupErr = 'Username must contain at least one alphanumeric character and can only contain letters, numbers, underscores, dots, and dashes.';
        return res.redirect('/signup');
      }
  
      // Check if the username or email already exists
      const existingUser = await User.findOne({ $or: [{ username }, { email }] });
      if (existingUser) {
        req.session.signupErr = 'Username or Email already exists.';
        return res.redirect('/signup');
      }
  
      const hashedPassword = await bcrypt.hash(password, 10);
      const newUser = new User({ username, email, password: hashedPassword });
      await newUser.save();
      req.session.user = { username: newUser.username, loggedIn: true };
      res.redirect('/');
    } catch (err) {
      console.error('Error in user signup:', err);
      res.status(500).json({ error: 'Internal Server Error' });
    }
  };

exports.logout = (req, res) => {
  req.session.user = null;
  res.redirect('/login');
};

解决方案

1. 添加用户会话验证中间件

问题根源是当前系统仅依赖session判断登录状态,未验证session对应的用户是否仍存在于数据库。需添加全局中间件,在每次用户请求时校验用户有效性:

在app.js的session配置之后、路由之前插入以下代码:

// 引入User模型
const User = require('./models/users');

// 验证用户会话有效性的中间件
app.use(async (req, res, next) => {
  if (req.session.user) {
    const user = await User.findOne({ username: req.session.user.username });
    if (!user) {
      // 用户已被删除,清除session并跳转登录页
      req.session.user = null;
      return res.redirect('/login');
    }
  }
  next();
});

2. 删除用户时清理对应所有会话

管理员删除用户时,除了删除数据库中的用户记录,还需清除该用户所有活跃session。MongoStore将session存储在sessions集合中,可通过查询集合中包含该用户信息的记录进行删除:

修改adminController.js中的deleteUser方法:

exports.deleteUser = async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      req.session.successMessage = 'User not found';
      return res.redirect('/admin/view-users');
    }
    // 删除用户记录
    await User.findByIdAndDelete(req.params.id);
    // 删除该用户所有活跃session
    await req.session.store.collection('sessions').deleteMany({
      'session.user.username': user.username
    });
    req.session.successMessage = 'User deleted successfully';
    res.redirect('/admin/view-users');
  } catch (err) {
    console.error('Error deleting user:', err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
};

3. 优化session存储结构(可选)

当前session仅存储username,建议同时存储用户_id,提升验证和清理session的效率:

修改userController.js中的login和signup方法:

// login方法
if (isMatch) {
  req.session.user = { 
    _id: user._id.toString(), 
    username: user.username, 
    loggedIn: true 
  };
  res.redirect('/');
}

// signup方法
req.session.user = { 
  _id: newUser._id.toString(), 
  username: newUser.username, 
  loggedIn: true 
};

对应调整验证中间件和session删除逻辑:

// 中间件修改为
const user = await User.findById(req.session.user._id);

// deleteUser中的session删除改为
await req.session.store.collection('sessions').deleteMany({
  'session.user._id': user._id.toString()
});

4. 前端实时检测(可选)

若需要更实时的体验,可在前端定期发送请求检查用户状态,例如每30秒请求一次验证接口,发现用户已被删除则自动跳转登录页。


内容的提问来源于stack exchange,提问作者prajitha k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:25:54