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

问题描述

我有一个 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 指的是 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 = "/*",Compress = false,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>

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

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

解决方法

这是通过执行以下操作修复的:

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