【发布时间】:2022-04-15 20:04:31
【问题描述】:
我有一个问题。
是否可以在一条路由上使用多个身份验证守卫(在我的情况下是基本身份验证和 ldap 身份验证)。 当一个守卫成功时,应该对路由进行身份验证。
【问题讨论】:
标签: nestjs
我有一个问题。
是否可以在一条路由上使用多个身份验证守卫(在我的情况下是基本身份验证和 ldap 身份验证)。 当一个守卫成功时,应该对路由进行身份验证。
【问题讨论】:
标签: nestjs
简短回答:不,如果您在一条路线上添加多个守卫,他们都需要通过才能激活路线。
长答案:但是,通过使您的 LDAP 保护扩展基本保护,您可以尝试完成。如果LDAP特定逻辑成功,则返回true,否则返回调用结果super.canActivate()。然后,在您的控制器中,将基本或 LDAP 保护添加到您的路由中,但不能同时添加。
export BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
return false;
}
}
export LdapGuard extends BasicGuard implements CanActivate {
constructor(
protected readonly reflector: Reflector
) {
super(reflector);
}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if () {
// Do some logic and return true if access is granted
return true;
}
// Basically if this guard is false then try the super.canActivate. If its true then it would have returned already
return await super.canActivate(context);
}
}
欲了解更多信息,请参阅this GitHub issue on the official NestJS repository。
【讨论】:
您可以创建一个抽象守卫,并在那里传递实例或引用,如果任何传递的守卫返回 true,则从该守卫返回 true。
假设您有 2 个守卫:BasicGuard 和 LdapGuard。你有一个控制器UserController,路由@Get(),应该受到这些守卫的保护。
所以,我们可以用下面的代码创建一个抽象守卫MultipleAuthorizeGuard:
@Injectable()
export class MultipleAuthorizeGuard implements CanActivate {
constructor(private readonly reflector: Reflector, private readonly moduleRef: ModuleRef) {}
public canActivate(context: ExecutionContext): Observable<boolean> {
const allowedGuards = this.reflector.get<Type<CanActivate>[]>('multipleGuardsReferences', context.getHandler()) || [];
const guards = allowedGuards.map((guardReference) => this.moduleRef.get<CanActivate>(guardReference));
if (guards.length === 0) {
return of(true);
}
if (guards.length === 1) {
return guards[0].canActivate(context) as Observable<boolean>;
}
const checks$: Observable<boolean>[] = guards.map((guard) =>
(guard.canActivate(context) as Observable<boolean>).pipe(
catchError((err) => {
if (err instanceof UnauthorizedException) {
return of(false);
}
throw err;
}),
),
);
return forkJoin(checks$).pipe(map((results: boolean[]) => any(identity, results)));
}
}
如您所见,这个守卫不包含对特定守卫的任何引用,而只接受引用列表。在我的示例中,所有守卫都返回Observable,因此我使用forkJoin 来运行多个请求。当然,它也可以被 Promises 采用。
为了避免在控制器中启动 MultipleAuthorizeGuard 并手动传递必要的依赖项,我将这个任务留给 Nest.js 并通过自定义装饰器 MultipleGuardsReferences 传递引用
export const MultipleGuardsReferences = (...guards: Type<CanActivate>[]) =>
SetMetadata('multipleGuardsReferences', guards);
所以,在控制器中我们可以有下一个代码:
@Get()
@MultipleGuardsReferences(BasicGuard, LdapGuard)
@UseGuards(MultipleAuthorizeGuard)
public getUser(): Observable<User> {
return this.userService.getUser();
}
【讨论】:
您可以使用组合守卫来注入您需要的所有守卫并结合它们的逻辑。 有关闭的github问题: https://github.com/nestjs/nest/issues/873
【讨论】:
还有一个 npm 包可以解决这种情况:https://www.npmjs.com/package/@nest-lab/or-guard。
然后你调用一个唯一的守卫,它引用所有必要的守卫作为参数:
guards([useGuard('basic') ,useGuard('ldap')])
【讨论】:
如果您查看 AuthGuard,您会看到以下定义:
(文件为 node_modules/@nestjs/passport/dist/auth.guard.d.ts)
export declare const AuthGuard: (type?: string | string[]) => Type<IAuthGuard>;
这意味着 AuthGuard 可以接收字符串数组。
在我的代码中,我执行了以下操作:
@UseGuards(AuthGuard(["jwt", "api-key"]))
@Get()
getOrders() {
return this.orderService.getAllOrders();
}
在 Postman 中,端点可以有 api-key 和 JWT。
这意味着 2 个 Guard 之间存在 OR 函数。
【讨论】: