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并生成下图所示:
在这里插入图片描述
然后把生成的结果传给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,也可以自己设置。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Young soul2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值