nestjs之JWT认证实现流程

这篇具有很好参考价值的文章主要介绍了nestjs之JWT认证实现流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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;
};

保存PassportStrategycallback,后面解析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并生成下图所示:
nestjs之JWT认证实现流程,nestjs原理,javascript,前端
然后把生成的结果传给PassportStrategycallback函数,该函数会调用我们定义好的validate方法。如果validate方法没有返回值或者抛出异常那么就会判断为失败。validate返回值最后会被传入auth.controller.tsgetProtectedRoutereq方法。

还有@InjectRepository(User)中是做了哪些事情呢?InjectRepository只不过在user后面加了一个repository名字userRepository传给了Inject,相当于@Inject('UserRepository')

// users.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UsersService {
  constructor(
    // InjectRepository只不过在user后面加了一个repository名字userRepository传给了Inject,相当于@Inject('UserRepository')
    // @Inject('UserRepository')是Reflect.defineMetadata(constants_1.SELF_DECLARED_DEPS_METADATA, dependencies, target),给当前类设置依赖{index: 0,param: "UserRepository"},类型为'self:paramtypes'
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {
    console.log(123);
    
  }

  async create(username: string, password: string): Promise<User> {
    const hashedPassword = await bcrypt.hash(password, 10);
    const newUser = this.usersRepository.create({ username, password: hashedPassword });
    return this.usersRepository.save(newUser);
  }

  async validateUser(username: string, pass: string): Promise<any> {
    const user = await this.usersRepository.findOne({ where: { username } });
    if (user && await bcrypt.compare(pass, user.password)) {
      const { password, ...result } = user;
      return result;
    }
    return null;
  }
}

那么有了注入,那么就有提供注入的地方,这里只有TypeOrmModule.forFeature([User]),我们看看forFeature代码。

// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { User } from './user.entity';
import { UsersController } from './users.controller ';
import { JwtModule } from '@nestjs/jwt';

@Module({
  // TypeOrmModule.forFeature([User])设置provider,为了注入repostory,比如@Inject('UserRepository')。这个方法会生成UserRepository的provider
  imports: [TypeOrmModule.forFeature([User]),JwtModule.register({
    secret: '123456', // 设置一个秘密的密钥
    signOptions: { expiresIn: '3600s' }, // 设置 token 过期时间
  }),],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersLoginModule {}

createTypeOrmProviders(entities, dataSource)中返回一个provider,对应@Inject("UserRepository")

@Module({})
export class TypeOrmModule {
  static forRoot(options?: TypeOrmModuleOptions): DynamicModule {
    return {
      module: TypeOrmModule,
      imports: [TypeOrmCoreModule.forRoot(options)],
    };
  }

  static forFeature(
    entities: EntityClassOrSchema[] = [],
    dataSource:
      | DataSource
      | DataSourceOptions
      | string = DEFAULT_DATA_SOURCE_NAME,
  ): DynamicModule {
    const providers = createTypeOrmProviders(entities, dataSource);
    EntitiesMetadataStorage.addEntitiesByDataSource(dataSource, [...entities]);
    return {
      module: TypeOrmModule,
      providers: providers,
      exports: providers,
    };
  }

  static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule {
    return {
      module: TypeOrmModule,
      imports: [TypeOrmCoreModule.forRootAsync(options)],
    };
  }
}

这里返回一个provider

function createTypeOrmProviders(entities, dataSource) {
    return (entities || []).map((entity) => ({
        provide: (0, typeorm_utils_1.getRepositoryToken)(entity, dataSource),// 生成标识符UserRepository,entiry.name+Repository
        useFactory: (dataSource) => {// 重要!!3.将entity设置到repository上
            const enitityMetadata = dataSource.entityMetadatas.find((meta) => meta.target === entity);
            const isTreeEntity = typeof enitityMetadata?.treeType !== 'undefined';
            return isTreeEntity
                ? dataSource.getTreeRepository(entity)
                : dataSource.options.type === 'mongodb'
                    ? dataSource.getMongoRepository(entity)
                    : dataSource.getRepository(entity);
        },
        inject: [(0, typeorm_utils_1.getDataSourceToken)(dataSource)],// 一个数组,指定 useFactory 函数的依赖项
        /**
         * 用于解决动态模块序列化问题的额外属性。它通过 getMetadataArgsStorage 函数找到与实体类相对应的表元数据
         * that occurs when "TypeOrm#forFeature()" method is called with the same number
         * of arguments and all entities share the same class names.
         */
        targetEntitySchema: (0, typeorm_1.getMetadataArgsStorage)().tables.find((item) => item.target === entity),
    }));
}

理解代码的关键部分

  1. Passport 集成:
    代码中使用了 Passport.jspassport.authenticate 方法。这是 Passport.js 的核心功能,用于执行认证流程。
  2. 处理认证结果:
    handleRequest 方法处理 passport.authenticate 的结果。这里可以自定义决定如何处理认证失败(例如抛出异常)或成功(例如返回用户对象)。

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

  1. wsy.strategy.ts

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

  2. AuthGuard('jwt')
    解析header中的token并将validate函数的返回值设置到req上,默认是user,也可以自己设置。文章来源地址https://www.toymoban.com/news/detail-798153.html

到了这里,关于nestjs之JWT认证实现流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • jwt+redis实现登录认证

    项目环境:spring boot项目 pom.xml引入jwt和redis application.yml配置文件中,对redis进行配置 JwtUtil工具类 登录的控制器 在前端发起登录请求,用户名和密码验证通过后,生成jwt,把jwt字符长的值,存放在redis缓存中,然后把jwt字符串返回到前端。 前端拿到jwt字符串后存在一个变量中

    2024年02月21日
    浏览(23)
  • Spring Gateway使用JWT实现统一身份认证

    在开发集群式或分布式服务时,鉴权是最重要的一步,为了方便对请求统一鉴权,一般都是会放在网关中进行处理。目前非常流行的一种方案是使用JWT,详细的使用说明,可以找相关的资料查阅,这里先不进行深入的引用了。主要使用它下面的特性: 它的数据使用JSON格式封

    2024年02月12日
    浏览(24)
  • ASP.NET Core高级之认证与授权(二)--JWT认证前后端完整实现

    了解JWT身份认证的流程 了解基于JWT身份认证和Session身份认证的区别 学习如何在ASP.NET Core WebAPI项目中封装JWT认证功能 在上文ASP.NET Core高级之认证与授权(一)–JWT入门-颁发、验证令牌中演示了JWT认证的一个入门案例,本文是一个基于JWT认证的完整的前后端实现代码案例。 JWT身

    2024年02月01日
    浏览(32)
  • springBoot JWT实现websocket的token登录拦截认证

    功能:所有关于websocket的请求必须登录,实现websocket需要登录后才可使用,不登录不能建立连接。 后台spring security配置添加websocket的请求可以匿名访问,关于websocket的请求不要认证就可以随意访问,去除匿名访问后,前端在与websocket建立链接无法在请求头里直接加入Authoriz

    2024年02月13日
    浏览(34)
  • 手把手教你Shiro整合JWT实现登录认证

    SpringBoot Mybatis-plus Shiro JWT Redis Shiro: Shiro 是一个基于 Java 的开源的安全框架。 在 Shiro 的核心架构里面,Subject 是访问系统的用户。SecurityManager 是安全管理器,负责用户的认证和授权,相当于 Shiro 的老大哥。 Realm 相当于数据源,用户的认证和授权都在 Realm 的方法中进行。

    2023年04月17日
    浏览(30)
  • 在Spring boot中 使用JWT和过滤器实现登录认证

    在navicat中运行如下sql,准备一张user表 导入pom.xml坐标 在工utils包下创建一个用于生成和解析JWT 令牌的工具类 在pojo包下创建user类 在mapper包下添加 UserMapper接口 在service包下添加 UserService类 在utils包下添加统一响应结果封装类 在controller包下添加LoginController类 这样登录获取toke

    2024年02月06日
    浏览(27)
  • 操作过滤器—MVC中使用操作过滤器实现JWT权限认证

    上一篇文章分享了授权过滤器实现JWT进行鉴权,文章链接:授权过滤器—MVC中使用授权过滤器实现JWT权限认证,接下来将用操作过滤器实现昨天的JWT鉴权。 ​ 与授权过滤器大部分一样,只是执行的时机和继承的接口有所不同。操作过滤器是在Action执行的前和后进行调用执行

    2024年02月13日
    浏览(31)
  • Flask 高级应用:使用蓝图模块化应用和 JWT 实现安全认证

    本文将探讨 Flask 的两个高级特性:蓝图(Blueprints)和 JSON Web Token(JWT)认证。蓝图让我们可以将应用模块化,以便更好地组织代码;而 JWT 认证是现代 Web 应用中常见的一种安全机制。 在大型应用中,一个单独的 Python 文件可能无法容纳所有的路由和视图函数。这时,Flask 的

    2024年02月13日
    浏览(32)
  • Gateway+Springsecurity+OAuth2.0+JWT 实现分布式统一认证授权!

    目录 1. OAuth2.0授权服务 2. 资源服务 3. Gateway网关 4. 测试   在SpringSecurity+OAuth2.0 搭建认证中心和资源服务中心-CSDN博客 ​​​​​​ 基础上整合网关和JWT实现分布式统一认证授权。   大致流程如下: 1、客户端发出请求给网关获取令牌 2、网关收到请求,直接转发给授权服务

    2024年01月24日
    浏览(40)
  • Spring Boot + Vue的网上商城之springsecurity+jwt+redis实现用户权限认证实现

    在网上商城项目中,用户的安全性是非常重要的。为了实现用户权限认证和安全校验,我们可以使用Spring Security、JWT和Redis来实现。本篇博客将详细介绍后端和前台的实现过程,并提供相应的代码案例。 当用户点击登录按钮时,前端发送一个POST请求到后端的登录接口,传递用

    2024年02月07日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包