Skip to content

该文可以说是对照官网的学习笔记,使用流行的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()装饰器就可以了