开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天, 点击查看活动详情
该文可以说是对照官网的学习笔记,使用流行的passport,顺序有所改变,关于自定义Passport认证的方法,我总结了一下: 先注册导入相应xxxModule,提供对应xxxStrategy,构建对应xxxAuthGuard,最后直接使用UseGuards()
一、安装相应依赖
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()装饰器就可以了