You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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先检查该标记,存在则直接放行。

  1. 修改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;
  }
}
  1. 修改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();
    }
  }
}

方式二:自定义组合守卫(推荐)

创建一个组合守卫,统一控制两个守卫的执行顺序和逻辑,保持原有守卫的独立性。

  1. 创建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);
  }
}
  1. 路由使用组合守卫
// 替换原有的@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:10:28