守卫
守卫
守卫是一个用 @Injectable() 装饰器注释的类,它实现了 CanActivate 接口。

守卫有单一的责任。它们根据运行时存在的某些条件(如权限、角色、ACL 等)确定给定请求是否将由路由处理程序处理。这通常称为授权。授权(及其通常与之合作的身份验证)通常由传统 Express 应用中的 中间件 处理。中间件是身份验证的不错选择,因为诸如令牌验证和将属性附加到 request 对象之类的事情与特定路由上下文(及其元数据)没有紧密联系。
但是中间件,就其本质而言,是愚蠢的。它不知道调用 next() 函数后将执行哪个处理程序。另一方面,Guards 可以访问 ExecutionContext 实例,因此确切地知道接下来要执行什么。它们的设计与异常过滤器、管道和拦截器非常相似,可让你在请求/响应周期的正确位置插入处理逻辑,并以声明方式进行。这有助于使你的代码保持干爽和声明式。
注意
防护在所有中间件之后、任何拦截器或管道之前执行。
授权守卫
如前所述,授权对于 Guards 来说是一个很好的用例,因为只有当调用者(通常是特定的经过身份验证的用户)拥有足够的权限时,特定的路由才应该可用。我们现在要构建的 AuthGuard 假设一个经过身份验证的用户(因此,一个令牌附加到请求标头)。它将提取并验证令牌,并使用提取的信息来确定请求是否可以继续。
// 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);
}
}
function validateRequest(request: any): boolean | Promise<boolean> | Observable<boolean> {
// 这里处理你的验证逻辑
// 例如,检查请求头、查询参数、请求体等
// 返回 true 表示验证通过,返回 false 表示验证失败
throw new Error('Function not implemented.');
}validateRequest() 函数内的逻辑可以根据需要简单或复杂。这个例子的要点是展示守卫如何适应请求/响应周期。
每个守卫都必须实现一个 canActivate() 函数。此函数应返回一个布尔值,指示是否允许当前请求。它可以同步或异步(通过 Promise 或 Observable)返回响应。Nest 使用返回值来控制下一步的动作:
- 如果它返回 true,请求将被处理。
- 如果它返回 false,Nest 将拒绝该请求
执行上下文
canActivate() 函数采用单个参数,即 ExecutionContext 实例。ExecutionContext 继承自 ArgumentsHost。我们之前在异常过滤器章节中看到了 ArgumentsHost。在上面的示例中,我们只是使用我们之前在 ArgumentsHost 上定义的相同辅助方法来获取对 Request 对象的引用。你可以参考 异常过滤器 章的参数主机部分,了解有关此主题的更多信息。
通过扩展 ArgumentsHost``,ExecutionContext 还添加了几个新的辅助方法,这些方法提供有关当前执行过程的更多详细信息。这些细节有助于构建更通用的守卫,这些守卫可以在广泛的控制器、方法和执行上下文中工作。了解有关 ExecutionContext此处 的更多信息。
基于角色的身份验证
让我们构建一个功能更强大的守卫,只允许具有特定角色的用户访问。我们将从一个基本的守卫模板开始,并在接下来的部分中构建它。目前,它允许所有请求继续:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log('被访问了,请求将被处理');
return true;
}
}绑定守卫
与管道和异常过滤器一样,防护可以是控制器范围、方法范围或全局作用域。下面,我们使用 @UseGuards() 装饰器设置了一个控制器作用域的守卫。这个装饰器可以接受一个参数,或者一个逗号分隔的参数列表。这使你可以通过一个声明轻松应用一组适当的保护。
// app.controller.ts
import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { RolesGuard } from './auth/auth.guard';
@Controller()
@UseGuards(RolesGuard)
export class AppController {
constructor(private readonly appService: AppService) {}
@Get(':id')
async getHello(@Param('id') id:number) {
return this.appService.getHello(id);
}
}// app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(id): number {
return id;
}
}执行结果如下:

调整下守卫,让它被拒绝。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log('被访问了,将拒绝该请求');
return false;
}
}执行结果如下:

注意:当路径错误的时候,守卫不会被执行。
提示
@UseGuards() 装饰器是从 @nestjs/common 包导入的。
上面,我们传递了 RolesGuard 类(而不是实例),将实例化的责任留给框架并启用依赖注入。与管道和异常过滤器一样,我们也可以传递一个就地实例:
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}上面的构造将守卫附加到此控制器声明的每个处理程序。如果我们希望守卫仅应用于单个方法,我们可以在方法级别应用 @UseGuards() 装饰器
全局守卫
为了设置全局守卫,使用 Nest 应用实例的 useGlobalGuards() 方法
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());提示
对于混合应用,useGlobalGuards() 方法默认不会为网关和微服务设置保护(有关如何更改此行为的信息,请参阅 混合应用)。对于 "standard"(非混合)微服务应用,useGlobalGuards() 会全局安装守卫
全局守卫用于整个应用,用于每个控制器和每个路由处理程序。在依赖注入方面,从任何模块外部注册的全局守卫(如上例中的 useGlobalGuards())不能注入依赖,因为这是在任何模块的上下文之外完成的。为了解决这个问题,你可以使用以下结构直接从任何模块设置守卫:
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}提示
当使用此方法对防护执行依赖注入时,请注意,无论采用此构造的模块如何,防护实际上是全局的。这应该在哪里完成?选择定义守卫(上例中的 RolesGuard)的模块。此外,useClass 不是处理自定义提供程序注册的唯一方法。了解更多 此处。
