ExpressJS+MongoDB中管理员创建用户后Token持久化问题
问题描述
我正在使用ExpressJS和MongoDB开发API。用户可登录,仅已登录的管理员用户能访问/register接口注册新用户,即仅管理员可在应用内创建新员工及其关联用户。
登录功能正常,可获取当前登录用户;非管理员访问注册接口会收到对应错误提示,管理员可成功创建新用户。但问题是:管理员创建新用户后,自身已登录的Token会丢失,调用/api/auth/me接口时会返回"Invalid token"错误。如何实现Token持久化,让管理员无需重新登录即可创建多个用户?
现有代码
Auth控制器(auth.js)
exports.register = asyncHandler(async (req, res, next) => { const { email, password, name, lastName } = req.body; const user = await User.create({ email, password }); const employee = await Employee.create({ name, lastName, user: user._id }) res.status(200).json({ success: true, user: user, employee: employee }) }); // @desc Loginuser // @route POST /api/v1/auth/login // @access Public exports.login = asyncHandler(async (req, res, next) => { const { email, password } = req.body; // Validate emil & password if (!email || !password) { return next(new ErrorResponse('Please provide an email and password', 400)); } // Check for user const user = await User.findOne({ email }).select('+password'); if (!user) { return next(new ErrorResponse('Invalid credentials', 401)); } // Check if password matches const isMatch = await user.matchPassword(password); if (!isMatch) { return next(new ErrorResponse('Invalid credentials', 401)); } sendTokenResponse(user, 200, res); }); // Get token from model, create cookie and send response const sendTokenResponse = (user, statusCode, res) => { // Create token const token = user.getSignedJwtToken(); const options = { expires: new Date( Date.now() + process.env.JWT_COOKIE_EXPIRE * 24 * 60 * 60 * 1000 ), httpOnly: true }; if (process.env.NODE_ENV === 'production') { options.secure = true; } res .status(statusCode) .cookie('token', token, options) .json({ success: true, token }); }; // @desc Get current logged in user // @route POST /api/auth/me // @access Private exports.getMe = asyncHandler(async (req, res, next) => { const user = await User.findById(req.user._id); res.status(200).json({ success: true, data: user }); })
中间件函数
const jwt = require('jsonwebtoken'); const asyncHandler = require('./async'); const ErrorResponse = require('../utils/errorResponse'); const User = require('../models/User'); // Protect Routes exports.protect = asyncHandler(async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } /* else if (req.cookies.token) { token = req.cookies.token; } */ // Make sure token exists if (!token) { return next(new ErrorResponse('Not authorized to access this route', 401)); } try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // Check if decoded has id property if (!decoded.id) { return next(new ErrorResponse('Invalid token', 401)); } req.user = await User.findById(decoded.id); if (!req.user) { return next(new ErrorResponse('User not found', 404)); } next(); } catch (error) { return next(new ErrorResponse('Invalid token', 401)); } }); //Grant access to specific roles exports.authorize = (...roles) => { return (req, res, next) => { if(!roles.includes(req.user.role)) { return next(new ErrorResponse(`User role ${req.user.role} is not authorized to access this route`, 403)); } next(); } }
解决方案
JWT是无状态令牌,只要签名有效且未过期,服务器就会认可。出现创建用户后Token失效的情况,通常是客户端Token丢失/错误替换或服务器验证逻辑存在漏洞,以下是针对性的解决步骤:
1. 修复服务器端Token验证逻辑
当前protect中间件注释了从Cookie读取Token的逻辑,如果管理员通过Cookie存储Token(login接口会自动设置Cookie),服务器将无法读取到Token。取消该注释,兼容Authorization头和Cookie两种Token传递方式:
// Protect Routes exports.protect = asyncHandler(async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } else if (req.cookies.token) { token = req.cookies.token; } // Make sure token exists if (!token) { return next(new ErrorResponse('Not authorized to access this route', 401)); } try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // Check if decoded has id property if (!decoded.id) { return next(new ErrorResponse('Invalid token', 401)); } req.user = await User.findById(decoded.id); if (!req.user) { return next(new ErrorResponse('User not found', 404)); } next(); } catch (error) { // 打印错误信息方便排查 console.error('JWT验证错误:', error); return next(new ErrorResponse('Invalid token', 401)); } });
2. 避免客户端Token被意外覆盖
- 登录后,客户端应将Token存储在
localStorage/sessionStorage或保留Cookie,不要在处理/register响应时清空或替换Token。 - 修改
register接口的响应,只返回新用户的非敏感信息,避免客户端混淆:
exports.register = asyncHandler(async (req, res, next) => { const { email, password, name, lastName } = req.body; const user = await User.create({ email, password }); const employee = await Employee.create({ name, lastName, user: user._id }) // 仅返回非敏感信息,避免客户端误将新用户数据当成管理员Token res.status(201).json({ success: true, user: { id: user._id, email: user.email }, employee: employee }) });
3. 确保Token有效期合理
检查环境变量JWT_EXPIRE和JWT_COOKIE_EXPIRE的值,设置足够长的有效期(例如30d表示30天,24h表示24小时),避免Token在短时间内过期。
4. 排查管理员用户文档状态
创建新用户后,确认管理员的用户文档未被意外删除或修改,可在protect中间件中打印req.user信息,验证用户是否存在且ID与Token中的decoded.id一致。
内容的提问来源于stack exchange,提问作者tukitooo1998
相关产品推荐
相关产品推荐

