簡體   English   中英

使用 axios 將文件上傳到 S3 存儲桶時出現 403

[英]403 when upload file to S3 bucket using axios

我正在使用 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;

這是創建存儲桶的 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 header 是Content-Type: audio/mpeg ,但是當使用 axios 時,它是Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryClLJS3r5Xetv3rN7 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryClLJS3r5Xetv3rN7

如何讓它與 axios 一起使用? 我正在切換到 axios,因為它能夠監控請求進度,因為我想顯示上傳進度條。

我關注了這個博客,但不確定我錯過了什么: https://bobbyhadz.com/blog/aws-s3-presigned-url-react

您沒有將任何答案標記為已接受,所以我猜您沒有解決它。

對於那里的任何未來觀眾。 您收到403 forbidden錯誤的原因是您的服務器端和客戶端中的Content-Type不匹配。 我假設您正確設置了 AWS 策略。

您在后端的代碼應如下所示:

const presignedPUTURL = s3.getSignedUrl("putObject", {
  Bucket: "bucket-name",
  Key: String(Date.now()),
  Expires: 100,
  ContentType: "image/png", // important
});

在前端(假設您使用的是 axios):

const file = e.target.files[0]

const result = await axios.put(url, file, {
  withCredentials: true,
  headers: { "Content-Type": "image/png" },
});

實際上,您通常必須發送文件類型以在POST正文或其他內容中生成預簽名的 url,然后在 axios 中執行file.type以獲取上傳文件的文件類型。

您在 axios 中使用 POST。 應該改為 PUT。

此外,我認為內容類型必須與請求預簽名 URL 期間指定的內容類型相匹配,正如您正確指出的那樣,這是audio/mpeg

相應地,您的data應該只是file ,而不是formData

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

檢查您的 Lambda 執行角色。 它可能是罪魁禍首。 也許它沒有授予足夠的權限來允許將文件放入您的存儲桶。

URL signing is a delegation of power on behalf of the signer, which is restricted to a specified object, action... Signing does not magically grants full read/write permissions on S3, even on the specific object related to the presigned URL.

生成簽名的“用戶”需要足夠的權限以允許您希望通過該預簽名的 URL 委派的操作。 在這種情況下,這是您的 Lambda function 的執行角色。

您可以將AmazonS3FullAccess托管策略添加到執行角色,看看它是否能解決您的情況。 經過幾天的奮斗,這種變化使我擺脫了困境。 之后,在投入生產之前,將該規則限制為您希望允許上傳到的特定存儲桶(最小權限原則)。

如果您使用 SAM 本地仿真進行開發,那么只要您在本地運行函數,這些執行角色似乎就不會被考慮在內; 即使沒有 S3 權限,簽名的鏈接也可以在該上下文中工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM