上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

nestjs passport及@nestjs/passport源码级分析(二)

[复制链接]
  • TA的每日心情
    慵懒
    2024-10-28 07:07
  • 签到天数: 193 天

    [LV.7]常住居民III

    712

    主题

    5961

    回帖

    6760

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6760

    荣誉开发者喜迎中秋油中2周年生态建设者

    发表于 2022-12-4 00:46:01 | 显示全部楼层 | 阅读模式

    前提

    上一节我们学习了怎么配置
    那大概有以下几个问题
    1.我们在路由只是声明了一个@UseGuards(LocalAuthGuard)
    而LocalAuthGuard中没有任何调用查询的源码,为什么就得到了执行
    补充,这里没有使用@UseGuards(AuthGuard('local'))是因为我们又声明了一个LocalAuthGuard
    并且LocalAuthGuard 继承自AuthGuard('local')

    @Injectable()
    export class LocalAuthGuard extends AuthGuard('local') {
      handleRequest(err, user) {
        if (err) {
          throw err;
        }
        if (!user) {
          // 登录失败
          console.log('登录失败');
          return null;
        }
        return { ...user, tag: 'local' };
      }
    }

    2.我们在local.strateg.ts中虽然声明了查询条件
    但是为什么没有任何地方引用他
    仅仅是在模块中导入一下就可以执行查询条件了
    到底是怎么找到的?又是怎么运行的?
    3.为什么官方文档这么简略??????????????????

    开始

    我们首先从@UseGuards(LocalAuthGuard)开始读吧
    UseGuards是一个路由守卫,必须实现canActive函数
    传入的一个LocalAuthGuard的类,而这个类继承自AuthGuard('local')

    class LocalAuthGuard extends AuthGuard('local'){
        handleRequest
    }

    我们先读一下AuthGuard,在@nestjs/passport内
    找到了lib\auth.guard.ts

    export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> =
      memoize(createAuthGuard);

    根据AuthGuard('local')可以知道AuthGuard是一个函数,传入一些参数得到特定的类
    而memoize根据名字是一个节省资源创建的记忆函数,直接跳过,查看createAuthGuard
    源码如下

    function createAuthGuard(type?: string | string[]): Type<CanActivate> {
      class MixinAuthGuard<TUser = any> implements CanActivate {
        @Optional()
        @Inject(AuthModuleOptions)
        protected options: AuthModuleOptions = {};
    
        constructor(@Optional() options?: AuthModuleOptions) {
          this.options = options ?? this.options;
          if (!type && !this.options.defaultStrategy) {
            new Logger('AuthGuard').error(NO_STRATEGY_ERROR);
          }
        }
    
        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);
          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;
        }
    
        getRequest<T = any>(context: ExecutionContext): T {
          return context.switchToHttp().getRequest();
        }
    
        getResponse<T = any>(context: ExecutionContext): T {
          return context.switchToHttp().getResponse();
        }
    
        async logIn<TRequest extends { logIn: Function } = any>(
          request: TRequest
        ): Promise<void> {
          const user = request[this.options.property || defaultOptions.property];
          await new Promise<void>((resolve, reject) =>
            request.logIn(user, (err) => (err ? reject(err) : resolve()))
          );
        }
    
        handleRequest(err, user, info, context, status): TUser {
          if (err || !user) {
            throw err || new UnauthorizedException();
          }
          return user;
        }
    
        getAuthenticateOptions(
          context: ExecutionContext
        ): Promise<IAuthModuleOptions> | IAuthModuleOptions | undefined {
          return undefined;
        }
      }
      const guard = mixin(MixinAuthGuard);
      return guard;
    }

    可以看到对一个class类做了一次mixin,主要还是看class
    初始化判断了一个options,可以说几乎没有操作

        constructor(@Optional() options?: AuthModuleOptions) {
          this.options = options ?? this.options;
          if (!type && !this.options.defaultStrategy) {
            new Logger('AuthGuard').error(NO_STRATEGY_ERROR);
          }
        }

    然后我们看路由触发的canActivate函数
    合并了options以及默认options,getAuthenticateOptions是为了方便我们hook搞的
    然后获取了当前请求的Reuqest以及Response

          const options = {
            ...defaultOptions,
            ...this.options,
            ...(await this.getAuthenticateOptions(context))
          };
                const [request, response] = [
            this.getRequest(context),
            this.getResponse(context)
          ];

    其中getRequest和getResponse也在类中定义了

        getRequest<T = any>(context: ExecutionContext): T {
          return context.switchToHttp().getRequest();
        }
    
        getResponse<T = any>(context: ExecutionContext): T {
          return context.switchToHttp().getResponse();
        }

    接下来继续看通过createPassportContext传入请求和响应拿到了passportFn
    然后同步一下passportFn,传入了type,options以及一个handleRequest函数,最后将结果赋值到request然后返回true

          const passportFn = createPassportContext(request, response);
          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;

    那中心基本在createPassportContext上
    我们去看看
    createPassportContext 返回了一个函数,函数如果被调用则返回一个promise,然后调用passport.authenticate,传入type,我们这里是local字符串,也就是想调用passport-local,options是选项,然后一个回调函数
    成功则把info赋值到request.authInfo上,并且resolve(callback(err, user, info, status))一下
    失败则调用reject

    const createPassportContext =
      (request, response) => (type, options, callback: Function) =>
        new Promise<void>((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()))
        );

    注意这里callback是passportFn的第三个参数,也就是

          const user = await passportFn(
            type || this.options.defaultStrategy,
            options,
            (err, user, info, status) =>
              this.handleRequest(err, user, info, context, status)
          );

    中的第三个参数
    会回调handleRequest函数,想到了什么?没错,就是我们创建LocalAuthGuard类的时候复写了handleRequest函数

      handleRequest(err, user) {
        if (err) {
          throw err;
        }
        if (!user) {
          // 登录失败
          console.log('登录失败');
          return null;
        }
        return { ...user, tag: 'local' };
      }

    最后会在canActive调用的结束的时候

    request[options.property || defaultOptions.property] = user;

    赋值到request对象上
    那我们就基本理清了LocalAuthGuard的逻辑

    PassPort守卫逻辑

    在初始化的时候传入type
    然后每次访问路由触发父类的canActive函数
    canActive函数会去调用PassPort的authenticate函数,把我们传入的数据等等丢给passport,本质就相当于一层浅封装,如果成功则回调handleRequest函数,并且把函数返回的数据赋值到Request对象的某个属性上,那这节课我们就分析完毕上层了
    说白了这个就相当于一个浅封装

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表