NestJS如何根据认证用户角色返回差异化API响应结果
NestJS 基于用户角色返回差异化API结果的实现方案
我曾遇到如下问题:如何基于用户角色返回不同的API结果,以GET /users接口为例,若发起请求的用户角色为MEMBER,我希望返回的内容比ADMIN角色用户获取的更精简。
我是NestJS新手,该方案可能对老手来说十分基础,但我搜遍全网都没找到对应答案,花了一整天时间才摸索出来,因此分享出来供大家参考。
实现的核心是创建自定义拦截器,随后在控制器中引入该拦截器即可,具体步骤如下:
- 创建自定义拦截器
- 用自定义拦截器装饰控制器中的GET、POST等处理函数
- 更新Entity对象,指定哪些角色可查看对应字段
注:我使用Passport和JWT令牌做身份认证,角色数据存在了令牌中,你也可以修改代码通过其他方式获取认证用户的角色数据。
1. 创建自定义拦截器
使用Nest CLI生成拦截器的基础代码,命令如下:
nest g interceptor common/interceptors/role-sanitize
该文件需要获取认证用户的角色,并过滤输出的数据对象,代码如下:
import { CallHandler, ClassSerializerInterceptor, ExecutionContext, Injectable, NestInterceptor, UseInterceptors } from "@nestjs/common"; import { Observable } from "rxjs"; import { map } from 'rxjs/operators'; import { JwtService } from "@nestjs/jwt"; import { classToPlain } from "class-transformer"; @Injectable() @UseInterceptors(ClassSerializerInterceptor) export class RoleSanitizeInterceptor implements NestInterceptor { constructor( private readonly jwtService: JwtService ) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { interface token { email: string; id: number; role: string; } let role = 'MEMBER' const req = context.switchToHttp().getRequest() const token = <string>req.headers.authorization if(token){ let user = <token>this.jwtService.decode(<string>token.replace('Bearer ', '')) role = user.role } return next.handle().pipe(map(data => { return classToPlain(data, {groups: [role]}) })) } }
2. 用自定义拦截器装饰控制器函数(GET、POST等)
完成自定义拦截器创建后,你就可以用它装饰接口端点,示例如下:
import { RoleSanitizeInterceptor } from "../common/interceptors/role-sanitize.interceptor"; .... @UseGuards(JwtAuthGuard) @UseInterceptors(RoleSanitizeInterceptor) @Get(':id') async findOne(@Param('id') id: number): Promise<User> { let record = await this.usersService.findOne({ where: [{id: id}] }) if(!record){ throw new NotFoundException(`User not found`); } return record }
3. 更新Entity对象,指定可查看对应数据的角色
以下是我的Entity文件示例,你可以看到我使用@Expose注解,根据角色控制字段是否暴露:
import { Column, Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, BeforeInsert, BeforeUpdate } from "typeorm"; import { Exclude, Expose } from 'class-transformer'; import { hash } from 'bcrypt' import { UserMembership, UserRole } from "../dto/create-user.dto"; @Entity('users') export class User { @PrimaryGeneratedColumn() id: number @Column() first_name: string @Column() last_name: string @Column({ unique: true }) email: string @Column() @Exclude() password: string @Column({default: true}) @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) password_reset: boolean @Column({default: 'MEMBER'}) role: UserRole @Column({default: 'FREE'}) membership: UserMembership @Column({default: false}) @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) paid: boolean @Column({default: false}) @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) auto_renew: boolean @CreateDateColumn() @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) created_at: Date @UpdateDateColumn() @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) updated_at: Date @Column({nullable: true, default: null}) @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) last_login: Date @Column({nullable: true, default: null}) renew_at: Date @Column({nullable: true, default: null}) @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) canceled_at: Date @DeleteDateColumn() @Expose({ groups: ["ADMIN", "SUPERADMIN"] }) deleted_at: Date @BeforeInsert() @BeforeUpdate() async hashPassword() { if(this.password){ this.password = await hash(this.password, 10); } } constructor(partial: Partial<User>) { Object.assign(this, partial); } }
效果验证
MEMBER角色返回结果
{ "id": 1, "first_name": "Jon", "last_name": "Doe", "email": "jon.doe@email.com", "role": "MEMBER", "membership": "FREE", "renew_at": null }
ADMIN角色返回结果
{ "id": 1, "first_name": "Jon", "last_name": "Doe", "email": "jon.doe@email.com", "password_reset": true, "role": "ADMIN", "membership": "FREE", "paid": false, "auto_renew": false, "created_at": "2021-11-16T18:42:31.699Z", "updated_at": "2021-11-17T21:26:26.823Z", "last_login": null, "renew_at": null, "canceled_at": null, "deleted_at": null }
希望该方案能帮到你,帮你节省开发时间。
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

