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

Terraform配置API Gateway V2对接CloudFront返回301/403问题

问题根因
  • 你看到的CloudFront默认301响应页面、API Gateway无访问日志,核心是协议策略触发了边缘节点直接响应:当viewer_protocol_policy = "redirect-to-https"时,CloudFront收到HTTP请求会直接在边缘节点返回301跳转,根本不会回源到API Gateway,所以后端查不到请求记录;配置为https-only时会直接拒绝HTTP请求返回403,也不会回源。
  • API Gateway源域名提取正则写法错误:Terraform的replace函数使用RE2正则,不需要额外加/作为正则分隔符,你当前写的"/^https?://([^/]*).*/"无法匹配https://开头的invoke_url,导致提取出的源域名带协议前缀,CloudFront源配置无效,HTTPS请求也无法正确转发到后端。
  • 回源头转发配置缺失:/api/*路径的缓存规则仅转发了Origin头,没有转发API Gateway域名校验必需的Host头,即使请求到达后端也会因域名不匹配返回403/301。
  • 路径拼接逻辑错误:配置的origin_path会直接拼接在用户请求路径前转发给API Gateway,当前配置回源路径会变成/serverless_lambda_stage/api/xxx,和后端实际路由不匹配;且默认不会自动去掉/api前缀,后端无法匹配到对应接口。
  • 冗余配置冲突:你的需求是把API挂载到主域名的/api路径下,不需要单独配置api.xxxx.com自定义域名、对应子域名托管区及解析记录,冗余配置可能引发解析或回源冲突。
  • 缺少主域名到CloudFront的解析记录:当前配置里没有把xxxx.com和www.xxxx.com解析到CloudFront分发域名的记录,部分场景下会出现解析异常。
配置调整方案

1. 修正API Gateway源配置

修正正则提取逻辑,确认阶段路径配置正确:

origin {
  # 去掉正则前后的/分隔符,正确提取纯域名
  domain_name = replace(aws_apigatewayv2_stage.lambda.invoke_url, "^https?://([^/]*).*", "$1")
  origin_id   = "apigw"
  # 直接引用阶段名,避免硬编码路径错误,如果用$default阶段这里填空字符串即可
  origin_path = "/${aws_apigatewayv2_stage.lambda.name}"

  custom_origin_config {
    http_port              = 80
    https_port             = 443
    origin_protocol_policy = "https-only"
    origin_ssl_protocols   = ["TLSv1.2"]
  }
}

2. 调整/api路径缓存行为

补全必要头转发,修改协议策略,增加路径改写逻辑去掉/api前缀:

ordered_cache_behavior {
  path_pattern     = "/api/*"
  allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
  cached_methods   = ["GET", "HEAD"]
  target_origin_id = "apigw"

  default_ttl = 0
  min_ttl     = 0
  max_ttl     = 0

  forwarded_values {
    query_string = true
    # 补全API Gateway校验必需的Host头,以及后端识别请求需要的其他头
    headers      = ["Origin", "Host", "X-Forwarded-Proto", "X-Forwarded-For", "Authorization", "Accept"]
    cookies {
      forward = "all"
    }
  }
  # 改为redirect-to-https,实现HTTP自动跳HTTPS,不会直接返回403
  viewer_protocol_policy = "redirect-to-https"

  # 关联Lambda@Edge在回源前去掉/api前缀
  lambda_function_association {
    event_type   = "origin-request"
    lambda_arn   = aws_lambda_function.cf_path_rewrite.qualified_arn
    include_body = false
  }
}

3. 添加路径改写Lambda@Edge

首先新增Lambda执行角色,允许CloudFront调用:

resource "aws_iam_role" "lambda_edge_role" {
  name = "lambda-edge-cf-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]
        }
      }
    ]
  })
}

# 给角色加Lambda基础执行权限即可
resource "aws_iam_role_policy_attachment" "lambda_edge_basic" {
  role       = aws_iam_role.lambda_edge_role.name
  policy_arn = "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

然后创建路径改写函数,把打包好的代码(index.js)上传:

resource "aws_lambda_function" "cf_path_rewrite" {
  filename      = "path_rewrite.zip"
  function_name = "cf-path-rewrite"
  role          = aws_iam_role.lambda_edge_role.arn
  handler       = "index.handler"
  runtime       = "nodejs18.x"
  publish       = true # Lambda@Edge必须发布版本才能被CloudFront关联
}

对应的index.js代码逻辑很简单:

exports.handler = (event, context, callback) => {
  const request = event.Records[0].cf.request;
  // 去掉URI开头的/api前缀
  request.uri = request.uri.replace(/^\/api/, '') || '/';
  callback(null, request);
};

4. 清理冗余配置、补全解析记录

删除以下和当前需求无关的资源,避免冲突:

  • aws_apigatewayv2_domain_name 下配置的api.xxxx.com自定义域名资源
  • aws_route53_zone.api_main 子域名托管区
  • aws_route53_record.api-ns 子域名NS记录
  • aws_route53_record.api_picturethis api子域名指向API Gateway的A记录

补全主域名和www子域名到CloudFront的别名解析:

resource "aws_route53_record" "main_cloudfront" {
  zone_id = aws_route53_zone.main.zone_id
  name    = var.site_domain
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.dist.domain_name
    zone_id                = aws_cloudfront_distribution.dist.hosted_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "www_cloudfront" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "www.${var.site_domain}"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.dist.domain_name
    zone_id                = aws_cloudfront_distribution.dist.hosted_zone_id
    evaluate_target_health = false
  }
}
验证步骤
  1. 执行terraform plan确认变更无误后应用配置
  2. 等待CloudFront部署完成(通常5-15分钟)
  3. 先测试HTTPS接口:curl -v https://xxxx.com/api/你的接口路径,确认返回正常响应,API Gateway日志可查到请求记录
  4. 测试HTTP跳转:curl -v http://xxxx.com/api/你的接口路径,确认返回301跳转到对应HTTPS地址

如果不想用Lambda@Edge做路径改写,也可以给API Gateway的所有路由统一加上/api前缀,去掉Lambda关联配置即可,只是后续接口维护成本更高。


内容的提问来源于stack exchange,提问作者Conor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 12:48:21