You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向Digital Ocean预签名URL发起PUT请求时缺失Access-Control-Allow-Origin头

问题:预签名URL上传文件时的CORS错误与ACL权限异常

我通过NodeJS调用DigitalOcean API(基于AWS SDK S3)生成预签名URL,传递给前端Angular应用后,前端发起PUT请求上传文件时触发CORS错误,Chrome和Firefox均提示:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource...
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 400.

同时用Postman发起PUT请求时,尽管生成预签名URL时设置了ACL为public-read,文件最终仍为私有状态。


已配置的CORS规则

控制面板配置

Origin : * 
Allowed Methods : GET, PUT
Allowed Headers : *
Access Control Max Age: 300

上传的cors.xml文件

<?xml version="1.0" encoding="UTF-8"?>
 <CORSConfiguration>
  <CORSRule>
     <AllowedOrigin>*</AllowedOrigin>
     <AllowedMethod>GET</AllowedMethod>
     <AllowedMethod>HEAD</AllowedMethod>
     <AllowedMethod>PUT</AllowedMethod>
     <AllowedMethod>POST</AllowedMethod>
     <MaxAgeSeconds>3000</MaxAgeSeconds>
     <AllowedHeader>*</AllowedHeader>
   </CORSRule>
  </CORSConfiguration>

相关代码

NodeJS后端代码

exports.getPreSignedUrl = async (fileName, contentType) => {
    const params = {
      Bucket: digitalocean.BUCKET_NAME,
      Key: fileName,
      ContentType: contentType,
      ACL: 'public-read'
    };
  
    return await getSignedUrl(s3Client, new PutObjectCommand(params), { expiresIn: 15 * 60 });
}

router.post("/getPreSignedUrl", async (req, res) => {
    const {fileName, contentType} = req.body
    const signedUrl = await digitalocean.getPreSignedUrl(fileName,contentType)
    res.status(200).send({ signedUrl });
});

Angular前端代码

onSave() {
    if (this.isLoading) { return }
    if (!this.files.length) {
      this._snackBar.open("You need to choose file first!", 'Close', {
        duration: 2000,
      });
      return;
    }
    const file = this.files[0];
    const contentType = file.type;
    const fileName = 'images-' + `${new Date().getTime()}-${file.name.toLowerCase()}`
    this.isLoading = true;
    this.itemService
      .getPreSignedUrl({ fileName, contentType : '*' })
      .subscribe((res: { signedUrl: string }) => {
        const signedUrl = res.signedUrl
        this.http.put(signedUrl, file) // PUT请求上传文件
          .subscribe((output) => {
            this.itemService
              .saveTechLink({ fileName }, this.data.itemId)
              .pipe(take(1))
              .subscribe((res: { sharedLink: string }) => {
                this.isLoading = false;
                this.dialogRef.close(res.sharedLink)
              })
          },
            (error) => {
              console.error('Error uploading file:', error);
            }
          );
      })
  }

问题排查与解决方案

1. CORS错误修复

(1)ContentType参数不匹配

前端调用接口时传入的contentType是*,但生成预签名URL时指定了ContentType参数,而浏览器上传文件时会自动带上真实的文件类型请求头,导致签名验证失败(签名包含ContentType参数),返回400错误的同时CORS头也无法正确返回。

修复操作:
修改Angular代码,传入真实的文件contentType而非*:

// 替换原代码中的调用行
this.itemService.getPreSignedUrl({ fileName, contentType }) // 使用file.type的真实值

(2)确保CORS规则生效

DigitalOcean Spaces的CORS配置可能需要数分钟生效,建议:

  • 检查控制面板的CORS规则是否保存正确,删除重复或冲突的规则
  • 控制面板配置与cors.xml文件二选一即可,无需同时配置
  • 用curl验证CORS配置:
curl -X OPTIONS -H "Origin: https://你的前端域名" -H "Access-Control-Request-Method: PUT" https://你的space域名/测试文件名

2. ACL权限不生效修复

(1)检查IAM权限

生成预签名URL的IAM用户必须拥有PutObject和PutObjectAcl权限,否则无法设置文件的公开权限。

(2)避免请求头冲突

不要在PUT上传请求中手动添加x-amz-acl请求头,否则会覆盖预签名URL中指定的ACL参数,导致权限异常。

(3)验证签名参数

检查生成的预签名URL中是否包含x-amz-acl=public-read参数,若无则说明ACL参数未正确加入签名逻辑,可在NodeJS代码中打印params确认参数是否正确。

3. 前端请求额外注意事项

Angular的HttpClient.put请求需保持与预签名URL的参数一致:

  • 不要手动设置Content-Type头,浏览器会自动匹配文件类型与预签名参数
  • 若需自定义请求头,必须在生成预签名URL时将这些头加入params的Headers字段中

内容的提问来源于stack exchange,提问作者Umamaheswararao Meka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:45:23