Skip to content

存储跨域(CORS)配置与上传排查 ​

更新时间:2026/10/16 09:53:00

平台的文件上传走「预签名直传」:浏览器向后端申请一个预签名地址,然后由浏览器直接把文件传到对象存储, 文件不经过后端。这一步是「页面 → 对象存储」的跨域请求,桶上没有放行跨域规则时,浏览器会在发出真实请求之前就拦下它。

这个问题的麻烦之处在于:「测试连接」是通过的。测试连接是后端服务器发起的探测,不经过浏览器, 天然测不出跨域问题。密钥、权限、桶是否存在它都能测,唯独测不出 CORS。

真实案例:2026-10 有部署使用 Cloudflare R2,在桶的 CORS 策略里把「允许请求头 / 暴露响应头」也写成了 *, 控制台保存时只报一句「此策略无效」,换浏览器、换网络排查了很久。根因是 R2 的策略校验只允许 * 出现在 AllowedOrigins,两个头字段必须逐个列名。

现象 ​

  • 浏览器控制台报 No 'Access-Control-Allow-Origin' header is present on the requested resource (或带 CORS policy 字样的报错);
  • 上传转圈后失败,但后端日志里找不到对应请求,预检阶段就被浏览器拦下了;
  • 「测试连接」通过、密钥权限都正常,上传却必失败;
  • 小程序 / APP 端能传,Web 管理端不能传(只有浏览器环境受同源策略约束)。

先分清是不是 CORS ​

「上传失败」至少有三种成因,混在一起会把排查带偏:

现象真实原因判别方法
控制台报 CORS 字样,后端无请求日志桶的跨域规则未放行见下方「验证」一节
请求一直 pending / 连接超时 / 网络错误预签名地址不可达(服务端点填了内网地址、防火墙拦截)用 curl 直接传预签名地址(服务端请求不受 CORS 限制),能通则与 CORS 无关
上传返回 403 AccessDenied密钥缺权限 / 桶策略拒绝 / 预签名已过期看后端日志或 curl 的真实状态码

一句话判别法:curl 能传成功、浏览器报 CORS = 跨域配置问题;curl 也失败 = 与 CORS 无关,去查端点与权限。

修复 ​

跨域规则就四个要素:来源(管理端与 H5 的地址,联调阶段可用 *)、方法(必须含 PUT)、 允许请求头(*;Cloudflare R2 需逐个列名)、暴露响应头(建议加 ETag)。各要素的取值细节、 各厂商控制台入口与 R2 可粘贴策略见下方。

通用四要素 ​

各厂商控制台的位置与命名不同,但语义一一对应:

配置项建议值说明
来源 Origin / AllowedOrigins管理端与 H5 的访问地址;联调阶段可先用 *只填 scheme://host[:port],不带路径、不带尾斜杠,端口不能用通配符
方法 Methods / AllowedMethodsPUT, GET, HEAD, POST上传走 PUT,读取与回显走 GET/HEAD;只勾 GET 会让上传的预检请求失败
允许请求头 AllowedHeaders*(Cloudflare R2 需逐个列名)至少覆盖浏览器实际发送的请求头,取值方法见下
暴露响应头 ExposeHeadersETag前端要做 ETag/MD5 校验必须显式列出,否则 JS 读不到该响应头

关于来源填 *(允许所有来源):预签名 URL 的签名在 query 参数里、不携带 Cookie,所以通配符可用; 若哪天改成携带 Cookie 的跨域调用,浏览器不允许「通配符 + 凭证」并存,必须改成具体域名。

不知道要放行哪些请求头时不要去猜:开发者工具 → Network → 上传时那条 OPTIONS 预检请求 → Access-Control-Request-Headers 头列出的名字,就是清单。

各厂商入口 ​

厂商位置备注
MinIO无桶级界面,由服务端变量 MINIO_API_CORS_ALLOW_ORIGIN 控制自建场景通常无需配置
阿里云 OSS桶 → 数据安全 → 跨域设置* 可用于来源与两个头字段;除来源和方法外,别忘了单独加暴露响应头 ETag
腾讯云 COS桶 → 安全管理 → 跨域访问 CORS 设置同上;控制台的「暴露 Headers」即 ETag 的位置
Cloudflare R2桶 → Settings → CORS Policy(JSON 编辑)* 只能写在 AllowedOrigins,两个头字段必须逐个列名

其余厂商(华为云 OBS、AWS S3、七牛云 Kodo 等)在各自控制台的桶跨域设置里按上述四要素配置即可,入口名称以官方文档为准。

R2 可粘贴策略 ​

R2 的 CORS Policy 用 JSON 编辑,下面的策略可直接粘贴使用:

json
[
  {
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedHeaders": [
      "Content-Type",
      "Content-Length",
      "Content-MD5",
      "Content-Disposition",
      "Content-Encoding",
      "Cache-Control",
      "Authorization",
      "x-amz-acl",
      "x-amz-date",
      "x-amz-content-sha256"
    ],
    "ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Last-Modified"],
    "MaxAgeSeconds": 3600
  }
]

注意 R2 只有 AllowedOrigins 支持 *,AllowedHeaders / ExposeHeaders 必须逐个列名; 需要放行更多请求头(如自定义元数据 x-amz-meta-* 的具体名字)时逐个追加到 AllowedHeaders。

控制台保存不了时,可改用官方 CLI 设置(报错会精确到字段): npx wrangler r2 bucket cors set <桶名> --file cors.json(文件格式为 {"rules":[{"allowed":{"origins":[...],"methods":[...],"headers":[...]},"exposeHeaders":[...],"maxAgeSeconds":3600}]})。

验证 ​

分两层验证:先确认「服务端链路的预签名地址可用」,再确认「浏览器侧跨域放行」。

bash
# 1. 服务端直接 PUT 预签名地址(不走浏览器, 不受 CORS 限制)
curl -X PUT --upload-file ./test.png "https://<预签名地址>"

# 2. 带 Origin 头观察预检响应(部分厂商对 OPTIONS 直接返回跨域响应头)
curl -sD - -o /dev/null -X OPTIONS \
  -H "Origin: https://admin.example.com" \
  -H "Access-Control-Request-Method: PUT" \
  "https://<预签名地址>" | grep -i "access-control"
  • 第 1 步成功、第 2 步响应里没有 Access-Control-Allow-Origin:跨域规则未生效(或未配),回到「修复」;
  • 两步都失败:不是跨域问题,查服务端点是否浏览器可达、密钥权限与预签名是否过期。

改完规则仍不生效时,通常是三层延迟之一:桶规则传播(官方称极端情况要几十秒)、 浏览器预检缓存(按 MaxAgeSeconds 缓存,用无痕窗口复测)、CDN / 自定义域缓存(需刷新)。