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

配置CORS后DigitalOcean Spaces预签名链接上传仍报CORS错误

问题描述

已在DigitalOcean Spaces控制台将CORS配置最大化放宽,后端通过以下代码生成预签名URL:

import { S3Client as S3ClientConstructor } from '@aws-sdk/client-s3';
import { PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const Spaces = new S3ClientConstructor({
  endpoint: 'https://sfo2.digitaloceanspaces.com',
  region: 'sfo2',
  forcePathStyle: false,
  credentials: {
    accessKeyId: process.env.DO_ACCESS_KEY_ID,
    secretAccessKey: process.env.DO_SECRET_ACCESS_KEY
  }
});

const uploadUrl = await getSignedUrl(
    Spaces,
    new PutObjectCommand({
      Bucket: 'my bucket',
      ACL: 'public-read',
      // Folder
      Key: 'dev'
    }),
    {
      // 10min
      expiresIn: 600
    }
  );

前端使用HTML文件选择器上传的代码如下:

<input
          className="hidden"
          onChange={async function upload(e) {
            setLoading(true);

            if (!e.target.files?.length) {
              // User canceled the file browser.
              return;
            }

            try {
              const uploadUrl = await fetchPresignedUrl();

              const file = e.target.files[0];
              const formData = new FormData();
              formData.append('file', file);

              const res = await fetch(uploadUrl, {
                method: 'PUT',
                body: formData,
                headers: {
                  'Content-Type': file.type,
                  'x-amz-acl': 'public-read'
                }
              });

              console.log('res.status:', res.status);
            } finally {
              setLoading(false);
            }
          }}
          id="upload-button"
          type="file"
        />

但上传时仍出现CORS错误:(Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 403,脱敏后的预签名URL为:

https://MY_BUCKET_HERE.sfo2.digitaloceanspaces.com/dev?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=******Faws4_request&X-Amz-Date=20230228T194311Z&X-Amz-Expires=600&X-Amz-Signature=***********&X-Amz-SignedHeaders=host&x-amz-acl=public-read&x-id=PutObject
解决办法

1. 修正前端请求格式

预签名PutObject请求不需要封装FormData,直接将文件作为请求体即可,同时要避免重复设置已包含在签名中的头部:

  • 移除FormData的使用,直接传入file作为body
  • 删除手动设置的x-amz-acl头部(预签名URL已携带该参数,重复设置会导致签名验证失败)

修改后的前端代码:

<input
          className="hidden"
          onChange={async function upload(e) {
            setLoading(true);

            if (!e.target.files?.length) {
              // User canceled the file browser.
              return;
            }

            try {
              const uploadUrl = await fetchPresignedUrl();

              const file = e.target.files[0];

              const res = await fetch(uploadUrl, {
                method: 'PUT',
                body: file,
                headers: {
                  'Content-Type': file.type
                }
              });

              console.log('res.status:', res.status);
            } finally {
              setLoading(false);
            }
          }}
          id="upload-button"
          type="file"
        />

2. 调整预签名URL的Key参数

当前后端代码中Key: 'dev'是目录名,需要指定具体文件名,否则会将文件上传为目录,同时可能影响签名有效性。建议动态生成包含文件名的Key:

// 可从前端传递文件名或后端生成唯一标识
const fileName = 'user-upload.jpg'; 
const uploadUrl = await getSignedUrl(
    Spaces,
    new PutObjectCommand({
      Bucket: 'my bucket',
      ACL: 'public-read',
      Key: `dev/${fileName}`
    }),
    {
      expiresIn: 600
    }
  );

3. 验证CORS配置完整性

确保Spaces的CORS配置包含以下规则:

  • 允许来源:*
  • 允许方法:OPTIONS、PUT
  • 允许头部:Content-Type
  • 最大预检缓存时长:设置为3600等合理值

4. 排查签名验证问题

403状态码通常是签名不匹配导致,此时Spaces返回的响应可能未携带CORS头部,进而触发浏览器的CORS错误。需确认:

  • 后端的Access Key和Secret Key拥有PutObject权限
  • Bucket名称正确(需小写)
  • 预签名时的endpoint和region与Bucket所在区域一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 13:08:14