NestJS UseGuards未走依赖注入 无法覆盖Guard问题咨询
当前行为
官方文档说明:
...我们传入RolesGuard类型(而非实例),将实例化职责交给框架,从而支持依赖注入。
因此我尝试通过模块的providers将一个Guard替换为另一个自定义实现,但该操作未达预期。
值得注意的是,在控制器构造函数中注入同一个服务时,可正确拿到替换后的实现。
复现代码
单模块、多模块场景均可以复现该问题。
控制器代码:
@Controller() @UseGuards(AuthGuard)
模块代码:
@Module({ controllers: [AppController], providers: [ { provide: AuthGuard, useClass: AuthOverrideGuard, } ], })
预期行为
我预期AuthGuard完全不会被实例化,但实际运行中两个类都会被实例化,且最终生效的Guard是AuthGuard。
运行环境
- Nest版本:8.0.6
- Node版本:16.8.0
- 运行平台:Linux
相关Issue
已在NestJS官方仓库提交对应Issue。
问询内容
请问该问题是我的使用方式有误,还是NestJS本身的Bug?是否有可行的替代方案可以实现Guard的覆盖?
解答
问题原因
这不属于Nest的Bug,是Nest增强器(Guard/拦截器/管道等)的默认实例化逻辑和常规服务Provider的逻辑相互独立导致的。
当你在@UseGuards()装饰器中直接传入类引用时,Nest会在增强器上下文单独实例化这个类,不会去匹配模块中注册的同类型Provider,所以你在providers中声明的替换规则不会作用到通过装饰器绑定的Guard上。
你在控制器构造函数中可以拿到替换后的实例,是因为构造函数注入走的是常规Provider解析链路,和增强器的实例化链路完全隔离,这也是你会观察到两个类都被实例化的原因。
可行的替代方案
方案1:通过自定义注入Token绑定Guard
该方案可以保留依赖注入能力,同时实现Guard实现的动态替换:
- 首先定义唯一注入Token:
export const AUTH_GUARD = Symbol('AUTH_GUARD');
- 控制器中使用Token代替直接传类:
@Controller() @UseGuards(AUTH_GUARD)
- 模块中配置Token对应的实现类:
@Module({ controllers: [AppController], providers: [ { provide: AUTH_GUARD, useClass: AuthOverrideGuard, } ], })
此时Nest会走常规Provider解析逻辑获取Guard实例,即可实现自定义覆盖。
方案2:使用全局Guard绑定Token替换
如果是全局生效的Guard,可以直接使用Nest内置的APP_GUARD Token绑定,无需在控制器单独加装饰器:
import { APP_GUARD } from '@nestjs/core'; @Module({ providers: [ { provide: APP_GUARD, useClass: AuthOverrideGuard, } ], })
该方案适合全局统一权限校验的场景,修改时只需替换useClass对应的类即可。
方案3:封装Guard工厂函数静态切换
如果不想修改现有@UseGuards(AuthGuard)的写法,可以将AuthGuard封装为工厂函数,根据配置返回不同实现:
export function AuthGuard() { // 可根据环境变量、配置等逻辑切换返回的Guard类 return enableOverride ? AuthOverrideGuard : BaseAuthGuard; }
控制器保持原有写法即可,该方案适合不需要依赖注入的静态切换场景。
内容的提问来源于stack exchange,提问作者Dimdum

