繁体   English   中英

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

[英]How to exclude entity field from returned by controller JSON. NestJS + Typeorm

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

这个问题上提供的解决方案对我或 NestJS 都不起作用。 如果需要,我可以发布我的代码。 还有其他想法或解决方案吗? 谢谢。

我建议创建一个利用类转换器库的拦截器:

@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;
}

您可以像这样覆盖模型的 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 响应中排除,但将在模型实例中可用。 我喜欢这个解决方案,因为它将所有模型配置保留在实体中。

作为对卡米尔回答的补充:

您现在可以使用内置的ClassSerializerInterceptor而不是创建自己的拦截器,请参阅序列化文档

@UseInterceptors(ClassSerializerInterceptor)

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

import { Exclude } from 'class-transformer';

export class User {
    /** other properties */    

    @Exclude()
    password: string;
}

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

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

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

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

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

@Column({ select: false })
password: string

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

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

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

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

@Column({ select: false })
password: string

可以在此处阅读有关隐藏列的信息

对我有用的只是

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

toJSON() { return classToPlain(this); }

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

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

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

这个问题上提供的解决方案对我或 NestJS 不起作用。 如果需要,我可以发布我的代码。 任何其他想法或解决方案? 谢谢。

这已经是一个老话题了,但我仍然想分享我的解决方案,也许它会对某人有所帮助。 我使用 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());

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

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

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;
}

资料来源: Stackoverflow 答案

除了 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确保过滤后的数据将正确返回。

@Injectable()
export default class TransformInterceptor implements NestInterceptor {
  intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<AnyObject> {
    return next.handle().pipe(map((args) => instanceToPlain(args)));
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM