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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 14:15:03