AWS Cloudfront for S3 支持的网站 + Rest API:(错误 - MethodNotAllowed / 指定的方法不允许针对此资源)

AWS Cloudfront for S3 backed website + Rest API: (Error - MethodNotAllowed / The specified method is not allowed against this resource)

我有一个 AWS S3 支持的静态网站和一个 RestApi。我正在为静态网站和 RestApi 配置单个 Cloudfront Distribution。我已经为 S3 来源和 RestApi 来源完成了 OriginConfigs。我正在使用 AWS CDK 在代码中定义基础设施。

采用了这篇文章的方法:https://dev.to/evnz/single-cloudfront-distribution-for-s3-web-app-and-api-gateway-15c3]

API定义在相对路径/r/<resourcename>/r/api/<methodname>下。例如,/r/Account 引用帐户资源,/r/api/Validate 引用名为 Validate 的 rpc 样式方法(在本例中为 HTTP POST 方法)。实现资源方法的 Lambda 方法配置了适当的 PREFLIGHT OPTIONS,静态网站的 url 列在该资源的允许来源中。例如:/r/api/Validate 方法 lambda 具有

exports.main = async function(event, context) {
  try {
    var method = event.httpMethod;

    if(method === "OPTIONS") {
      const response = {
        statusCode: 200,
        headers: {
          "Access-Control-Allow-Headers" : "*",
          "Access-Control-Allow-Credentials": true,
          "Access-Control-Allow-Origin": website_url,
          "Vary": "Origin",
          "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE"
        }
      };
      return response;
    } else if(method === "POST") {
      ...
    }
   ...
}

API 和网站部署正常。这是 CDK 部署代码片段。

        const string api_domain = "myrestapi.execute-api.ap-south-1.amazonaws.com";
        const string api_stage = "prod";

        internal WebAppStaticWebsiteStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            // The S3 bucket to hold the static website contents
            var bucket = new Bucket(this, "WebAppStaticWebsiteBucket", new BucketProps {
                PublicReadAccess = false,
                BlockPublicAccess = BlockPublicAccess.BLOCK_ALL,
                RemovalPolicy = RemovalPolicy.DESTROY,
                WebsiteIndexDocument = "index.html",
                Cors = new ICorsRule[] {
                    new CorsRule() {
                        AllowedHeaders = new string[] { "*" },
                        AllowedMethods = new HttpMethods[] { HttpMethods.GET, HttpMethods.POST, HttpMethods.PUT, HttpMethods.DELETE, HttpMethods.HEAD },
                        AllowedOrigins = new string[] { "*" }
                    }
                }
            });

            var cloudfrontOAI = new OriginAccessIdentity(this, "CloudfrontOAI", new OriginAccessIdentityProps() {
                Comment = "Allows cloudfront access to S3"
            });

            bucket.AddToResourcePolicy(new PolicyStatement(new PolicyStatementProps() {
                Sid = "Grant cloudfront origin access identity access to s3 bucket",
                Actions = new [] { "s3:GetObject" },
                Resources = new [] { bucket.BucketArn + "/*" },
                Principals = new [] { cloudfrontOAI.GrantPrincipal }
            }));

            // The cloudfront distribution for the website
            var distribution = new CloudFrontWebDistribution(this, "WebAppStaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
                ViewerProtocolPolicy = ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
                DefaultRootObject = "index.html",
                PriceClass = PriceClass.PRICE_CLASS_ALL,
                GeoRestriction = GeoRestriction.Whitelist(new [] {
                    "IN"
                }),
                OriginConfigs = new [] {
                    new SourceConfiguration() {
                        CustomOriginSource = new CustomOriginConfig() {
                            OriginProtocolPolicy = OriginProtocolPolicy.HTTPS_ONLY,
                            DomainName = api_domain,
                            AllowedOriginSSLVersions = new OriginSslPolicy[] { OriginSslPolicy.TLS_V1_2 },
                        },
                        Behaviors = new IBehavior[] {
                            new Behavior() {
                                IsDefaultBehavior = false,
                                PathPattern = $"/{api_stage}/r/*",
                                AllowedMethods = CloudFrontAllowedMethods.ALL,
                                CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
                                DefaultTtl = Duration.Seconds(0),
                                ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
                                    QueryString = true,
                                    Headers = new string[] { "Authorization" }
                                }
                            }
                        }
                    },
                    new SourceConfiguration() {
                        S3OriginSource = new S3OriginConfig() {
                            S3BucketSource = bucket,
                            OriginAccessIdentity = cloudfrontOAI
                        },
                        Behaviors = new [] {
                            new Behavior() {
                                IsDefaultBehavior = true,
                                //PathPattern = "/*",
                                DefaultTtl = Duration.Seconds(0),
                                Compress = false,
                                AllowedMethods = CloudFrontAllowedMethods.ALL,
                                CachedMethods = CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS
                            }
                        },
                    }
                }
            });

            // The distribution domain name - output
            var domainNameOutput = new CfnOutput(this, "WebAppStaticWebsiteDistributionDomainName", new CfnOutputProps() {
                Value = distribution.DistributionDomainName
            });

            // The S3 bucket deployment for the website
            var deployment = new BucketDeployment(this, "WebAppStaticWebsiteDeployment", new BucketDeploymentProps(){
                Sources = new [] {Source.Asset("./website/dist")},
                DestinationBucket = bucket,
                Distribution = distribution
            });
        }

我遇到以下错误(从浏览器控制台错误日志中提取):

bundle.js:67 POST https://mywebapp.cloudfront.net/r/api/Validate 405

bundle.js:67 
<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>MethodNotAllowed</Code>
  <Message>The specified method is not allowed against this resource.</Message>
  <Method>POST</Method>
  <ResourceType>OBJECT</ResourceType>
  <RequestId>xxxxx</RequestId>
  <HostId>xxxxxxxxxxxxxxx</HostId>
</Error>

预期的流程是 POST 对 https://mywebapp.cloudfront.net/r/api/Validate 的调用(使用 fetch() api 进行)由云端转发到 RestApi 后端。看起来 Cloudfront 正在执行此操作,但后端正在返回错误(根据错误消息)。

我错过了什么?我该怎么做?

已通过执行以下操作解决此问题:

  1. 移至 Distribution 构造(根据 AWS 文档,它是接收最新更新时使用的构造)。
  2. 添加 CachePolicy 和 OriginRequestPolicy 来控制 Cookie 转发和 Header 转发