【问题标题】:NestJS - Combine multiple Guards and activate if one returns trueNestJS - 组合多个警卫并在一个返回 true 时激活
【发布时间】:2022-04-15 20:04:31
【问题描述】:

我有一个问题。

是否可以在一条路由上使用多个身份验证守卫(在我的情况下是基本身份验证和 ldap 身份验证)。 当一个守卫成功时,应该对路由进行身份验证。

【问题讨论】:

    标签: nestjs


    【解决方案1】:

    简短回答:不,如果您在一条路线上添加多个守卫,他们都需要通过才能激活路线。

    长答案:但是,通过使您的 LDAP 保护扩展基本保护,您可以尝试完成。如果LDAP特定逻辑成功,则返回true,否则返回调用结果super.canActivate()。然后,在您的控制器中,将基本或 LDAP 保护添加到您的路由中,但不能同时添加。

    basic.guard.ts

    export BasicGuard implements CanActivate {
      constructor(
          protected readonly reflector: Reflector
      ) {}
    
      async canActivate(context: ExecutionContext) {
         const request = context.switchToHttp().getRequest();
         if () {
            // Do some logic and return true if access is granted
            return true;
         }
    
        return false;
      }
    }
    

    ldap.guard.ts

    export LdapGuard extends BasicGuard implements CanActivate {
      constructor(
          protected readonly reflector: Reflector
      ) {
       super(reflector);
    }
    
      async canActivate(context: ExecutionContext) {
         const request = context.switchToHttp().getRequest();
         if () {
            // Do some logic and return true if access is granted
            return true;
         }
        
        // Basically if this guard is false then try the super.canActivate.  If its true then it would have returned already
        return await super.canActivate(context);
      }
    }
    

    欲了解更多信息,请参阅this GitHub issue on the official NestJS repository

    【讨论】:

      【解决方案2】:

      您可以创建一个抽象守卫,并在那里传递实例或引用,如果任何传递的守卫返回 true,则从该守卫返回 true。

      假设您有 2 个守卫:BasicGuardLdapGuard。你有一个控制器UserController,路由@Get(),应该受到这些守卫的保护。

      所以,我们可以用下面的代码创建一个抽象守卫MultipleAuthorizeGuard

      @Injectable()
      export class MultipleAuthorizeGuard implements CanActivate {
        constructor(private readonly reflector: Reflector, private readonly moduleRef: ModuleRef) {}
      
        public canActivate(context: ExecutionContext): Observable<boolean> {
          const allowedGuards = this.reflector.get<Type<CanActivate>[]>('multipleGuardsReferences', context.getHandler()) || [];
      
          const guards = allowedGuards.map((guardReference) => this.moduleRef.get<CanActivate>(guardReference));
      
          if (guards.length === 0) {
            return of(true);
          }
      
          if (guards.length === 1) {
            return guards[0].canActivate(context) as Observable<boolean>;
          }
      
          const checks$: Observable<boolean>[] = guards.map((guard) =>
            (guard.canActivate(context) as Observable<boolean>).pipe(
              catchError((err) => {
                if (err instanceof UnauthorizedException) {
                  return of(false);
                }
                throw err;
              }),
            ),
          );
      
          return forkJoin(checks$).pipe(map((results: boolean[]) => any(identity, results)));
        }
      }
      

      如您所见,这个守卫不包含对特定守卫的任何引用,而只接受引用列表。在我的示例中,所有守卫都返回Observable,因此我使用forkJoin 来运行多个请求。当然,它也可以被 Promises 采用。

      为了避免在控制器中启动 MultipleAuthorizeGuard 并手动传递必要的依赖项,我将这个任务留给 Nest.js 并通过自定义装饰器 MultipleGuardsReferences 传递引用

      export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
        SetMetadata('multipleGuardsReferences', guards);
      

      所以,在控制器中我们可以有下一个代码:

      @Get()
      @MultipleGuardsReferences(BasicGuard, LdapGuard)
      @UseGuards(MultipleAuthorizeGuard)
      public getUser(): Observable<User> {
        return this.userService.getUser();
      }
      

      【讨论】:

      • 您的元数据键不匹配。您需要在 canActivate 方法的第一行中将“allowedGuards”更改为“multipleGuardsReferences”。
      • 感谢您的建议。我会修复这个例子
      • 感谢@SergiyVoznyak 的宝贵意见!
      【解决方案3】:

      您可以使用组合守卫来注入您需要的所有守卫并结合它们的逻辑。 有关闭的github问题: https://github.com/nestjs/nest/issues/873

      【讨论】:

        【解决方案4】:

        还有一个 npm 包可以解决这种情况:https://www.npmjs.com/package/@nest-lab/or-guard

        然后你调用一个唯一的守卫,它引用所有必要的守卫作为参数:

        guards([useGuard('basic') ,useGuard('ldap')])
        

        【讨论】:

          【解决方案5】:

          根据 AuthGuard,它开箱即用

          AuthGuard 定义

          如果您查看 AuthGuard,您会看到以下定义:

          (文件为 node_modules/@nestjs/passport/dist/auth.guard.d.ts)

          export declare const AuthGuard: (type?: string | string[]) => Type<IAuthGuard>;
          

          这意味着 AuthGuard 可以接收字符串数组。

          代码

          在我的代码中,我执行了以下操作:

            @UseGuards(AuthGuard(["jwt", "api-key"]))
            @Get()
            getOrders() {
              return this.orderService.getAllOrders();
            }
          

          邮递员测试

          在 Postman 中,端点可以有 api-key 和 JWT。

          • 在 Postman 授权中使用 JWT 测试:有效
          • 在 Postman 授权中使用 API-Key 测试:有效

          这意味着 2 个 Guard 之间存在 OR 函数。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-05-08
            • 1970-01-01
            • 2018-02-09
            • 2019-08-16
            • 2020-11-10
            • 2019-12-06
            • 2019-10-08
            • 1970-01-01
            相关资源
            最近更新 更多