如何在 Nest.js 中使用自定义 Guard 及兼容 jwt 情况下无需单独每个文件配置 Guard

如果你对照了官方文档,安装JWT认证的话,你的controller可能类似于这样

@Controller('auth/')
export class AuthController {
  constructor(
    private readonly authService: AuthService,
    private readonly helper: Helper,
  ) {}

//    获取token(使用local策略)
  @UseGuards(LocalAuthGuard)
  @Post('access-token')
  async login(@Request() req) {
    return this.authService.login(req.user);
  }
//    解析token获取user信息(使用Jwt策略)
  @UseGuards(JwtAuthGuard)
  @Get('user')
  getProfile(@Request() req) {
    return this.helper.getUser();    //    文档上是 return this.request.user 我做了个封装
  }
}

看上去很美好,带上用户信息请求access-token就可以获取到access_token,不带上token请求user接口就会401,但是有一个问题,要是这个项目有很多接口/方法,都要进行jwt验证,难道要一个一个的@UseGuards(JwtStrategy)去写吗,那多麻烦啊,还让代码变得丑陋,我们为什么不配置一个默认的Guard,再需要不认证的时候单独指定呢?
先来看看nest.js官方提供的Guard可用作用域。

第一种:作用于方法
如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard
第二种:作用于controller
如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard
第三种:作用于全局
如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard
目前Nest只提供以上三种作用于,如果你项目较大,接口较多,使用第一种和第二种岂不是要写非常多的@UseGuards(xxxxx),如果使用全局的话,那我的获取Token的access-token接口也会凉掉。
我目前在进行的项目有十五个模块,一个一个写我可不愿意。于是我准备自己实现一个上层Guard,来对不同的请求进行不同的策略分配

第一步:创建一个自定义的上层Guard
auth.guard.ts

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

@Injectable()
//    自定义Guard必须实现canActivate方法
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
  }

}

第二步:创建一个不需要认证(NoAuth)的标识位,让部分接口不需要进行jwt的验证
我在这里选择创建一个装饰器,使用SetMetadata()方法设置一个bool值,用于告诉这个RoleAuthGuard,他不需要进行jwt的策略认证(我将它放在common下)

如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard

import { SetMetadata } from '@nestjs/common'

export const NoAuth = () => SetMetadata('no-auth', true);

第三步:返回完善RoleAuthGuard

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import { AuthGuard, IAuthGuard } from '@nestjs/passport';

@Injectable()
export class RoleAuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
  //    在这里取metadata中的no-auth,得到的会是一个bool
    const noAuth = this.reflector.get<boolean>('no-auth', context.getHandler());
    const guard = RoleAuthGuard.getAuthGuard(noAuth);
    return guard.canActivate(context);    //    执行所选策略Guard的canActivate方法
  }

//    根据NoAuth的t/f选择合适的策略Guard
  private static getAuthGuard(noAuth: boolean): IAuthGuard {
    if (noAuth) {
      return new (AuthGuard('local'))();
    } else {
      return new (AuthGuard('jwt'))();
    }
  }
}

第四步:设置这个Guard为全局使用

import { APP_FILTER, APP_GUARD } from '@nestjs/core';
import { AllExceptionsFilter } from '@/all-exception.filter';
import { RoleAuthGuard } from '@/auth/guards/role-auth.guard';
@Module({
  imports: [...],
  providers: [
  //    此为过滤全局异常,可以忽略
    {
      provide: APP_FILTER,
      useClass: AllExceptionsFilter,
    },
    //    设置全局守卫,useClass为自定义的Guard
    {
      provide: APP_GUARD,
      useClass: RoleAuthGuard,
    },
    ...
  ],
})
export class AppModule {}

最后,使用Postman进行测试,默认会使用Jwt策略,在不需要认证的位置加上@NoAuth()即可

如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard

如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard

如何在Nest.js中使用自定义Guard及兼容jwt情况下无需专门每个文件配置Guard

本作品采用《CC 协议》,转载必须注明作者和本文链接
Reflection.
讨论数量: 10

巧了,最近也在研究 nestjs ,刚好前两天再看官方 demo 中的 19-auth-jwt ,然后通过打日志,发现一个疑惑,非常不解,下面描述一下,问问大佬可否指点一下?

启动demo之后,会有俩接口:

  1. oauth/login
  2. profile

第1个是用来身份验证,验证通过,颁发 jwt 令牌。
第2个用来检测 jwt 令牌的合法性。

以上流程应该都没问题吧。

那么问题来了,在第1步验证用户信息的时候,如果验证成功,没啥可说的。

但是,验证失败的时候,返回了401授权失败的信息,这个也可以理解。

但是这个401返回的条件有两种:

  1. 用户名和密码 都输入 ,但输入的不对
  2. 用户名和密码 任意一个不输入都不输入

第一种情况,会进入 strategy 等校验的地方,正常。

第二种情况,压根不会进入,连校验都不会,而且,从app 层到下面,完全没进入业务逻辑代码里面。???

我从 app controller 到下面所有的 每个地方都打了断点日志,来看执行顺序,发现的这个情况。

所以看到第2种情况的时候,我有些疑惑,网上也没有找到详细的 passport 相关的原理机。

3年前 评论
NearTheShore (楼主) 3年前
yaimeet (作者) 3年前

刚开始学习,正好考虑到了这个问题

3年前 评论
NearTheShore (楼主) 3年前
Paulo (作者) 3年前

我又来了。

这里local守卫也不能给任何路由使用的,因为local策略的validate是接收username和password进行用户校验的,所以只能用于login。如果其他的普通路由不需要权限验证,是不能使用local守卫的,否则会收到401错误(因为请求其他路由的时候,显然不会发送username和password)

所以理论上有三种情况,一种是需要使用local Authguard做用户登录验证,一种是Jwt做正常的请求鉴权,另一种是不需要守卫,直接放行

3年前 评论
codergin 1年前

你好,请问能有demo或者源码吗?我这边按您的方式写,报错了。报错如下: Error: Unknown authentication strategy "local"

3年前 评论
NearTheShore (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!