NestJS 的 守卫 学习

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

守卫的职责只有一个,那就是授权验证,即通过判断权限、角色、ACL 等条件确定是否满足程序的处理条件,假如没有则是返回权限不足的异常。

授权通常是由应用程序中的中间件来处理,身份验证采用中间件来实现是一个不错选择,因为令牌验证和对请求对象进行处理的这些操作都与路由上下文没有密切的联系。

但是中间件在执行的时候我们需要使用next()来声明下一个调用程序,所以在使用上还是比较不便。而在 NestJS 中的守卫可以访问ExecutionContext,因此程序在运行的时候可以明确知道接下来要执行的内容,守卫的设计非常类似于异常过滤器、管道和拦截器,让您可以在请求/响应周期中的正确位置插入处理逻辑,并且以声明方式执行此操作,这有助于保持代码整洁和声明性。

守卫的生命周期是在所有中间件之后,拦截器和管道之前执行。

授权守卫

每个守卫必须实现canActivate()方法,该方法应返回一个布尔值,指示是否允许当前请求。守卫的返回方式可以是同步或者异步,返回的类型为Observable

返回的请求处理如下:

  • 如果返回 true 的话,则请求将被处理
  • 如果返回 false 的话,则拒绝请求

我们来看一个简单的验证实例:

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);
  }
}

在上述的例子中我们可以看到函数内部使用了validateRequest()方法来处理守卫的逻辑并返回授权验证的结果,而守卫的canActivate()方法中我们可以调用上下文来获取到请求对象,这样我们就可以做更多的操作。

ExecutionContext 简要介绍

在上述的例子中可以看到canActivate()方法只是接受ExecutionContext的实例。而ExecutionContext继承于ArgumentsHostArgumentsHost已经在异常过滤器章节中简单介绍过。而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> {
    
    
    return true; // 允许所有请求继续
  }
}

绑定守卫

守卫的绑定和异常过滤器一样,守卫可以在控制器、方法和全局范围上进行声明,注入的时候只要使用@UseGuards()装饰器就可以,该装饰器可以采用单个参数或以逗号分隔的参数列表。具体的实例如下:

// 控制器上进行添加守卫
@Controller('commodity')
@UseGuards(RolesGuard)
export class CommodityController {
    
    }

// 方法上添加守卫
@Get('/all')
@UseGuards(RolesGuard)
getCommoditys() {
    
    
  return this.commodityService.findAll()
}

// 全局添加守卫
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());

说明:@UseGuards()装饰器传入的时候类名的时候,框架会帮我们启用依赖项注入,而使用实例的话则不会。

全局守卫会应用于整个应用程序、每个控制器和每个路由。当我们需要在某个模块注入全局守卫的依赖项是不行的,因为全局的守卫是在没有上下文环境下完成,所以没有上下文的支持,模块是不能找到对应实例。为了解决这个问题,我们可以用下面的方式来进行过滤器的注入:

import {
    
     Module } from "@nestjs/common";
import {
    
     APP_GUARD } from "@nestjs/core";

@Module({
    
    
  providers: [
    {
    
    
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {
    
    }

采用上述的方式进行守卫的注入,注入后的守卫还是全局的。

设置每个处理程序的角色

有时候我们会遇到对于不同的路线可以有不同的权限方案,例如有些可能仅对管理员用户开放,而另一些则可以对所有人开放。

当遇到上述这种情况的时候我们可以使用@SetMetadata()来自定义元数据,具体的代码如下:

@Get('/all')
@UseGuards(RolesGuard)
@SetMetadata('roles', ['admin'])
getCommoditys(@Body() quest: any) {
    
    
  return this.commodityService.findAll()
}

通过上面的构造,我们将 roles 元数据(roles 是键,而[‘admin’]是特定值)附加到 create()方法中。@SetMetadata()虽然这可行,但直接在路线中使用并不是一个好习惯。相反,创建您自己的装饰器,如下所示:

// 自定义装饰器
import {
    
     SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

// 引用自定义装饰器
@Get('/all')
@UseGuards(RolesGuard)
@SetMetadata('roles', ['admin'])
getCommoditys(@Body() quest: any) {
    
    
  return this.commodityService.findAll()
}

而我们要在守卫中获取自定义的参数可以使用Reflector对象,具体实例代码如下:

import {
    
     Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import {
    
     Reflector } from "@nestjs/core";
import {
    
     Observable } from "rxjs";

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

  canActivate(
    context: ExecutionContext
  ): boolean | Promise<boolean> | Observable<boolean> {
    
    
    const roles = this.reflector.get<string[]>("roles", context.getHandler());
    console.log(roles);
    return true;
  }
}

具体实例

根据上述的内容,可以整合一下所有的知识点,编写如下的完整代码实例:

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());
    if (!roles) {
    
    
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

猜你喜欢

转载自blog.csdn.net/qq_33003143/article/details/132002072