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

NestJS认证模块ES6解构Mongoose对象无法移除password问题

问题原因

和Promise机制没有任何关系,核心原因是Mongoose查询返回的user不是普通JavaScript对象,而是Mongoose封装的Document实例。
你看到的$__、$isNew、_doc都是Document实例自身的内部属性,平时写代码能直接通过user.password、user.email访问到业务字段,是因为Mongoose在Document原型层做了getter/setter代理,真实的业务数据实际存在内部的_doc字段上。
ES6解构只会遍历对象自身的可枚举属性,不会读取原型链上通过代理挂载的属性,所以你解构出来的全是Mongoose的内部状态字段,真正的用户数据还留在_doc里,自然也没法通过解构移除password——你从一开始就没解构到这个被代理的字段。
官方示例用的是内存中存储的普通用户对象数组,不存在Mongoose Document的代理逻辑,所以解构写法在示例里可以正常运行,你直接套用到Mongoose场景就会出现这个问题。

是否必须移除密码字段

必须处理,不要有侥幸心理:

  • 哪怕当前接口最终只返回带用户_id的JWT,不直接响应用户对象,只要带密码哈希的Document实例在业务链路中传递,后续代码迭代时很容易因为疏忽把整个对象序列化返回,直接造成敏感数据泄露
  • 即便是bcrypt这类高强度哈希的密码,泄露后依然存在被暴力碰撞的风险,本身就不该脱离认证环节流转
  • 敏感数据按需返回是基本的安全原则,没必要在非必要的环节携带密码字段。
修复方案

不要直接访问内部的_doc字段,这是Mongoose的私有属性,版本迭代可能变更,业务代码依赖私有属性会留下兼容隐患,推荐按优先级选以下正规方案:

  • 最高优先级:查询时直接返回普通JS对象
    在Mongoose查询后链式调用.lean()方法,返回结果就是纯普通JS对象,没有Mongoose的内部属性和代理逻辑,解构、属性删除等操作都和普通对象行为一致,同时查询性能比返回Document实例高3-5倍,是最推荐的写法:
    async findOneByEmail(email: string): Promise<User | undefined> {
      return await this.userModel.findOne({ email: email }).lean().exec();
    }
    
    注意如果是validateUser这类需要比对密码的场景,先正常查询带密码的用户,比对通过后,对lean返回的普通对象直接解构或者delete掉password字段即可。
  • 根源规避:查询层面直接排除敏感字段
    对于不需要密码的查询场景,直接在查询时过滤掉password字段,从根源避免敏感字段被查出:
    // 第二个参数为字段投影,-password表示排除password字段
    return await this.userModel.findOne({ email: email }, '-password').lean().exec();
    
    也可以在Schema定义时给password字段加select: false配置,默认所有查询都不会返回password字段,需要做密码比对的时候再显式通过.select('+password')查出来,一劳永逸。
  • 兼容场景:手动转换Document为普通对象
    如果某些场景必须使用Mongoose Document实例,可以在做属性操作前调用实例自带的.toObject()或.toJSON()方法,转为普通JS对象后再做解构、字段删除操作:
    const user = await this.usersService.findOne(username);
    if (user && user.password === pass) {
      const plainUser = user.toObject();
      const { password, ...result } = plainUser;
      return result;
    }
    
属于框架bug还是使用问题

既不是NestJS的bug,也不是Mongoose的bug,属于典型的ODM使用不当:
官方示例使用内存模拟数据,所有用户对象都是原生普通JS对象,解构逻辑可以正常运行;你在切换为Mongoose作为存储层时,没有处理Mongoose Document和原生JS对象的行为差异,直接照搬了普通对象的写法才触发问题,是使用Mongoose开发时非常常见的入门踩坑点。

内容的提问来源于stack exchange,提问作者Liam Clark Gutiérrez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 06:24:31