Nestjs系列(一)轻松搞定Nestjs用户名密码认证和Jwt的使用方法

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天, 点击查看活动详情

该文可以说是对照官网的学习笔记,使用流行的passport,顺序有所改变,关于自定义Passport认证的方法,我总结了一下: 先注册导入相应xxxModule,提供对应xxxStrategy,构建对应xxxAuthGuard,最后直接使用UseGuards()

  • 对于passport-local验证:导入PassportModule,提供LocalStrategy,构建LocalAuthGuard,使用UseGuards(LocalAuthGuard)
  • 对于passport-jwt验证:导入JwtModule,提供JwtStrategy,构建JwtAuthGuard,使用UseGuards(JwtAuthGuard)
  • 一、安装相应依赖

    nestjs有自己封装好的库,这里吧passport-local用到的库一并安装

    $ npm install --save @nestjs/passport passport passport-local
    $ npm install --save-dev @types/passport-local
    

    原生的passport需要提供两个东西:

  • 针对特定的策略,提供一系列配置,如jwt需要一个secret密钥用来生成token
  • 一个验证的回调函数,验证通过返回整个用户信息,验证错误返回null,验证错误可以是用户没找到或者用户密码不匹配
  • 使用@nestjs/passport,通过创建一个类继承PassportStrategy(Stratery, name?),通过super()方法传入配置信息(对应步骤1),在该类中实现validate()方法用来做验证(对应步骤2)

    二、使用passport-local步骤

    1. 新建auth-module、auth-service

    $ nest g module auth
    $ nest g service auth
    

    2. 导入相应Module

    增加PassportModule,使用Passport的一系列特性

    import { Module } from '@nestjs/common';
    import { AuthService } from './auth.service';
    import { PassportModule } from '@nestjs/passport';
    @Module({
      imports: [UserModule, PassportModule],
      providers: [AuthService],
    export class AuthModule {}
    

    在AuthModule中引入UserService

    这样就可以使用UserService,官网上的做法是直接在imports中导入了UserModule,在我看来,这里只是需要使用UserService提供的方法,感觉没必要导入,提供一个UserService就够了

    @Module({
      providers: [AuthService,UserService],
    

    编写auth.service.ts

    注意:这一步其实并不是必须的步骤,只是为把后面策略中验证用户名密码的代码单独剥离出来而已,这个Service中涉及到操作数据库,我们一般习惯把直接跟数据库打交道的都放到Service中

    import { Injectable, Logger } from '@nestjs/common';
    import { UserService } from '../user/user.service';
    import * as bcrypt from 'bcrypt';
    @Injectable()
    export class AuthService {
      constructor(
        private readonly userService: UserService,
        private readonly logger: Logger,
     async validateUser(username: string, pass: string) {
         //这里是连接了数据库,根据username查找用户的操作,练习时可以硬编码
        const user = await this.userService.findOneByUsername(username);
        if (user) {
          const match = await bcrypt.compare(pass, user.password);
          if (match) {
            this.logger.log('用户名密码认证成功');
            const { password, ...result } = user;
            return result;
          this.logger.error('密码错误.');
        this.logger.error('用户名密码认证失败');
        return null;
    

    3. 提供策略

    也就是开篇讲的那两点,编写一个类,实现一个方法

    import { Strategy } from 'passport-local';
    import { Injectable, UnauthorizedException } from '@nestjs/common';
    import { PassportStrategy } from '@nestjs/passport';
    import { AuthService } from './auth.service';
    // name: 'local' 默认值,也可以自定义
    @Injectable()
    export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
      constructor(private authService: AuthService) {
         // 这里默认不用配置,假设的是你的User实体中属性名为username和password
         // 如果不是的话,需要手动指定是usernameField:xx和passwordField:xxx
        super();
      async validate(username: string, password: string) {
        const user = this.authService.validateUser(username, password);
        if (!user) {
          throw new UnauthorizedException();
        return user;
    

    4. 创建Guard

    构建一个LocalAuthGuard

    import { Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    @Injectable()
    export class LocalAuthGuard extends AuthGuard('local') {}
    

    5. 使用

    直接使用UseGuards(填写对应的Guard)

    import { Controller, Get, Post, Request, UseGuards } from '@nestjs/common';
    import { AppService } from './app.service';
    import { LocalAuthGuard } from './auth/local-auth.guard';
    @Controller()
    export class AppController {
      constructor(private readonly appService: AppService) {}
      @UseGuards(LocalAuthGuard)
      @Post('auth/login')
      async login(@Request() req) {
        return req.user;
    

    使用了UseGuards后,会自动进行验证,Passprot会自动创建一个user的object放到req上,这个object其实就是我们编写的那个类中实现的validate方法返回的user

    三、JWT的使用

    jwt其实就是登录的时候将用户名(或其他想放进去的信息)配合一个密钥生成一个token,返回给前端,前端保留在某个位置,前端之后发起的请求都带着这个token来,后端进行相应的鉴权响应

    先安装依赖包

    $ npm install --save @nestjs/jwt passport-jwt
    $ npm install --save-dev @types/passport-jwt
    

    步骤分为一下几步:

    1. 导入JwtModule

    在auth-module的imports中引入注册JwtModule,配置相关信息

    imports:[
      JwtModule.register({
        secret: jwtConstants.secret,
        signOptions: {
          expiresIn: '4h',
    

    2. 提供Jwt验证策略

    需要在auth-module的providers的中提供相应的策略,但是我们现在还没有,所以下一步是创建jwt验证策略JwtStrategy,它需要继承PassportStrategy(Strategy, name?),其中Strategy来自于passport-jwt,name是可选的,默认是jwt,

    import { ExtractJwt, Strategy } from 'passport-jwt';
    import { PassportStrategy } from '@nestjs/passport';
    import { Injectable } from '@nestjs/common';
    import { jwtConstants } from './constants';
    @Injectable()
    export class JwtStrategy extends PassportStrategy(Strategy, 'customJwt') {
      constructor() {
        super({
          jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
          ignoreExpiration: false,
          secretOrKey: jwtConstants.secret,
      async validate(payload: any) {
        //能到这里来说明一定是一个之前签过名的用户,不然不会进到该函数,直接返回401错误了
        return { userId: payload.sub, username: payload.username };
    

    创建好之后将JwtStrategy加入到auth-module的providers中

     providers: [
        JwtStrategy,
    

    3. 构建Guard

    策略做好后就创建xxxGuard,需要继承自AuthGuard(这里的名字就是创建策略时的那个name),可以作为@UseGuard()装饰器的参数,

    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    @Injectable()
    export class JwtAuthGuard extends AuthGuard('customJwt') {}
    

    这样做以后,每当使用该JwtAuthGuard的时候,都会找到JwtStrategy进行先前验证,这个寻找的过程找的就是name这个属性

    4. 使用

    以上工作完成后就可以开始使用了!

    使用方式:@UseGuards(这里填创建好的Guard,如JwtAuthGuard)

  • 在Controller上使用,则该控制器下的所有方法都要经过jwt的token验证
  • 在方法上使用,只有该方法需要经过jwt的token验证
  • @UseGuards(JwtAuthGuard)
    @Get('hello')
    getHello(@Request() req) {
    	return req.user;
    

    四、全局使用Guard

    类似jwt这种token认证,几乎每个请求都会携带,如果要给所有的请求都加上@UseGuards(JwtAuthGuard)未免有些过于繁琐,因此nestjs提供了一种全局使用的方法

    1. 注册全局guard

    可以在任意一个模块进行,不过我们就在auth模块进行了,方法如下:

    import { Module } from '@nestjs/common';
    import { APP_GUARD } from '@nestjs/core';
    import { JwtAuthGuard } from './jwt-auth.guard';
    @Module({
      providers: [
          provide: APP_GUARD,
          useClass: JwtAuthGuard,
    export class AuthModule {}
    

    2. 自定义Public装饰器

    import { SetMetadata } from '@nestjs/common';
    export const IS_PUBLIC_KEY = 'isPublic';
    export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
    

    3. 重写JwtAuthGuard

    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { AuthGuard } from '@nestjs/passport';
    import { Reflector } from '@nestjs/core';
    import { Observable } from 'rxjs';
    import { IS_PUBLIC_KEY } from './public-auth.decorator';
    @Injectable()
    export class JwtAuthGuard extends AuthGuard('customJwt') {
      constructor(private reflector: Reflector) {
        super();
      canActivate(
        context: ExecutionContext,
      ): boolean | Promise<boolean> | Observable<boolean> {
        // 用来过滤白名单,被@Public装饰器修饰的控制器直接跳过不做验证
        const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
          context.getHandler(),
          context.getClass(),
        if (isPublic) {
          return true;
        return super.canActivate(context);
    

    上面的意思就是说,只要是被@Public()修饰的,都不做验证,其它的都还是经过super.canActivate(context)的验证

    4. 使用

    直接在需要过滤的方法或者Controller上添加@Public()装饰器就可以了