Nest.js 中关于守卫的使用

参考:

Nest.js:https://docs.nestjs.cn/

守卫:https://docs.nestjs.cn/9/guards

基本使用

在 Nest.js 中,守卫是一个使用 @Injectable() 装饰器的类,守卫应该实现 CanActivate 接口。

一个简单的例子如下:

// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request); // 在此处编写自己的校验逻辑,返回true表示可以访问,false为禁止访问
  }
}

深入使用

自定义元数据

在守卫的执行过程中,难免会需要根据不同的情况做不同的处理,此时就需要用到自定义元数据。

在 Nest.js 中,自定义元数据是非常方便的,可以使用 @SetMetadata() 装饰器将定制元数据。

// cats.controller.ts
@Post()
@SetMetadata('roles', ['admin']) // 在这里就将 元数据 附加了上去,key 为 roles ,value 为 ['admin'],至于怎么获取元数据,后面会说。
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

很显然的是,直接调用 @SetMetadata() 并不是一个很好的做法,更好的写法是创建自己的装饰器,例如:

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

之后只需要在要用到的地方调用即可。

// cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

使用元数据

在自定义了元数据之后,我们还需要拿到元数据的值才能使用,在这里可以使用 @nestjs/core 中提供的 Reflector 帮助类。

// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get<string[]>('roles', context.getHandler()); // 这一步就能拿到元数据了,需要注意的是,context.getHandler() 只能获取到 方法 上的元数据,无法获取 类 上的元数据。
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

元数据扩展

更多相关内容可参考:应用上下文

在上一节中,我们通过 context.getHandler() 来获取元数据,但这里获取的元数据只能是 方法 的,无法获取到 的,那么要怎么获取到 的元数据呢?

答案是通过 context.getClass(),即:

const roles = this.reflector.get<string[]>('roles', context.getClass()); 

那么接下来问题来了,如果要先从 获取元数据,再从 方法 获取元数据,要怎么办呢?

Nest.js 也提供了相应的方案。

// cats.controller.ts
@Roles('user')
@Controller('cats')
export class CatsController {
  @Post()
  @Roles('admin')
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
}


// 如果是前者覆盖后者,则使用 getAllAndOverride(),此时将生成包含 ['admin']的 roles
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
  context.getHandler(),
  context.getClass(),
]);

// 如果是两者合并,则使用 getAllAndMerge(),此时将生成包含 ['user', 'admin']的roles
const roles = this.reflector.getAllAndOverride<string[]>('roles', [
  context.getHandler(),
  context.getClass(),
]);

总结

通过 @SetMetadata() 设置元数据,再通过 Reflector 获取元数据,我们就可以非常方便的在守卫、拦截器、管道或者其他 Nest.js 组件中使用元数据,极大的简化了装饰器的使用。