NestJs Guard


NestJs Guard


サンプルコードはGithubにあります.)

Goal

  • NestJsからGuardの使い方を知る.
    	- ExcutionContext
    
    	- custom metadata
  • NestJs Guardを作成します.
  • Guardとは?


    ガードは、@Injectable()デコーダを用いて、CanActivateインターフェースを実装するクラスである.@Injectable()を使用する理由は、インスタンスではなくタイプを渡すためであり、インスタンス化の責任は、フレームワークに残して依存性を持たせることである.(newを使用してインスタンスを追加することもでき、以下に詳細に説明する)

    防御は単一の責任、特定の状況(権限、役割、ACLS...)これにより、与えられた要求は、ルーティングハンドルによって処理されるか否かが決定される.Expressでは主にミドルウェアを用いて処理される.
    ミドルウェアは主に認証関連の作業に用いられ、NestJsで認可された作業は防御によって完了する.
    公式ファイルによると、ミドルウェアはnext()を呼び出した後、どのルーティングハンドルが実行されるか分からない.しかしながら、ExcutionContextを使用することができるので、次のステップで実行されるルーティングハンドルを正確に知ることができる.
    HINTGuards are executed after each middleware, but before any interceptor or pipe.(保護はミドルウェアの後に実行され、インタフェースとパイプの前に実行されます.)

    CanActiveインタフェース

    CanActiveインタフェースの構成は次のとおりです.
    export interface CanActivate {
        canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>;
    }
    パラメータとして実行コンテキスト(ExecutionContext)を受け入れるcanActivateという方法がある.

    実行コンテキスト

    canActivateメソッドパラメータとしての実行コンテキストは、ArgumentHostを継承する.
    export interface ArgumentsHost {
        getArgs<T extends Array<any> = any[]>(): T;
        getArgByIndex<T = any>(index: number): T;
        switchToRpc(): RpcArgumentsHost;
        switchToHttp(): HttpArgumentsHost;
        switchToWs(): WsArgumentsHost;
        getType<TContext extends string = ContextType>(): TContext;
    }
    
    export interface ExecutionContext extends ArgumentsHost {
        getClass<T = any>(): Type<T>;
        getHandler(): Function;
    }
    ArgumentsHostは、前のフィルタを使用しているときに見られるはずです.ExecutionContextArgumentsHostを継承するので、switchは、各通信プロトコルに適したRequest, Response, next()法によって得ることができる.
    ここで重要な点はExecutionContextが有する方法である.getClassは、クライアントへのアクセスを要求するときに処理可能なルーティングハンドル付きコントローラの情報を有し、getHandlerは、クライアントからの要求を処理するルーティングハンドルの情報を有する.
    したがって、ミドルウェアとは異なり、その後実行されるコントローラまたはルーティングハンドルの情報を知ることができます.

    カスタム保護の作成(ロールベース)


    上に書いた内容で、Custom Guardを簡単に作成できます.
    @Injectable()
    export class RoleGuard implements CanActivate {
      canActivate(context: ExecutionContext): boolean {
        return true
      }
    }
    上記のカスタムガードを適用する方法はパイプ適用層とあまり変わらない.핸들러-레벨に適用してサンプルコードを記述します.
    export class AppController {
      constructor(private readonly appService: AppService) { }
    
      @Get()
      @UseGuards(RoleGuard)
      getHello(): string {
        return this.appService.getHello();
      }
    }
    前述したように、@UseGuardsデコーダを使用して防御することができ、現在、エンドポイントに送信された要求はRoleGuardからtrueに戻り、何も起こらない.
    でも!falseの値を返すと、クライアントは次のメッセージを受信します.
    {
        "statusCode": 403,
        "message": "Forbidden resource",
        "error": "Forbidden"
    }
    上記のメッセージをクライアントに渡すのではなくカスタマイズする場合は、認証に失敗した場合、throwで新しい例外を処理し、例外フィルタで応答を操作できます.
    上のように護衛を作ることができますが、公式文書によると、これはスマートな方法ではありません.ガードレールの最大の利点は、上述したようにExecutionContextを使用できることである.
    どのルーティングハンドルまたはコントローラが使用されているかを知ることができるので、要求されたエンドポイントがRoleに合致することを保証することができる.このとき出現する概念はcustom metadataである.

    custom metadata

    custom metadataは、@SetMetadata()を使用して、コントローラまたはルータハンドルにメタデータを定義することができる.
    @Get()
    @SetMetadata('role', 'admin')
    @UseGuards(RoleGuard)
    getHello(): string {
    return this.appService.getHello();
    }
    以上のように、@SetMetadata()を用いて、roleというメタデータをadminのキー値として定義し、getHelloのルーティングハンドルを付与する.
    しかし、公式文書によると、ルータHandlerに@SetMetadata()を直接使うのはよくない方法だ.そこで、カスタムデータムコーディネータを作成して指定します.
    // custom decorator
    export const Roles = (role: string) => SetMetadata('role', role);
    
    // route handler
    @Get()
    @Role('admin')
    @UseGuards(RoleGuard)
    getHello(): string {
    return this.appService.getHello();
    }
    Roleは、CustomDecoratorによって読み取り可能なコードとして付与され、強制的なタイプとすることができる.

    Reflectorを使用したメタデータの使用


    上記の概念をすべて加算すると、@SetMetadata()(or上に作成されたcustomベンチマークコーディネータ)を用いてmetadataをルーティングハンドルに付与し、ExecutionContextを用いて以降実行されるルーティングハンドルの情報をガードで知ることができる.
    では、ルータハンドルのメタデータをどのように取得しますか?NestJsでは、Reflectorを使用してメタデータにアクセスできます.Reflectorは、生成者においてDIを得ることができる.
    @Injectable()
    export class RoleGuard implements CanActivate {
      constructor(private readonly reflector: Reflector) { }
      canActivate(context: ExecutionContext,): boolean {
        const role = this.reflector.get<string>('role', context.getHandler());
    
        // do something
      }
    }
    
    export declare class Reflector {
      //...
      get<TResult = any, TKey = any>(metadataKey: TKey, target: Type<any> | Function): TResult;
      //...
    }
    パラメータ、第1の受信キー、第2の受信タイプ、またはReflectorとして、getによって提供されるFunctionを使用してメタデータをインポートすることができる.このとき、タイプまたはFunctionに関する情報をExecutionContextから取得することができる.

    Reference


  • https://docs.nestjs.com/guards#guards

  • https://wikidocs.net/158626