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
相关产品推荐
相关产品推荐

