Nestjs入门系列-07-守卫

106 阅读3分钟

守卫

守卫的含义

守卫是根据选择的策略对身份进行验证,保护路由访问

Untitled.png

守卫有一个单独的责任。它们根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理。这通常称为授权。在传统的 Express应用程序中,通常由中间件处理授权(以及认证)。中间件是身份验证的良好选择,因为诸如 token验证或添加属性到 request对象上与特定路由(及其元数据)没有强关联。

但是,中间件,就其本质而言,是愚蠢的。它不知道在调用next()函数后,哪个处理程序将被执行。

另一方面,卫兵可以访问ExecutionContext(执行上下文)实例,因此知道下一步将执行什么。它们的设计很像异常过滤器、管道和拦截器,可以让你在请求/响应周期中的正确位置插入处理逻辑,而且是以声明的方式进行。这有助于保持你的代码的简洁性和声明性。

守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。

邂逅守卫

授权是守卫的一个很好的用例,因为只有当调用者(通常是经过身份验证的特定用户)具有足够的权限时,特定的路由才可用。

首先使用cli命令创建守卫

nest g guard guard --no-spec
// guard/guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Observable } from 'rxjs'

@Injectable()
// 守卫是一个实现CanActivate接口的一个可注入的class
export class GuardGuard implements CanActivate {
  // 这个class必须实现一个canActivate函数
  // 函数返回true,表示守卫通过,返回false,表示不守卫通过并抛出异常
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // 通过context可以获取到上下文
    // 我们就可以在这里通过进行进行判断(比如:是否携带了token)
    return true
  }
}

我们来做一个简单的例子

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

@Injectable()
export class GuardGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // 获取request对象
    const req = context.switchToHttp().getRequest<Request>()
    // 从request对象中解构出name,age
    const { name, age } = req.body
    // 如果两个都存在则通过验证,否则不通过验证
    return name && age ? true : false
  }
}
// app.controller.ts
import { Controller, Post, UseGuards } from '@nestjs/common'
import ConfigService from './config/config.service'
import { GuardGuard } from './guard/guard.guard'

@Controller()
// 使用守卫,可以传递多个守卫
// 我们可以在controller上使用,也可以是controller里的某个方法使用
@UseGuards(GuardGuard)
export class AppController {
  constructor(private readonly config: ConfigService) {}

  @Post()
  // @UseGuards(GuardGuard)
  getCats() {
    return 'Hello World'
  }
}

测试

Untitled 1.png

Untitled 2.png

全局守卫

我们也可以在主文件里使用全局守卫,全局守卫用于整个应用程序, 每个控制器和每个路由处理程序

// main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { GuardGuard } from './guard/guard.guard'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalGuards(new GuardGuard())
  await app.listen(3000)
}
bootstrap()

权限守卫

假如我们需要其中一些可能只对管理用户可用,而另一些则可以对所有人开放。我们如何以灵活和可重用的方式将角色与路由匹配起来?

Nest提供了通过 @SetMetadata装饰器将定制元数据附加到路由处理程序的能力。

@Post()
@UseGuards(GuardGuard)
// 第一个参数key
// 第二个参数value
@SetMetadata('roles', ['admin'])
getCats() {
  return 'Hello World'
}

虽然这样可以运行,但直接使用 @SetMetadata() 并不是一个好做法。相反,你应该创建你自己的装饰器(更加优雅)

这种方法更简洁、更易读,而且是强类型的。

@Post()
@Roles('admin')
getCats() {
  return 'Hello World'
}

为了访问我们自定义的元数据,我们需要在守卫里使用 Reflector帮助类

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

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

  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    // 通过key获取value
    const roles = this.reflector.get<string[]>('roles', context.getHandler())
    // value中包含admin,表示有此权限可以通过,否则无法通过
    return roles.includes('admin') ? true : false
  }
}

原文链接codertao.notion.site/5a2cc5c1e67…