守卫是一个使用 @Injectable() 装饰器的类。 守卫应该实现 CanActivate 接口。守卫跟中间件很像,但它有个单一职责,它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。这通常称为授权。在传统的 Express 应用程序中,通常由中间件处理授权。中间件是身份验证的良好选择。到目前为止,访问限制逻辑大多在中间件内。这样很好,因为诸如 token 验证或将 request 对象附加属性与特定路由没有强关联。
中间件不知道调用 next() 函数后会执行哪个处理程序。另一方面,警卫可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,目的是让您在请求/响应周期的正确位置插入处理逻辑,并以声明的方式进行插入。这有助于保持代码的简洁和声明性。注意:守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
**

1.自定义守卫

自定义守卫必须满足以下2两个条件:
(1).使用@Injectable()装饰器装饰。
(2).自定义守卫需要实现CanActivate接口,并重写CanActivate接口的canActivate()函数,canActivate()函数返回一个布尔值,指示是否允许当前请求,异步地返回响应(通过 Promise 或 Observable)。Nest使用返回值来控制下一个行为:

  • 如果返回 true, 将处理用户调用。
  • 如果返回 false, 则 Nest 将忽略当前处理的请求。

canActivate()接收单个参数ExecutionContext实例。ExecutionContext 继承自 ArgumentsHost ,ArgumentsHost 是传递给原始处理程序的参数的包装器,ExecutionContext 提供了更多功能,它扩展了 ArgumentsHost,但是也提供了有关当前执行过程的更多详细信息。

  1. //结构 ExecutionContext
  2. export interface ExecutionContext extends ArgumentsHost {
  3. getClass<T = any>(): Type<T>;
  4. getHandler(): Function;
  5. }

接下来我们将实现一个验证注解,此注解的作用是验证请求的请求头是否包含token字段,如果请求头不包含token字段且需要验证则抛出一个异常,否则正常处理请求。

  1. // src/common/guards/auth.guard.ts
  2. /**
  3. * 自定义守卫需要满足以下两个要求:
  4. * (1).使用@Injectable()装饰器装饰
  5. * (2).守卫类需实现CanActivate接口,并重写canActivate()函数,canActivate()返回一个布尔值来表示是否允许请求通过,
  6. * canActivate()函数接收单个参数ExecutionContext实例,ExecutionContext继承自ArgumentsHost,
  7. * ExecutionContext相比较ArgumentsHost提供了更多功能。我们可以通过ExecutionContext获取请求或响应对象。
  8. *
  9. * AuthGuard的作用是验证请求的请求头是否携带头,若请求携带请求头则允许请求到路由方法,否则将抛出错误,
  10. * 这里使用@Auth()装饰器来装饰器需要验证的路由方法,若装饰器了@Auth()则需要验证,否则不需要验证,
  11. * 这里是使用nest提供的反射器工具Reflector 用于判断类或方法上是否有装饰器,这是搭配SetMetadata使用的
  12. */
  13. import {
  14. CanActivate,
  15. ExecutionContext,
  16. Injectable,
  17. UnauthorizedException,
  18. } from '@nestjs/common';
  19. import { Observable } from 'rxjs';
  20. import { Reflector } from '@nestjs/core';
  21. import { Request } from 'express';
  22. @Injectable()
  23. export class AuthGuard implements CanActivate {
  24. //通过构造函数将Reflector注入进来
  25. constructor(private readonly reflector: Reflector) {}
  26. canActivate(
  27. context: ExecutionContext,
  28. ): boolean | Promise<boolean> | Observable<boolean> {
  29. const auth = this.reflector.get<string[]>(
  30. 'auth',
  31. context.getClass() || context.getHandler(),
  32. );
  33. //如果未使用@Auth() 或 @Auth(false) 则不需要验证正常通过
  34. if (!auth) return true;
  35. //否则验证请求对象请求头是否含有token
  36. const request: Request = context.switchToHttp().getRequest();
  37. const header = request.headers;
  38. //判断请求头是否有token,若token为空则抛出错误
  39. if (!header.token) {
  40. throw new UnauthorizedException('');
  41. }
  42. return true;
  43. }
  44. }

然后自定义一个@Auth()装饰器,使用SetMetadata描述装饰器的元数据。

  1. // src/common/decorators/auth.decorator.ts
  2. /**
  3. * 授权装饰器
  4. */
  5. import { SetMetadata } from '@nestjs/common';
  6. export const Auth = (isAuth: boolean = true) => SetMetadata('auth', isAuth);

使用守卫也很简单,使用@UseGuards()装饰器局部使用或者在main.ts中注册全局守卫。

  1. import { Controller, Get, UseGuards } from '@nestjs/common';
  2. import { AppService } from './app.service';
  3. import { AuthGuard } from './common/guards/auth.guard';
  4. import { Auth } from './common/decorators/auth.decorator';
  5. @Controller()
  6. @Auth()
  7. export class AppController {
  8. constructor(private readonly appService: AppService) {}
  9. @Get('/hello')
  10. @UseGuards(AuthGuard)
  11. hello(): string {
  12. return 'hello';
  13. }
  14. }

请求头传入token效果图:
aaa.jpg

请求头未传入token效果图:
bbb.jpg