nestjs中,AuthGuard类,在CanActive函数中无法调用Inject

启因

最近写nestjs项目,他的UserGuard很方便,相当于在route处理request时,可以经由AuthGuard来处理验证代码.

但是,我在写的过程中,出了一个问题,花了很长时间来解决,比如如下 StaffAuthGuard.ts代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { ExecutionContext, Inject, Injectable } from '@nestjs/common';
import { StaffAuthService } from './staff-auth.service';
import { of } from 'rxjs';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class StaffJwtAuthGuard extends AuthGuard('jwt') {
  constructor(private authService: StaffAuthService) {
    super(authService);
    this.authService = authService;
    console.log(
      '🚀 ~ file: staff-auth.guard.ts:11 ~ StaffJwtAuthGuard ~ constructor ~ authService',
      authService,
      ', this.authService: ',
      this.authService,
    );
  }
  canActivate(context: ExecutionContext) {
    if (context.getType() != 'http') {
      return false;
    }
    const authHeader = context.switchToHttp().getRequest()
      .headers.authorization;
    if (!authHeader) {
      return false;
    }
    const authHeaderParts = authHeader.split(' ');
    if (authHeaderParts.length != 2) {
      return false;
    }
    const [, jwt] = authHeaderParts;

    this.authService
      .verifyJwt(jwt)
      .then((res) => {
        const exp = res.exp;
        if (!exp) return of(false);
        const TOKEN_EXP_MS = exp * 1000;
        const isJwtValid = Date.now() < TOKEN_EXP_MS;
        // return of(isJwtValid);
        return isJwtValid;
      })
      .catch((err) => {
        console.log(
          '🚀 ~ file: staff-auth.guard.ts:35 ~ StaffJwtAuthGuard ~ canActivate ~ err',
          err,
        );
        // return of(false);
        return false;
      });
    return super.canActivate(context);
  }
}

在调用api时,经由StaffAuthGuard.canActivate时,就会报undefined,加了日志后,发现是这行:

1
2
 this.authService
      .verifyJwt(jwt)

出错,此处就是this.authService未定义,但是编译可以通过,于是我在constructor函数中加上了日志,this.authService明明是有值的,为什么到调用的时候就没有了?

解决过程

百思不得其解,搞了几天,我都要放弃了,其间我还用了ChatGPT来帮我解决问题,他给了我的问题:

1
2
3
4
5
6
7
8
9
It's difficult to determine what could be causing this.authService to be undefined without seeing the entire code. However, here are a few common issues that could be causing the problem:

    The AuthService has not been imported correctly: Make sure that you have imported the AuthService using the correct import statement and path.

    The AuthService has not been added to the providers array in the module: If the AuthService is not part of the module's providers array, it won't be available for injection.

    The AuthService has not been injected into the constructor of the StaffJwtAuthGuard: If you are not injecting the AuthService into the constructor of the StaffJwtAuthGuard, then it won't be available as a property on the class.

Here's an example of how to correctly inject the AuthService into the StaffJwtAuthGuard:

照着对比了下,基本是这样做的,但是还是解决不了问题.

过了几天,中间我搞了点别的事情,换换心情,然后又回来解决这个,继续google,在nestjs的官方issue中找到一条: [# Nest can’t resolve dependencies of the AuthGuard (Guard decorator) #893](Nest can't resolve dependencies of the AuthGuard (Guard decorator) · Issue #893 · nestjs/nest · GitHub) 跟我的问题一样,也是在AuthGuard中无法引用inject后的服务,有一个回答: [Also ensure that you aren’t providing AuthService twice](Nest can't resolve dependencies of the AuthGuard (Guard decorator) · Issue #893 · nestjs/nest · GitHub) 引起了我的注意,

仔细观察日志,我发现StaffAuthGuard被初始化了两次,第一次日志里面有authSerivce,是正常有值的,第二次是undefined.顺着这个思路,把所有的StaffAuthGuard的地方都找出来,

使用//大法,一个个屏蔽测试,发现原因:

我的StaffJwtAuth是定义在app中,而需要用到此模块的controller在其引用的shared库中,按照官方文档(忘了具体位置了),如果在库中这样就需要在app中将guard定义为全局,如:

1
2
3
4
5
6
 providers: [
    {
      provide: APP_GUARD,
      useClass: StaffJwtAuthGuard,
    },
  ],

我就是这样定义了全局了,但是,在我调用的api的controller位置,也加上了@UseGuard(StaffJwtAuthGuard),就导致初始化了两次,并且第二次参数为undefined,将controller上的@UseGuard去掉后,就正常了.


思考

之后做了几个测试,把StaffAuthModule也放到库里面,屏掉全局APP_GUARD代码,在controller中加上代码,结果只有一个初始constructor调用,而且里面的authServiceundefined 之后我又继续google, 在官方issue里面又找到一个: middlewares are loosely coupled from modules, thus it may be better to use them instead. If you prefer to use guards, you need to guarantee an access to the AuthService. You can export this service from the AuthModule, and then import this module wherever you want to use your guard. ,意思就是说,在我这个场景中,在使用StaffAuthGuard.StaffAuthService的模块中,需要保证能访问到StaffAuthService, 于是,我在controller所在的模块的imports中增加了导入StaffAuthModule,结果,OK了,结论就是在使用AuthGurad的模块中,因为AuthGuard是松散的,所有需要在调用他的模块中,保证他所有的依赖都会被import

终于解决了,花了几天时间,不解决不舒服,现在舒服了,可以继续折腾了.