如何在NestJS中仅特定场景下设置AuthGuard为可选?
NestJS实现ApiKeyGuard通过后跳过AuthGuard校验
问题描述
我在NestJS中配置了两个守卫:ApiKeyGuard(基于API密钥认证)和AuthGuard(基于令牌认证),ApiKeyGuard优先级更高。需要实现:当请求通过ApiKeyGuard校验后,不再触发AuthGuard的校验,即ApiKeyGuard生效时AuthGuard变为可选。
现有代码示例
路由代码
// 支持应用内令牌及第三方用户通过API密钥访问 @UseGuards(ApiKeyGuard, AuthGuard) @Get('/get-products') async getProducts(): Promise<any> { try { return this.moduleRef .get(`appService`, { strict: false }) .getProducts(); } catch (error) { throw new InternalServerErrorException(error.message, error.status); } } // 仅允许应用内令牌访问 @UseGuards(AuthGuard) @Get('/get-users') async getUsers(): Promise<any> { try { return this.moduleRef .get(`appService`, { strict: false }) .getUsers(); } catch (error) { throw new InternalServerErrorException(error.message, error.status); } }
ApiKeyGuard实现
// api-key.guard.ts @Injectable() export class ApiKeyGuard implements CanActivate { constructor(private readonly apiKeyService: ApiKeyService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const req = context.switchToHttp().getRequest(); const key = req.headers['X-API-KEY'] ?? req.query.api_key; return this.apiKeyService.isKeyValid(key); } }
AuthGuard实现
// authentication.guard.ts @Injectable() export class AuthGuard implements CanActivate, OnModuleInit { constructor(private readonly moduleRef: ModuleRef) {} onModuleInit() {} async canActivate(context: ExecutionContext): Promise<boolean> { try { const request = context.switchToHttp().getRequest(); if (request.headers.authorization) { const token = request.headers.authorization.split(' ')[1]; const response = await this.checkToken(token); if (response) { return response; } else { throw new UnauthorizedException(); } } else { throw new UnauthorizedException(); } } catch (error) { throw new UnauthorizedException(); } } }
解决方案
提供两种可行的实现方式,按需选择:
方式一:通过请求标记跳过AuthGuard校验
修改原有守卫逻辑,在ApiKeyGuard校验通过时给请求对象添加标记,AuthGuard先检查该标记,存在则直接放行。
- 修改ApiKeyGuard
@Injectable() export class ApiKeyGuard implements CanActivate { constructor(private readonly apiKeyService: ApiKeyService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const req = context.switchToHttp().getRequest(); // Node.js会将请求头转为小写,用x-api-key更可靠 const key = req.headers['x-api-key'] ?? req.query.api_key; const isValid = await this.apiKeyService.isKeyValid(key); if (isValid) { // 添加认证标记 req.apiKeyAuthenticated = true; } return isValid; } }
- 修改AuthGuard
@Injectable() export class AuthGuard implements CanActivate, OnModuleInit { constructor(private readonly moduleRef: ModuleRef) {} onModuleInit() {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); // 优先检查是否已通过ApiKey认证 if (request.apiKeyAuthenticated) { return true; } try { if (request.headers.authorization) { const token = request.headers.authorization.split(' ')[1]; const response = await this.checkToken(token); if (response) { return response; } else { throw new UnauthorizedException(); } } else { throw new UnauthorizedException(); } } catch (error) { throw new UnauthorizedException(); } } }
方式二:自定义组合守卫(推荐)
创建一个组合守卫,统一控制两个守卫的执行顺序和逻辑,保持原有守卫的独立性。
- 创建CombinedAuthGuard
@Injectable() export class CombinedAuthGuard implements CanActivate { constructor( private readonly apiKeyGuard: ApiKeyGuard, private readonly authGuard: AuthGuard ) {} async canActivate(context: ExecutionContext): Promise<boolean> { // 先执行ApiKeyGuard校验 const isApiKeyValid = await this.apiKeyGuard.canActivate(context); if (isApiKeyValid) { return true; } // ApiKey校验失败,再执行AuthGuard校验 return this.authGuard.canActivate(context); } }
- 路由使用组合守卫
// 替换原有的@UseGuards(ApiKeyGuard, AuthGuard) @UseGuards(CombinedAuthGuard) @Get('/get-products') async getProducts(): Promise<any> { try { return this.moduleRef .get(`appService`, { strict: false }) .getProducts(); } catch (error) { throw new InternalServerErrorException(error.message, error.status); } }
两种方式对比
- 方式一:代码改动小,适合快速实现,但需要修改原有守卫逻辑,耦合性略高。
- 方式二:遵循单一职责原则,原有守卫无需修改,逻辑更清晰,适合复杂场景或需要复用组合逻辑的情况。
内容的提问来源于stack exchange,提问作者Teknoville
相关产品推荐
相关产品推荐

