前提
上一节我们学习了怎么配置
那大概有以下几个问题
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对象的某个属性上,那这节课我们就分析完毕上层了
说白了这个就相当于一个浅封装
结语
撒花~