如何从控制器 JSON 返回的实体字段中排除。 NestJS + Typeorm

Posted

技术标签:

【中文标题】如何从控制器 JSON 返回的实体字段中排除。 NestJS + Typeorm【英文标题】:How to exclude entity field from returned by controller JSON. NestJS + Typeorm 【发布时间】:2018-10-25 20:55:23 【问题描述】:

我想从返回的 JSON 中排除密码字段。 我正在使用 NestJS 和 Typeorm。

this question 上提供的解决方案不适用于我或 NestJS。如果需要,我可以发布我的代码。 还有其他想法或解决方案吗?谢谢。

【问题讨论】:

【参考方案1】:

你可以使用包https://github.com/typestack/class-transformer

您可以使用装饰器排除属性,也可以使用组排除属性。

【讨论】:

Silva,您能否指出对“使用组排除属性”的引用?我找不到它。谢谢【参考方案2】:

我建议创建一个利用 class-transformer 库的拦截器:

@Injectable()
export class TransformInterceptor implements NestInterceptor 
  intercept(
    context: ExecutionContext,
    call$: Observable<any>,
  ): Observable<any> 
    return call$.pipe(map(data => classToPlain(data)));
  

然后,只需使用@Exclude() 装饰器排除属性,例如:

import  Exclude  from 'class-transformer';

export class User 
    id: number;
    email: string;

    @Exclude()
    password: string;

【讨论】:

这是@kamil-myśliwiec :-D 创作者的最佳答案。 谢谢你,@kamil-myśliwiec。我会尽快试试这个。 此外,您可以在数据库查询期间(在 ORM 或 SQL 查询中)排除字段。 TypeORM 示例 - github.com/typeorm/typeorm/issues/535 . 我不明白如何在嵌套中实现 TransformInterceptor 类 :( ? 如果我没记错这里可以使用内置的ClassSerializerInterceptor【参考方案3】:

作为Kamil's answer的补充:

您现在可以使用内置的ClassSerializerInterceptor,而不是创建自己的拦截器,参见serialization docs。

@UseInterceptors(ClassSerializerInterceptor)

您可以在控制器类或其各个方法上使用它。这种方法返回的每个实体都将使用 class-transformer 进行转换,因此将 @Exclude 注释考虑在内:

import  Exclude  from 'class-transformer';

export class User 
    /** other properties */    

    @Exclude()
    password: string;


您可以通过在控制器或其方法上定义 @SerializeOptions() 来自定义其行为:

@SerializeOptions(
  excludePrefixes: ['_'],
  groups: ['admin']
)

例如,仅向某些用户公开某些字段:

@Expose( groups: ["admin"] )
adminInfo: string;

【讨论】:

你不会碰巧知道内置类是否支持定义组,即@Expose( groups: ["user", "admin"] )?我一直在努力弄清楚您将如何指示应该从控制器中使用哪个组。从class-transformer 文档我可以看到它支持classToPlain(user, groups: ["user"] ),但我有点困惑nest 在哪里调用它,以及是否可以通过这些选项。 只是一个新手问题:您在哪里定义“组”,即 class-transformer 如何知道用户所在的组?我们应该先定义组模型并让用户有多个组有点关系吗?即用户将拥有 user.groups = ['editor', user']?那么我们如何将当前请求的用户传递给拦截器,以便它相应地显示/隐藏字段呢? @AhmetCetin SerializeOptions 仅在 groups 是每个端点的静态时才有效。如果要根据请求动态确定groups,则必须为此实现自定义拦截器。 查看这个答案,其中组是动态确定的以进行验证,您需要做类似的事情,但要进行序列化(在您的情况下是拦截器而不是管道):***.com/a/54057206/4694994 @ddsultan 请对此提出一个新问题;如果没有更多详细信息,就无法在 cmets 中调试您的问题:一个最小的示例,以便可以重现问题以及您的预期行为或输出。谢谢:-)【参考方案4】:

你可以像这样覆盖模型的 toJSON 方法。

@Entity()
export class User extends BaseAbstractEntity implements IUser 
  static passwordMinLength: number = 7;

  @ApiModelProperty( example: faker.internet.email() )
  @IsEmail()
  @Column( unique: true )
  email: string;

  @IsOptional()
  @IsString()
  @MinLength(User.passwordMinLength)
  @Exclude( toPlainOnly: true )
  @Column( select: false )
  password: string;

  @IsOptional()
  @IsString()
  @Exclude( toPlainOnly: true )
  @Column( select: false )
  passwordSalt: string;

  toJSON() 
    return classToPlain(this);
  

  validatePassword(password: string) 
    if (!this.password || !this.passwordSalt) 
      return false;
    
    return comparedToHashed(password, this.password, this.passwordSalt);
  

通过使用 plainToClass 的类转换器方法和 @Exclude( toPlainOnly: true ),密码将从 JSON 响应中排除,但将在模型实例中可用.我喜欢这个解决方案,因为它将所有模型配置保留在实体中。

【讨论】:

有没有关于 toJSON() 的文档? toJSON 方法是可以在任何 javascript 类中重写的方法。它定义了如何将类转换为常规对象。调用 JSON.stringify 时,会调用该类的 toJSON 方法。 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 见描述部分。 只有添加 @Exclude( toPlainOnly: true ) 才是我想要的【参考方案5】:
@Column( select: false )
password: string

可以阅读关于隐藏列here

【讨论】:

请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助,质量更高,更有可能吸引投票。 当然。我认为这是不言自明的。【参考方案6】:

这个帖子中有很多好的答案。为了建立上面apun的答案,我认为以下方法最不可能意外泄漏密码字段:

@Column( select: false )
password: string

如果实体默认不选择该字段,并且只能显式查询(例如,如果使用查询生成器,则通过addSelect()),我认为某处出现滑倒的可能性要小得多,并且更少依赖框架的“魔力”(最终是class-transformer 库)来确保安全性。实际上,在许多项目中,您唯一明确选择的地方就是您检查凭据的地方。

这种方法还可以帮助防止密码哈希意外泄漏到日志条目等中,这是一个尚未提及的考虑因素。知道它不包含敏感信息,尤其是如果它最终可能在某个日志条目中序列化,那么在用户对象周围折腾感觉更安全。

总而言之,NestJS 的记录方法是使用 @Exclude() 装饰器,并且接受的答案来自项目的创始人。

我肯定经常使用 Exclude() 装饰器,但不一定用于密码或盐字段。

【讨论】:

【参考方案7】:

这已经是一个老话题了,但我仍然想分享我的解决方案,也许它会对某人有所帮助。我使用 Express,但我的示例可能也适合这种情况。

因此,在您的实体类中,您只需定义一个额外的静态 removePassword() 方法,该方法接收实体本身的实例,然后您发送一个由该方法创建的对象,而不是您从 DB 获得的原始实体对象:

import  Entity, Column, PrimaryGeneratedColumn  from 'typeorm';

@Entity( name: 'users' )
export class User 
  @PrimaryGeneratedColumn('uuid')
  id: string | undefined;

  @Column( type: 'varchar', length: 100, unique: true )
  email: string | undefined;

  @Column( type: 'text' )
  password: string | undefined;

  static removePassword(userObj: User) 
    return Object.fromEntries(
      Object.entries(userObj).filter(([key, val]) => key !== 'password')
    );
  

这基本上就像在数组上调用filter() 方法,但稍微复杂一些:您从条目数组中创建一个新对象(您最终将发送的对象),该对象是通过从原始条目中过滤掉密码条目而产生的数组(使用这种精确的filter() 方法)。

在你的路由处理程序中,虽然你总是会做这样的事情:

import  Router, Request, Response, NextFunction  from 'express';
import  User  from '../../entity/User';
import  getRepository  from 'typeorm';

const router = Router();

router.post(
  '/api/users/signin',
  (req: Request, res: Response, next: NextFunction) => 
    const  email  = req.body;

    getRepository(User)
      .findOne( email )
      .then(user =>
        user ? res.send(User.removePassword(user)) : res.send('No such user:(')
      )
      .catch(err => next(new Error(err.message)));
  
);

export  router as signinRouter ;

您也可以使用常规方法:

withoutPassword() 
  return Object.fromEntries(
    Object.entries(this).filter(([key, val]) => key !== 'password')
  );

在你的路由处理程序中:

res.send(user.withoutPassword());

【讨论】:

【参考方案8】:

为了避免任何背痛和头痛, 我建议使用 plainToClass 来获得完整的 mongoose/class-transform 兼容性,并避免必须进行自定义覆盖来克服这个问题。

例如,将其添加到您的服务中:

async validateUser(email: string, password: string): Promise<UserWithoutPassword | null> 
    const user = await this.usersService.findOne( email );

    if (user && await compare(password, user.password))
    
        return plainToClass(UserWithoutPassword, user.toObject());
    

    return null;


来源:*** answer

【讨论】:

【参考方案9】:

对我有用的只是

使用 @Exclude 注释字段并像这样覆盖 toJSON 模型方法

toJSON() return classToPlain(this);

【讨论】:

【参考方案10】:

除了this answer for Nestjs。

如何以动态方式使用类转换器组,因为这些组在函数装饰器@SerializeOptions 中是固定的。将动态组放入classToClassplainToClass

@Post()
@SerializeOptions(
  groups: ['admin','user'],
)
async create(
  @Body() createProjectDto: CreateProjectDto,
) 
  const data = await this.projectsService.create(createProjectDto);
  return classToClass(data,  groups: ['admin','DYNAMIC_GROUP'] );
  //Or you can return
  //return plainToClass(Project, plainObject, groups: ['admin','DYNAMIC_GROUP']);

函数体内的classToClassplainToClass进行实际过滤,而@SerializeOptions确保过滤后的数据将正确返回。

【讨论】:

以上是关于如何从控制器 JSON 返回的实体字段中排除。 NestJS + Typeorm的主要内容,如果未能解决你的问题,请参考以下文章

使用WisdomTool RESTClient进行自动化测试,如何取消对返回的body内容的校验?对排除的JSON属性字段不做校验?

如何隐藏/排除graphene_django中请求实体的某些外键字段?

Spring Data REST 默认对 JSON 隐藏技术实体字段(@Version、@Id)。如何将它们作为通常的属性返回?

带有@ToString.Exclude 的Lombok 排除字段不起作用

通过Gson过滤多余的字段

Spring MVC灵活控制返回json的值(自定义过滤字段)