李恒道 发表于 2022-12-4 00:46:01

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

# 前提
上一节我们学习了怎么配置
那大概有以下几个问题
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')
```js
class LocalAuthGuard extends AuthGuard('local'){
    handleRequest
}
```
我们先读一下AuthGuard,在@nestjs/passport内
找到了lib\auth.guard.ts
```js
export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> =
memoize(createAuthGuard);
```
根据AuthGuard('local')可以知道AuthGuard是一个函数,传入一些参数得到特定的类
而memoize根据名字是一个节省资源创建的记忆函数,直接跳过,查看createAuthGuard
源码如下
```js
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 = [
      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 = 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;
      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,可以说几乎没有操作
```js
    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
```js
      const options = {
      ...defaultOptions,
      ...this.options,
      ...(await this.getAuthenticateOptions(context))
      };
            const = [
      this.getRequest(context),
      this.getResponse(context)
      ];
```
其中getRequest和getResponse也在类中定义了
```js
    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
```js
      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 = user;
      return true;
```
那中心基本在createPassportContext上
我们去看看
createPassportContext 返回了一个函数,函数如果被调用则返回一个promise,然后调用passport.authenticate,传入type,我们这里是local字符串,也就是想调用passport-local,options是选项,然后一个回调函数
成功则把info赋值到request.authInfo上,并且resolve(callback(err, user, info, status))一下
失败则调用reject
```js
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的第三个参数,也就是
```js
      const user = await passportFn(
      type || this.options.defaultStrategy,
      options,
      (err, user, info, status) =>
          this.handleRequest(err, user, info, context, status)
      );
```
中的第三个参数
会回调handleRequest函数,想到了什么?没错,就是我们创建LocalAuthGuard类的时候复写了handleRequest函数
```js
handleRequest(err, user) {
    if (err) {
      throw err;
    }
    if (!user) {
      // 登录失败
      console.log('登录失败');
      return null;
    }
    return { ...user, tag: 'local' };
}
```
最后会在canActive调用的结束的时候
```js
request = user;
```
赋值到request对象上
那我们就基本理清了LocalAuthGuard的逻辑
# PassPort守卫逻辑
在初始化的时候传入type
然后每次访问路由触发父类的canActive函数
canActive函数会去调用PassPort的authenticate函数,把我们传入的数据等等丢给passport,本质就相当于一层浅封装,如果成功则回调handleRequest函数,并且把函数返回的数据赋值到Request对象的某个属性上,那这节课我们就分析完毕上层了
说白了这个就相当于一个浅封装
# 结语
撒花~
页: [1]
查看完整版本: nestjs passport及@nestjs/passport源码级分析(二)