iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Modern Web

NestJS 帶你飛!系列 第 13

[NestJS 帶你飛!] DAY13 - Guard

什麼是 Guard?

Guard 是一種檢測機制,就像公司的保全系統,需要使用門禁卡才能進入,否則就會被擋在門外。這樣的機制經常用在身份驗證與授權,當有未經授權的請求時,將會由 Guard 攔截並擋下。Express 的 Guard 經常在 Middleware 層做處理,這樣的處理方式並沒有不好,只是要能夠清楚掌握呼叫 next() 之後會執行什麼,相較之下,Nest 多設計了 Guard 更能確保它的執行順序,從下圖可以看出 Guard 是執行在 Middleware 之後、Interceptor 之前:
https://ithelp.ithome.com.tw/upload/images/20210410/20119338I9fRZRXtaj.png

設計 Guard

Guard 可以透過 CLI 產生:

$ nest generate guard <GUARD_NAME>

注意<GUARD_NAME> 可以含有路徑,如:guards/auth,這樣就會在 src 資料夾下建立該路徑並含有 Guard。

這邊我建立一個 AuthGuardguards 資料夾下:

$ nest generate guard guards/auth

src 底下會看見一個名為 guards 的資料夾,裡面有 auth.guard.ts 以及 auth.guard.spec.ts
https://ithelp.ithome.com.tw/upload/images/20210411/20119338um8WYh6nmr.png

建立出來的 Guard 骨架如下,會發現 Guard 其實也是帶有 @Injectable 裝飾器的 class,不過它必須實作 CanActivate 介面,並設計 canActivate(context: ExecutionContext) 方法:

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

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

canActivate(context: ExecutionContext) 可以是同步或非同步的,所以回傳值可以是 booleanPromise<boolean>Observable<boolean>,如果要讓驗證能夠通過,就必須讓最終結果為 true

提醒:會提供 ExecutionContext 是因為可以從這裡取出需要用來驗證的資料。

使用 Guard

在使用之前,先將 auth.guard.ts 修改一下,刻意將結果回傳 false,並採用非同步做法:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return of(false).pipe(delay(2000));
  }
}

接著建立 TodoModuleTodoController

$ nest generate module features/todo
$ nest generate controller features/todo

提醒:如果已經建立過可以略過此步驟。

透過 @UseGuards 裝飾器即可輕鬆套用,使用的方式大致上可以分成兩種:

  • 單一資源:在 Controller 的方法中套用 @UseGuards 裝飾器,只會針對該資源套用。
  • Controller:直接在 Controller 上套用 @UseGuards 裝飾器,會針對整個 Controller 中的資源套用。

下方以套用在 Controller 為例,修改 todo.controller.ts

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '../../guards/auth.guard';

@Controller('todos')
@UseGuards(AuthGuard)
export class TodoController {
  @Get()
  getAll() {
    return [];
  }
}

透過瀏覽器查看 http://localhost:3000 會發現被 Guard 擋掉了:

{
  "statusCode": 403,
  "message":"Forbidden resource",
  "error":"Forbidden"
}

全域 Guard

如果整個 App 都需要使用 Guard,那就可以直接將其掛在全域下,只需要修改 main.ts 即可,透過 useGlobalGuards 來配置全域 Guard:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AuthGuard } from './guards/auth.guard';

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

用依賴注入實作全域 Guard

上面的方法是透過模組外部完成全域配置的,透過指定 Provider 的 token 為 APP_GUARD 來實現,這裡是用 useClass 來指定要建立實例的類別:

import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodoModule } from './features/todo/todo.module';
import { AuthGuard } from './guards/auth.guard';

@Module({
  imports: [TodoModule],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_GUARD,
      useClass: AuthGuard
    }
  ]
})
export class AppModule {}

小結

Guard 是實作授權與身份驗證的好幫手,也是非常常用的功能,後面篇幅會出現 Guard 的相關應用。下方是今天的懶人包:

  1. Guard 執行在 Middleware 之後、Interceptor 之前。
  2. Guard 比 Middleware 更適合實作授權與身份驗證。
  3. 善用 ExecutionContext 取得驗證相關資訊。
  4. 全域 Guard 可以透過依賴注入的方式實作。

上一篇
[NestJS 帶你飛!] DAY12 - Interceptor
下一篇
[NestJS 帶你飛!] DAY14 - Custom Decorator
系列文
NestJS 帶你飛!32

尚未有邦友留言

立即登入留言