预签名的S3上传URL在到期时间之前过期

huangapple go评论87阅读模式
英文:

Presigned S3 upload url expires before expiration time

问题

我有一个用于生成预签名上传URL的Lambda函数。以下是我用于获取URL的代码:

    urlExpiresAt := time.Duration(10) * time.Second
    s3Session := s3.New(awsSession)
    request, _ := s3Session.PutObjectRequest(&s3.PutObjectInput{
        Bucket:      aws.String(awssettings.FileStorageBucketName),
        Key:         aws.String(fmt.Sprintf("%s/%s", *cognitoUser.Username, filename)),
        ContentType: aws.String(uploadContentType),
    })

    uploadUrl, err := request.Presign(urlExpiresAt)

在我的前端代码中,我获取该URL并立即尝试发送上传的POST请求:

    try {
      const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        variables: {
          filename,
        },
      });

      const formdata = new FormData();
      formdata.append("file", file);
      const ajaxRequest = new XMLHttpRequest();
      ajaxRequest.upload.addEventListener("progress", progressHandler, false);
      ajaxRequest.addEventListener("load", completeHandler, false);
      ajaxRequest.addEventListener("error", errorHandler, false);
      ajaxRequest.addEventListener("abort", abortHandler, false);
      ajaxRequest.open("POST", fileUploadUrl);
      ajaxRequest.send(formdata);
    } catch (err) {
      console.log(err);
    }

为了测试目的,我将我的存储桶配置为公开可访问:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "UploadFiles",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "<bucketARN>/*"
        }
    ]
}

为了测试目的,CORS配置如下:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "POST"
        ],
        "AllowedOrigins": [
            "http://localhost:8080"
        ],
        "ExposeHeaders": []
    }
]

CORS预检请求返回状态码200,但发送文件内容的POST请求被拒绝,状态码为403,并显示以下错误:

<Error>
  <Code>AccessDenied</Code>
  <Message>Request has expired</Message>
  <X-Amz-Expires>5</X-Amz-Expires>
  <Expires>2022-09-07T04:14:50Z</Expires>
  <ServerTime>2022-09-13T06:40:18Z</ServerTime>
...
</Error>

根据这个错误信息,看起来URL在预签名之前就已经过期了,这是不合理的。

我在这里做错了什么?

英文:

I have a lambda to generate pre-signed upload URL. Here is the code I use to get URL:

    urlExpiresAt := time.Duration(10) * time.Second
	s3Session := s3.New(awsSession)
	request, _ := s3Session.PutObjectRequest(&amp;s3.PutObjectInput{
		Bucket:      aws.String(awssettings.FileStorageBucketName),
		Key:         aws.String(fmt.Sprintf(&quot;%s/%s&quot;, *cognitoUser.Username, filename)),
		ContentType: aws.String(uploadContentType),
	})

	uploadUrl, err := request.Presign(urlExpiresAt)

On my front-end I retrieve that URL and immediately try to send upload POST request to it:

    try {
      const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        variables: {
          filename,
        },
      });

      const formdata = new FormData();
      formdata.append(&quot;file&quot;, file);
      const ajaxRequest = new XMLHttpRequest();
      ajaxRequest.upload.addEventListener(&quot;progress&quot;, progressHandler, false);
      ajaxRequest.addEventListener(&quot;load&quot;, completeHandler, false);
      ajaxRequest.addEventListener(&quot;error&quot;, errorHandler, false);
      ajaxRequest.addEventListener(&quot;abort&quot;, abortHandler, false);
      ajaxRequest.open(&quot;POST&quot;, fileUploadUrl);
      ajaxRequest.send(formdata);
    } catch (err) {
      console.log(err);
    }

for testing purposes I configured my bucket to be publically accessable:

{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;UploadFiles&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:PutObject&quot;,
            &quot;Resource&quot;: &quot;&lt;bucketARN&gt;/*&quot;
        }
    ]
}

and CORS for testing purposes is configured like this:

[
    {
        &quot;AllowedHeaders&quot;: [
            &quot;*&quot;
        ],
        &quot;AllowedMethods&quot;: [
            &quot;POST&quot;
        ],
        &quot;AllowedOrigins&quot;: [
            &quot;http://localhost:8080&quot;
        ],
        &quot;ExposeHeaders&quot;: []
    }
]

CORS pre-flight request returns status 200, however POST request to send file content gets rejected with status 403 and following error:

&lt;Error&gt;
  &lt;Code&gt;AccessDenied&lt;/Code&gt;
  &lt;Message&gt;Request has expired&lt;/Message&gt;
  &lt;X-Amz-Expires&gt;5&lt;/X-Amz-Expires&gt;
  &lt;Expires&gt;2022-09-07T04:14:50Z&lt;/Expires&gt;
  &lt;ServerTime&gt;2022-09-13T06:40:18Z&lt;/ServerTime&gt;
...
&lt;/Error&gt;

reading this error it looks like URL has expired before it was pre-signed which doesn't make sense.

What am I doing wrong here?

答案1

得分: 0

我发现我的解决方案有两个问题:

首先,生成的预签名URL使用的是PUT方法,而不是POST

其次,我使用的GraphQL客户端AWSAppSyncClient默认缓存查询结果,所以我只需要通过fetchPolicy: "network-only"禁用缓存。

    const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        fetchPolicy: "network-only",       // <- 这一行
        variables: {
          filename,
        },
      });
英文:

I figured out there was 2 issues with my solution:

First, presigned URL generated to use PUT method, not POST

Second, AWSAppSyncClient I use as a GraphQL client caches query results by default so all I needed is to disable caching with fetchPolicy: &quot;network-only&quot;

    const {
        data: { fileUploadUrl },
      } = await graphQlClient.query({
        query: gql(getFileUploadUrlQuery),
        fetchPolicy: &quot;network-only&quot;,       // &lt;- THIS LINE
        variables: {
          filename,
        },
      });

huangapple
  • 本文由 发表于 2022年9月13日 15:07:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/73698798.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定