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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:29:53