如何从控制器 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
中是固定的。将动态组放入classToClass
或plainToClass
。
@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']);
函数体内的classToClass
和plainToClass
进行实际过滤,而@SerializeOptions
确保过滤后的数据将正确返回。
【讨论】:
以上是关于如何从控制器 JSON 返回的实体字段中排除。 NestJS + Typeorm的主要内容,如果未能解决你的问题,请参考以下文章
使用WisdomTool RESTClient进行自动化测试,如何取消对返回的body内容的校验?对排除的JSON属性字段不做校验?
如何隐藏/排除graphene_django中请求实体的某些外键字段?
Spring Data REST 默认对 JSON 隐藏技术实体字段(@Version、@Id)。如何将它们作为通常的属性返回?