【问题标题】:403 when upload file to S3 bucket using axios使用 axios 将文件上传到 S3 存储桶时出现 403
【发布时间】:2022-01-26 03:12:03
【问题描述】:

我正在使用 axios 将音频文件上传到 AWS s3 存储桶。

工作流程是:React => AWS API Gateway => Lambda。

这是生成 S3 预签名 URL 的后端 Lambda 代码:

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                .bucket(AUDIO_S3_BUCKET)
                .key(objectKey)
                .contentType("audio/mpeg")
                .build();

        PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(10))
                .putObjectRequest(putObjectRequest)
                .build();

        PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(putObjectPresignRequest);

        AwsProxyResponse awsProxyResponse = new AwsProxyResponse();
        awsProxyResponse.setStatusCode(HttpStatus.SC_OK);
        awsProxyResponse.setBody(
                GetS3PresignedUrlResponse.builder()
                        .s3PresignedUrl(presignedPutObjectRequest.url().toString())
                        .build().toString());
return awsProxyResponse;

这里是创建bucket的java代码:

    private void setBucketCorsSettings(@NonNull final String bucketName) {
        s3Client.putBucketCors(PutBucketCorsRequest.builder()
                .bucket(bucketName)
                .corsConfiguration(CORSConfiguration.builder()
                        .corsRules(CORSRule.builder()
                                .allowedHeaders("*")
                                .allowedMethods("GET", "PUT", "POST")
                                .allowedOrigins("*") // TODO: Replace with domain name
                                .exposeHeaders("ETag")
                                .maxAgeSeconds(3600)
                                .build())
                        .build())
                .build());
        log.info("Set bucket CORS settings successfully for bucketName={}.", bucketName);
    }

在我的前端,这里是尝试上传文件的部分:

  const uploadFile = (s3PresignedUrl: string, file: File) => {
    let formData = new FormData();
    formData.append("file", file);
    formData.append('Content-Type', file.type);
    const config = {
        headers: {
          "Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
          //"Content-Type": file.type,
        },
        onUploadProgress: (progressEvent: { loaded: any; total: any; }) => {
            const { loaded, total } = progressEvent;

            let percent = Math.floor((loaded * 100) / total);

            if (percent < 100) {
                setUploadPercentage(percent);
            }
        },
        cancelToken: new axios.CancelToken(
            cancel => (cancelFileUpload.current = cancel)
        )
    };

    axios(
          { 
            method: 'post', 
            url: s3PresignedUrl, 
            data: formData,
            headers: {
              "Content-Type": 'multipart/form-data; boundary=---daba-boundary---'
            }
          }
        )
        .then(res => {
            console.log(res);
            setUploadPercentage(100);

            setTimeout(() => {
                setUploadPercentage(0);
            }, 1000);
        })
        .catch(err => {
            console.log(err);

            if (axios.isCancel(err)) {
                alert(err.message);
            }
            setUploadPercentage(0);
        });
  };

但是,当尝试上传文件时,它返回 403 错误。

如果我使用 fetch 而不是 axios 并且它可以工作,就像这样:

export async function putToS3(presignedUrl: string, fileObject: any) {
  const requestOptions = {
    method: "PUT",
    headers: {
      "Content-Type": fileObject.type,
    },
    body: fileObject,
  };
  //console.log(presignedUrl);
  const response = await fetch(presignedUrl, requestOptions);
 //console.log(response);
  return await response;
}

putToS3(getPresignedUrlResponse['s3PresignedUrl'], values.selectdFile).then(
          (putToS3Response) => {
            console.log(putToS3Response);
            Toast("Success!!", "File has been uploaded.", "success");
          }  
        );

在我看来,这两者之间的唯一区别是:使用fetch时,请求的Content-Type标头是Content-Type: audio/mpeg,但使用axios时,它是Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryClLJS3r5Xetv3rN7

如何使它与 axios 一起工作?我正在切换到 axios,因为它能够监控请求进度,因为我想显示一个上传进度条。

我关注了这个博客,但不确定我错过了什么:https://bobbyhadz.com/blog/aws-s3-presigned-url-react

【问题讨论】:

  • 您在 axios 上使用 method: 'post',您可能想改用 put
  • 我试过了,还是不行。

标签: reactjs amazon-web-services amazon-s3 axios


【解决方案1】:

您在 axios 中使用 POST。应该改为 PUT。

另外,我认为内容类型必须与请求预签名 URL 时指定的类型相匹配,即您正确指出的 audio/mpeg

相应地,您的data 应该只是file,而不是formData

axios(
          { 
            method: 'put', 
            url: s3PresignedUrl, 
            data: file,
            headers: {
              "Content-Type": 'audio/mpeg'
            }
          }
...

【讨论】:

    【解决方案2】:

    检查您的 Lambda 执行角色。它可能是罪魁祸首。也许它没有授予足够的权限来允许将文件放入您的存储桶。

    URL 签名是代表签名者的权力委托,仅限于指定的对象、动作... 签名不会神奇地授予对 S3 的完全读/写权限,即使是对相关的特定对象到预签名的 URL。

    生成签名的“用户”需要足够的权限才能允许您希望通过该预签名 URL 委派的操作。在这种情况下,这是您的 Lambda 函数的执行角色。

    您可以将AmazonS3FullAccess 托管策略添加到执行角色中,看看它是否能解决您的情况。经过几天的挣扎,这种变化使我摆脱了困境。之后,在投入生产之前,将该规则限制为您希望允许上传到的特定存储桶(最小权限原则)。

    如果您使用 SAM 本地仿真进行开发,那么只要您在本地运行函数,这些执行角色似乎就不会被考虑在内;即使没有 S3 权限,签名的链接也可以在该上下文中工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-08
      • 2015-05-05
      • 2018-01-23
      • 1970-01-01
      • 1970-01-01
      • 2018-04-25
      • 2021-09-09
      • 1970-01-01
      相关资源
      最近更新 更多