nestjs之JWT认证实现流程

news2025/2/23 8:04:37

nestjs的jwt认证利用了 Passport.js 的认证机制。要根据这个源码实现您自己的 AuthGuard,需要理解几个关键部分:如何集成 Passport.js、如何处理认证结果,以及如何使用 NestJS 的依赖注入系统。

先自定义一个策略函数类

// wsy.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class WsyStrategy extends PassportStrategy(Strategy) {
  constructor() {
    // 由于是indectable,所以实例化的时候就会执行super,super是PassportStrategy(Strategy)返回的类
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: '123456',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

PassportStrategy函数返回的是MixinStrategy 类,该类提供callback去调用我们自定义的validate方法

// @nestjs\passport\dist\passport\passport.strategy.js
"use strict";
function PassportStrategy(Strategy, name, callbackArity) {
    class MixinStrategy extends Strategy {
        constructor(...args) {
        	// 此时该回调函数的this是指向WsyStrategy的
            const callback = async (...params) => {
                const done = params[params.length - 1];
                try {
                	// 调用WsyStrategy中的validate方法
                    const validateResult = await this.validate(...params);
                    if (Array.isArray(validateResult)) {
                        done(null, ...validateResult);
                    }
                    else {
                        done(null, validateResult);
                    }
                }
                catch (err) {
                    done(err, null);
                }
            };
            ...
            // 调用Strategy的constructor方法,并传入callback
            super(...args, callback);
            // 获取唯一的passport为Authenticator
            const passportInstance = this.getPassportInstance();
            if (name) {
            	// 关键!!这里调用Authenticator的use方法设置当前的strategy为WsyStrategy
                passportInstance.use(name, this);
            }
            else {
                passportInstance.use(this);
            }
        }
        getPassportInstance() {
            return passport;
        }
    }
    return MixinStrategy;
}

保存我们的WsyStrategy策略

// passport\lib\authenticator.js
Authenticator.prototype.use = function(name, strategy) {
  if (!strategy) {
    strategy = name;
    name = strategy.name;
  }
  if (!name) { throw new Error('Authentication strategies must have a name'); }
  
  this._strategies[name] = strategy;
  return this;
};

保存PassportStrategy的callback,后面解析jwt后调用

// passport-jwt\lib\strategy.js
function JwtStrategy(options, verify) {
	
    passport.Strategy.call(this);
    this.name = 'jwt';
    ...
	// 保存了PassportStrategy的callback!!
    this._verify = verify;
    ...
}

此时发起请求前的配置以及配置好了,还有发起请求后的配置:

// auth.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus, UseGuards, Get, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './login.dto'; // 假设您有一个 DTO 定义登录请求的结构
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {
  // 首先请求被AuthGuard的返回给拦截,执行AuthGuard得到的是MixinAuthGuard
  @UseGuards(AuthGuard('jwt'))
  @Get()
  getProtectedRoute(@Request() req) {
    // 如果 JWT 验证通过,req.user 将包含用户信息
    return req.user;
  }
}

进入AuthGuard的编译后的源码

// AuthGuard是createAuthGuard
export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> =
  memoize(createAuthGuard);
function createAuthGuard(type?: string | string[]): Type<IAuthGuard> {
  class MixinAuthGuard<TUser = any> implements CanActivate {
    ...

    async canActivate(context: ExecutionContext): Promise<boolean> {
      const options = {
        ...defaultOptions,
        ...this.options,
        ...(await this.getAuthenticateOptions(context))
      };
      const [request, response] = [
        this.getRequest(context),
        this.getResponse(context)
      ];
      const passportFn = createPassportContext(request, response);
      // 最终是执行passportFn=createPassportContext 
      const user = await passportFn(
        type || this.options.defaultStrategy,
        options,
        (err, user, info, status) =>
          this.handleRequest(err, user, info, context, status)
      );
      request[options.property || defaultOptions.property] = user;
      return true;
    }
    handleRequest(err, user, info, context, status): TUser {
      if (err || !user) {
        throw err || new UnauthorizedException();
      }
      return user;
    }
  }
}

返回一个函数,该函数返回一个promise

const createPassportContext = (request, response) => (type, options, callback) => new Promise((resolve, reject) => passport.authenticate(type, options, (err, user, info, status) => {
    try {
        request.authInfo = info;
        return resolve(callback(err, user, info, status));
    }
    catch (err) {
        reject(err);
    }
})(request, response, (err) => (err ? reject(err) : resolve())));

化简后

const createPassportContext = (request, response) => (type, options, callback) => {
  // 该函数返回一个 Promise
  return new Promise((resolve, reject) => {
    // 使用 Passport 的 authenticate 方法进行认证
    passport.authenticate(type, options, (err, user, info, status) => {
      try {
        // 在认证回调中,将认证的信息存储在请求对象的 authInfo 属性中
        request.authInfo = info;
        // 调用传入的回调函数,传递认证结果和信息
        return resolve(callback(err, user, info, status));
      } catch (err) {
        // 捕获可能的错误并将其拒绝
        reject(err);
      }
    })(request, response, (err) => {
      // 处理认证过程中的错误
      if (err) {
        reject(err); // 如果有错误,拒绝 Promise
      } else {
        resolve(); // 没有错误,解析 Promise
      }
    });
  });
};

主要执行passport.authenticate方法

Authenticator.prototype.authenticate = function(strategy, options, callback) {
  return this._framework.authenticate(this, strategy, options, callback);
};
module.exports = function authenticate(passport, name, options, callback) {
  ...
  return function authenticate(req, res, next) {
    
    ...
    
    (function attempt(i) {
      var layer = name[i];
      // If no more strategies exist in the chain, authentication has failed.
      if (!layer) { return allFailed(); }
    
      // Get the strategy, which will be used as prototype from which to create
      // a new instance.  Action functions will then be bound to the strategy
      // within the context of the HTTP request/response pair.
      var strategy, prototype;
      if (typeof layer.authenticate == 'function') {
        strategy = layer;
      } else {
      // 关键!!这里执行_strategy方法并传入layer('jwt')去获取(初始化时调用Authenticator的use方法设置当前的strategy为WsyStrategy)WsyStrategy
        prototype = passport._strategy(layer);
        if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')); }
        
        strategy = Object.create(prototype);
      }
    ...
      // 执行WsyStrategy继承的JwtStrategy的authenticate方法
      strategy.authenticate(req, options);
    })(0); // attempt
  };
};

passport._strategy(layer)方法:

Authenticator.prototype._strategy = function(name) {
  return this._strategies[name];
};

函数strategy.authenticate(req, options)最终通过jsonwebtoken库解码我们的authtoken并生成下图所示:
在这里插入图片描述
然后把生成的结果传给PassportStrategy的callback函数,该函数会调用我们定义好的validate方法。如果validate方法没有返回值或者抛出异常那么就会判断为失败。validate返回值最后会被传入auth.controller.tsgetProtectedRoute的req方法。

理解代码的关键部分

  1. Passport 集成:
    代码中使用了 Passport.js 的 passport.authenticate 方法。这是 Passport.js 的核心功能,用于执行认证流程。
  2. 处理认证结果:
    handleRequest 方法处理 passport.authenticate 的结果。这里可以自定义决定如何处理认证失败(例如抛出异常)或成功(例如返回用户对象)。
  3. 获取请求和响应对象:
    在守卫中,需要访问当前的 HTTP 请求和响应对象。这是通过 ExecutionContext 实现的。

所以我们可以总结下这几个关键模块的功能:

  1. wsy.strategy.ts

该模块是给passport设置strategy为WsyStrategy和传入一些初始化参数,比如密钥,jwt的获取方式等,以及提供验证函数供用户给出返回值。

  1. AuthGuard(‘jwt’)
    解析header中的token并将validate函数的返回值设置到req上,默认是user,也可以自己设置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1391700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea 安装免费Ai工具 codeium

目录 概述 ide安装 使用 chat问答 自动写代码 除此外小功能 概述 这已经是我目前用的最好免费的Ai工具了&#xff0c;当然你要是有钱最好还是用点花钱的&#xff0c;比如copilot&#xff0c;他可以在idea全家桶包括vs&#xff0c;还有c/c的vs上运行&#xff0c;还贼强&am…

宝塔发布网站问题汇总和记录

1、添加网站站点后打不开 解决办法&#xff0c;关闭防跨站攻击2 2、laravel项目部署到linux的时候出现The stream or file "/home/www/storage/logs/laravel.log" could not be opened in append mode 给目录加权限 chmod -R 777 storage 3、Class "Redis"…

5G阅信在汽车销售行业的应用与优势

5G阅信在汽车销售行业的应用与优势包括&#xff1a;提升客户体验&#xff0c;提供更快速、稳定的网络服务&#xff1b;实时数据传输&#xff0c;更好地了解客户需求&#xff1b;增强现实应用&#xff0c;提供更真实、直观的购车体验&#xff1b;创新营销方式&#xff0c;如短视…

介绍一个强大的免费开源.net反编译工具

dnSpy dnSpy 是一个用C#开发&#xff0c;开源的调试器和.NET 汇编编辑器。 即使您没有任何可用的源代码&#xff0c;也可以使用它来编辑和调试程序&#xff0c;并可以把代码导出成.net工程。

esp32-idf Eclipse Log日志打印demo

Log日志打印demo 1、代码例程 esp32-S2 芯片 / Eclipse软件 开发环境 #include <stdio.h> #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_…

web自动化实现登录的几种方式

目录 前言 一、pythonunittest框架实现登录功能 二、pythonselenium实现登录功能 三、pythonrequests库实现登录功能 前言 今天主要想介绍python语言不同的自动化测试框架的结合方式来模拟登录功能。想了解自动化测试框架的同学不要错过哦&#xff01; 一、pythonunittest框…

MT6761芯片参数规格_MTK联发科4G智能模块

联发科MT6761平台采用12nm制程内建主频达 2.0 GHz 的 4 核 ARM Cortex–A53 处理器&#xff0c;IMG PowerVR GE 等级图形处理器&#xff0c;以及高速的 LPDDR4x 低功耗存储或是成本效益较高的 LPDDR3 内存。 内置微型化超低功耗的传感器中枢组件&#xff0c;适用于支持如计步器…

Python进程池multiprocessing.Pool

环境&#xff1a; 鲲鹏920:192核心 内存&#xff1a;756G python&#xff1a;3.9 python单进程的耗时 在做单纯的cpu计算的场景&#xff0c;使用单进程核多进程的耗时做如下测试&#xff1a; 单进程情况下cpu的占用了如下&#xff0c;占用一半的核心数&#xff1a; 每一步…

微服务实战项目_天机学堂01_初识项目

文章目录 一.项目简述二.Jenkins三.模拟真实业务:紧急bug修复和代码阅读四.测试和部署五.代码阅读-获取登录用户 一.项目简述 Q:天机学堂是什么? A:天机学堂是一个基于微服务架构的生产级在线教育项目 主要有两个端(项目已上线,可以点击查看): 管理后台: https://tjxt-admi…

【控制篇 / 分流】(7.4) ❀ 02. 对不同运营商IP网段访问进行分流 ❀ FortiGate 防火墙

【简介】公司有两条宽带用来上网&#xff0c;一条电信&#xff0c;一条联通&#xff0c;访问常用的某些网站速度时快时慢。领导要求&#xff0c;根据上网流量的目标运营商IP归属&#xff0c;将流量送到相应的运营商出口去&#xff0c;避免跨运营商上网。那么应该怎么做&#xf…

分销商城多端uniapp 可编译5端 - 等级提现额度

等级提现额度 等级提现额度是一种常见的财务管理策略&#xff0c;通常用于在线平台、金融服务或游戏中&#xff0c;用于控制不同等级用户的提现限额。这样的机制有助于平台管理资金流动性&#xff0c;防范欺诈&#xff0c;并鼓励用户提升他们的活跃度或忠诚度。以下是一个简单的…

HTML--JavaScript--语法基础

变量与常量 这个基本上没啥问题 变量命名规则&#xff1a; 变量由字母、数字、下划线、$组成&#xff0c;且变量第一个字符不能为数字 变量不能是系统关键字和保留字 语法&#xff1a; var 变量名 值&#xff1b;所有Javacript变量都由var声明 定义赋值字符串&#xff1a; …

大数据与人工智能赋能精益生产:掀起工业革命的浪潮!

随着科技的飞速发展&#xff0c;大数据和人工智能已经成为当今社会的热门话题。在这场科技革命中&#xff0c;大数据和人工智能如何赋能精益生产&#xff0c;引领工业革命的浪潮呢&#xff1f; 一、大数据&#xff1a;精益生产的智慧源泉 在精益生产中&#xff0c;大数据的应用…

力扣刷MySQL-第一弹(详细解析)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

【数据结构】常见八大排序算法总结

目录 前言 1.直接插入排序 2.希尔排序 3.选择排序 4.堆排序 5.冒泡排序 6.快速排序 6.1Hoare版本 6.2挖坑法 6.3前后指针法 6.4快速排序的递归实现 6.5快速排序的非递归实现 7.归并排序 8.计数排序&#xff08;非比较排序&#xff09; 9.补充:基数排序 10.总结…

LLaMa2 Chat gpt 大模型本地部署初体验

一直想在自己电脑或者测试环境随便找台服务器尝试部署一下“大模型”&#xff0c;但“大模型”对于内存和GPU的要求令人望而却步&#xff0c;层出不穷的各种术语也令人困惑&#xff0c;有点难以下手。 经过一段时间&#xff0c;在百度千帆大模型平台、讯飞星火大模型平台、魔搭…

k8s源码阅读:Informer源码解析

写在之前 Kubernetes的Informer机制是一种用于监控资源对象变化的机制。它提供了一种简化开发者编写控制器的方式&#xff0c;允许控制器能够及时感知并响应 Kubernetes 集群中资源对象的变化。Informer通过与Kubernetes API服务器进行交互&#xff0c;通过监听API服务器上资源…

【sqlserver】已成功与服务器建立链接,但是在登录过程中发生错误。(provider:TCP提供程序,error:0 - 指定的网络名不再可用。)

问题现象&#xff1a; 处理方式&#xff1a; 配置管理器&#xff0c;mssqlserver的协议 Named Pipes 启用&#xff0c;重新sqlserver服务试一下&#xff0c;我是自己摸索这样解决的&#xff0c;不行的话&#xff0c;可以看下下面链接建议 error:0 - 指定的网络名不可用

网络安全中的“三高一弱”和“两高一弱”是什么?

大家在一些网络安全检查中&#xff0c;可能经常会遇到“三高一弱”这个说法。那么&#xff0c;三高一弱指的是什么呢&#xff1f; 三高&#xff1a;高危漏洞、高危端口、高风险外连 一弱&#xff1a;弱口令 一共是4个网络安全风险&#xff0c;其中的“高危漏洞、高危端口、弱…

[New Tech] Compute Express Link 101

SDC2020: CXL 1.1 Protocol Extensions: Review of the cache and memory protocols in CXL