CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Nestjs如何做登陆鉴权
    • 安装依赖
    • 创建本地登录、token 策略类
    • 生成 TOKEN
    • 创建校验 tokenJWT 策略类
    • 自定义装饰器
    • 使用策略
  • Nestjs如何接入Redis
  • Nestjs typeorm 如何联查没有关联关系的实体
  • Nestjs接入nodemailer并通过QQ邮箱发送邮件步骤
  • Nestjs笔记
CD
2023-03-27
目录

Nestjs如何做登陆鉴权

登录的策略一般分为两种,一种是登录接口登录时需要的登录策略,一种是登录获取 token 之后验证 token 的策略, 这里我们使用的就是市面上比较通俗的,也是官方推荐的 passport 来做策略管理

# 安装依赖

pnpm add @nestjs/passport passport passport-local passport-jwt bcryptjs
// 类型提示
pnpm add @types/passport @types/passport-jwt @types/passport-local -D
1
2
3

接下来我们需要书写拦截器,也就是下面所说的策略类,来为我们的登录生成 jwt 和接口鉴权提供帮助. 我们知道拦截器的作用有下面几点:

  • 在函数执行之前/之后绑定额外的逻辑
  • 转换从函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本函数行为
  • 根据所选条件完全重写函数 (例如, 缓存目的)

这类被看作在接口调用前后做处理的函数,在 nest 中需要添加@Injectable 装饰器,他能告诉 nest 被装饰的函数是需要被注入的,同时 nest 会在构造函数中声明依赖。比如在 constructor 内注入某个 service 时, 如果这个类没有增加@Injectable 装饰器, 它是无法获取到依赖的服务的。例如

// @Injectable 装饰的类 都是Providers ,他们都可以通过 constructor 注入依赖关系
// @Injectable() 不使用Injectable
export class XXXX extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) { }
  async validate(payload: string, done: any) {
    // 报错,无法找到this.authService实例
    const user = await this.authService.validateUser(payload);
  }
}
1
2
3
4
5
6
7
8
9

下面我们就来书写登录前置的 demo

# 创建本地登录、token 策略类

创建一个文件来书写策略,这里命名为 local.strategy.ts

export class LocalStrategy extends PassportStrategy(Strategy, 'local')// local为校验策略名字
{
  constructor(
    @InjectModel(User) private userModel,
  ) {
    super({
      usernameField: 'username',
      passwordField: 'password',
    } as IStrategyOptions);
  }
  // super会从request数据包里拿到请求过来的字段名,并带给validate函数进行校验验证
  // 我们就需要在validate里写验证的逻辑,它代表如何去校验这个策略
  async validate(username: string, password: string) {
    const user = await this.userModel.findOne({ username }).select('+password');
    if (!user) {
      throw new BadRequestException('用户名不正确');
    }
    if (!compareSync(password, user.password)) {
      throw new BadRequestException('密码不正确');
    }
    return user;
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 生成 TOKEN

这里采用 nestjs 官方生成 jwt 的包,我们到我们 common 的公共包里,注入 jwt 的模块

pnpm add @nestjs/jwt
1
import { Module, Global } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
/**
 * 用于注入环境变量
 * Global 标记该模块为全局模块
 * 当我们引用了Common模块之后再任意地方都可以引用common模块里面引用的东西
 */
@Global()
@Module({
  imports: [
    ConfigModule.forRoot({
      // 实现环境变量全局化
      isGlobal: true,
    }),
    // 异步全局注入jwt模块, useFactory里return原本同步时应该传入的参数
    JwtModule.registerAsync({
      useFactory() {
        return {
          secret: process.env.JWT_SECRET,
        };
      },
    }),
    DbModule,
  ],
  providers: [CommonService],
  // JwtModule 在common模块被引用了,我们需要把他导出成公共的模块,让其他的模块也可以引用
  exports: [CommonService, JwtModule],
})
export class CommonModule {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

当我们导入了 jwt 模块后,我们就可以在别的模块的控制器内增加 jwt 的散列,调用其中的服务

// xxx.controller.ts

import {JwtService} from '@nestjs/jwt'
export class XXController {
  constructor(
    private jwtService: JwtService
  )

  xx() {
    // 生成jsonwebtoken的方法。传入一个唯一的id,可以是某一个表的主键,这样生成的jwt就是唯一的
    this.jwtService.sign(xx.id)

  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

接下来我们为 swagger 增加 填写 bearer token 的功能..

// main.ts
const options = new DocumentBuilder()
  .setTitle("xx")
  .setDescription("xxx")
  .addBearerAuth(); // 增加bearer
1
2
3
4
5

# 创建校验 tokenJWT 策略类

创建 jwt.strategy.ts 文件

import { HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './AuthService';
import {JwtPayloadToken} from './interfaces/JwtPayloadJwtPayloadInterfface';
import {ApiException} from '../error/exceptions/ApiException';
import {ApiErrorCode} from '../../config/ApiApiCodeEnum';

@Injectable()
export class AuthStrategy extends PassportStrategy(Strategy) {
  /**
   * 这里的构造函数向父类传递了授权时必要的参数,在实例化时,父类会得知授权时,客户端的请求必须使用 Authorization 作为请求头,
   * 而这个请求头的内容前缀也必须为 Bearer,在解码授权令牌时,使用秘钥 secretOrKey: 'secretKey' 来将授权令牌解码为创建令牌时的 payload。
   */
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'xxxxxxxxxx',
    });
  }

  /**
   * validate 方法实现了父类的抽象方法,在解密授权令牌成功后,即本次请求的授权令牌是没有过期的,
   * 此时会将解密后的 payload 作为参数传递给 validate 方法,这个方法需要做具体的授权逻辑,比如这里我使用了通过用户名查找用户是否存在。
   * 当用户不存在时,说明令牌有误,可能是被伪造了,此时需抛出 UnauthorizedException 未授权异常。
   * 当用户存在时,会将 user 对象添加到 req 中,在之后的 req 对象中,可以使用 req.user 获取当前登录用户。
   */
  async validate(payload: JwtPayloadToken, done: any) {
    const user = await this.authService.validateUser(payload);
    if (!user) {
      return done(new ApiException('token无效', ApiErrorCode.TOKEN_FAIL, 30001), false);
      // return done(new UnauthorizedException(), false);
    }
    done(null, user);
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

最后将其引用进 auth.module

# 自定义装饰器

创建获取当前用户信息的装饰器。 通过上面的 jwt 鉴权后 ,我们可以在 req 中拿到 jwt 模块帮我们注入的 user 信息,通过装饰器快速提取 user 的信息

// 新建文件current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
import { Request } from "express";

export const CurrentUser = createParamDecorator(
  (data, ctx: ExecutionContext) => {
    const req: Request = ctx.switchToHttp().getRequest();
    return req.user;
  }
);
1
2
3
4
5
6
7
8
9
10

# 使用策略

  1. 打开我们的 controller 文件
  2. 从 nestjs/common 中引入 UseGuards 装饰器
  3. 将 UseGuards 装饰器以:UseGuards('local') 的形式加在路由上

用什么策略加在路由或是类上。

import { UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@UseGuards(AuthGuard("local"))
class xxx {}
1
2
3
4
5
编辑 (opens new window)
#Nestjs
上次更新: 2023/03/27, 22:09:34
Nestjs如何接入Redis

Nestjs如何接入Redis→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式