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

Terraform配置AWS API Gateway OPTIONS预检请求失败排查

问题描述

我通过Terraform创建了带Lambda Proxy集成的AWS API Gateway REST API,Lambda函数负责对DynamoDB表执行CRUD操作。除GET和OPTIONS请求外,其余请求都配置了Cognito用户池授权器。

我用Terraform模块为所有资源配置了OPTIONS预检请求的MOCK集成,设置了Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Allow-Origin:

Terraform CORS模块代码

api/cors/main.tf

# api/cors/main.tf
resource "aws_api_gateway_method" "cors_method" {
  rest_api_id   = var.api_id
  resource_id   = var.resource_id
  http_method   = "OPTIONS"
  authorization = "NONE"
}

resource "aws_api_gateway_method_response" "cors_method_response" {
  rest_api_id = var.api_id
  resource_id = var.resource_id
  http_method = aws_api_gateway_method.cors_method.http_method
  status_code = "200"

  response_models = {
    "application/json" = "Empty"
  }

  response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = true
    "method.response.header.Access-Control-Allow-Methods" = true,
    "method.response.header.Access-Control-Allow-Origin"  = true,
  }

  depends_on = [aws_api_gateway_method.cors_method]
}

resource "aws_api_gateway_integration" "cors_integration" {
  rest_api_id      = var.api_id
  resource_id      = var.resource_id
  http_method      = aws_api_gateway_method.cors_method.http_method

  type = "MOCK"

  depends_on = [aws_api_gateway_method.cors_method]
}

resource "aws_api_gateway_integration_response" "cors_integration_response" {
  rest_api_id = var.api_id
  resource_id = var.resource_id
  http_method = aws_api_gateway_method.cors_method.http_method
  status_code = aws_api_gateway_method_response.cors_method_response.status_code

  response_parameters = {
    "method.response.header.Access-Control-Allow-Headers" = "'${join(",", var.headers)}'"
    "method.response.header.Access-Control-Allow-Methods" = "'${join(",", var.methods)}'",
    "method.response.header.Access-Control-Allow-Origin"  = "'${join(",", var.origins)}'",
  }

  depends_on = [aws_api_gateway_method_response.cors_method_response]
}

api/cors/variables.tf

# api/cors/variables.tf
variable "api_id" {}

variable "resource_id" {}

variable "origins" {
  type    = list(string)
  default = ["http://localhost:3000"]
}

variable "methods" {
  type = list(string)
}

variable "headers" {
  type    = list(string)
  default = ["Content-Type", "X-Amz-Date", "Authorization", "X-Api-Key", "X-Amz-Security-Token"]
}

api/main.tf(相关片段)

# api/main.tf

# API, other API resources, deployment, stage also defined here

# /users/{username}/follow
resource "aws_api_gateway_resource" "follow" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  parent_id   = aws_api_gateway_resource.username.id
  path_part   = "follow"
}

module "FollowCORS" {
  source      = "./cors"
  api_id      = aws_api_gateway_rest_api.api.id
  resource_id = aws_api_gateway_resource.follow.id
  methods     = ["DELETE", "OPTIONS", "PUT"]
}

Lambda函数代码(对应端点)

// Lambda for this endpoint/method

const AWS = require('aws-sdk');

// Set up DynamoDB DocumentClient

exports.handler = async (event) => {
  let body = {};
  let statusCode = 200;
  const headers = {
    'Access-Control-Allow-Headers':
      'Accept,Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token',
    'Access-Control-Allow-Methods': 'DELETE,OPTIONS,PUT',
    'Access-Control-Allow-Origin': '*', // temporary update as per suggestion
    'Content-Type': 'application/json',
    Vary: 'Origin',
  };

  // original `Access-Control-Allow-Origin` setting
  // const allowedOrigins = ['http://localhost:3000'];
  // const origin = event.headers.origin || '';
  // if (allowedOrigins.includes(origin)) {
  //   headers['Access-Control-Allow-Origin'] = origin;
  // }

  console.log('Event:\n', event);

  // Check/Get `cognito:username` from event

  try {
    // DELETE operation on DynamoDB table

    body.isFollowing = false;
  } catch (err) {
    console.error('Error:\n', err);

    statusCode = 500;
    body = { error: { message: err.message } };
  }

  return { statusCode, headers, body: JSON.stringify(body) };
};

前端请求代码

Axios DELETE请求

axios
  .delete(`${API_BASE_URL}/users/testuser/follow`, {
    headers: {
      Authorization: `Bearer ${id_token}`,
    },
  })
  .then((response) => {
    console.log(response);
  })
  .catch((error) => {
    console.log(error);
  });

Fetch DELETE请求

fetch(`${API_BASE_URL}/users/testuser/follow`, {
  method: 'DELETE',
  headers: {
     Authorization: `Bearer ${id_token}`,
 },
})
  .then((res) => res.json())
  .then((data) => {
    console.log(data);
  });

问题现象

  • Postman可成功访问所有端点,Next.js应用中GET请求(useSWR、fetch、axios)均正常,但DELETE/PATCH/POST/PUT请求无法发起
  • 收到CORS错误:

Access to fetch at 'https://{API_BASE_URL}/users/testuser/follow' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

  • Fetch请求额外报错:

TypeError: Failed to fetch

  • 请求未到达API,无CloudWatch日志;Network面板中axios请求仅含Referer: http://localhost:3000/,无Origin头,也无响应头
  • Postman测试OPTIONS方法时收到错误:

... not a valid key=value pair (missing equal-sign) in Authorization header ...

环境:Terraform v1.2.7(windows_amd64),hashicorp/aws版本~>4.26


解决方案

1. 修复OPTIONS请求的授权配置

Postman测试OPTIONS出现的Authorization错误,说明OPTIONS方法被错误应用了Cognito授权器。虽然Terraform中显式设置了authorization = "NONE",但可能存在父资源/API级别默认授权器继承,或部署未更新的问题:

  • 登录API Gateway控制台,确认OPTIONS方法的授权类型确实为NONE
  • 在Terraform的aws_api_gateway_deployment资源中添加依赖,确保配置变更后重新部署,比如:
resource "aws_api_gateway_deployment" "api_deployment" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  stage_name  = var.stage_name
  
  depends_on = [
    # 包含所有CORS模块引用
    module.FollowCORS
    # 其他API方法、集成资源
  ]
}

2. 修正CORS响应头格式

CORS规范不允许Access-Control-Allow-Origin设置多个逗号分隔的域名,只能是单个域名或*。当前MOCK集成的拼接方式不符合要求:

  • 修改api/cors/main.tf中的集成响应参数,暂时用*测试:
response_parameters = {
  "method.response.header.Access-Control-Allow-Headers" = "'${join(",", var.headers)}'"
  "method.response.header.Access-Control-Allow-Methods" = "'${join(",", var.methods)}'",
  "method.response.header.Access-Control-Allow-Origin"  = "'*'",
}
  • 若需严格限制Origin,可将OPTIONS集成改为Lambda代理,在Lambda中动态匹配请求的Origin头返回

3. 确保前端请求的Origin头正常发送

Network面板无Origin头可能是Next.js代理配置问题:

  • 确认API_BASE_URL是正确的跨域地址,而非本地代理地址
  • 检查next.config.js中的代理配置,避免转发请求时丢失Origin头

4. 验证部署生效

每次修改Terraform配置后,必须重新执行terraform apply并确认API部署已更新,否则配置不会生效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 10:54:14