1

我有以下问题“如何根据用户角色以不同方式返回 API 结果”,例如使用一个端点 GET /users

如果请求数据的用户是“成员”,我想显示 UI 将返回到“管理员”的精简版本。

我花了一整天的时间,但我终于弄明白了。请注意,我是 NestJS 的新手,所以这可能很明显,但我在互联网上到处搜索,找不到答案,所以我在这里为其他人发布。

这里的关键是创建一个自定义拦截器,然后您可以将其包含在您的控制器中。

  1. 创建您的自定义拦截器
  2. 使用您的自定义拦截器装饰您的控制器功能(GET、POST 等)
  3. 更新您的实体对象以指定哪些角色可以查看数据

我正在使用 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. 更新您的实体对象以指定哪些角色可以查看数据

这是我的实体文件的示例,您会注意到我正在使用 @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);
}
}

这是我调用端点时的结果,首先是普通的“成员”:

{
    "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
}

我希望这有帮助!我花了一整天的时间来解决这个问题,所以希望它能为你节省一些时间。

4

1 回答 1

0

而不是通过关联每个实体中的角色来执行此操作,我建议将角色存储在另一个表中,并带有他们的 ID,并为角色映射到用户创建一个关联表,并将其添加到 JwTAuthGuard,每当用户登录时。auth guards 搜索用户可以访问的角色,从而限制访问。

那将是一种更具可扩展性的方法

于 2021-11-18T17:25:38.317 回答