
浏览器向 COS 上传文件失败,不要先把存储桶改成公有读写。CORS 决定浏览器是否允许网页发起跨域请求、读取响应;签名和权限策略决定请求能否操作对象。这是两道独立检查,放开其中一道不能替代另一道。
本文以网页通过预签名 URL 执行单次 PUT 为例,把故障拆成预检、实际上传、响应头读取三个阶段。下面的域名、桶名和对象路径均为示例,需替换成自己的测试资源;不涉及分块上传。
打开开发者工具 Network 面板,保留日志后重新上传,按对象路径过滤请求。
不要只看 JavaScript 的 Failed to fetch。这个错误本身不能区分网络中断和跨域拦截。浏览器即使不允许脚本读取响应,服务端也可能已经执行了请求;重试之前应核对对象是否存在,避免重复副作用。
先记下三个值:网页的 Origin、实际请求方法、预检里的 Access-Control-Request-Headers。
Origin 由协议、主机和端口组成,不包含页面路径。https://app.example.com 与 http://app.example.com 不同,本地开发端口变化也会改变 Origin。
COS 的 PUT Bucket cors API 使用以下 XML 字段。这个示例仅展示一条规则的形态,不是要求直接覆盖已有配置:
<CORSConfiguration>
<CORSRule>
<AllowedOrigin>https://app.example.com</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedHeader>content-type</AllowedHeader>
<ExposeHeader>ETag</ExposeHeader>
<MaxAgeSeconds>600</MaxAgeSeconds>
</CORSRule>
</CORSConfiguration>AllowedMethod 填实际操作 PUT,不是因为看到预检就填 OPTIONS。AllowedHeader 对应浏览器准备发送的请求头;如果 SDK 还发送其他头,应根据实际预检补齐,不能照抄上面的一项。
ExposeHeader 则控制脚本能读取哪些非默认暴露的响应头。允许发送 content-type 与允许读取 ETag 是两个方向,不要混用。
这里的 600 是示例预检缓存秒数,不是推荐所有业务照搬的固定值。调整规则前先读取、备份现有配置,保留其他站点依赖的规则,避免修好一个前端却破坏另一个。
先选择一个无敏感信息的测试对象地址:
curl -i -X OPTIONS \
'https://examplebucket-1250000000.cos.ap-guangzhou.myqcloud.com/cors-probe.txt' \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: content-type'核对响应里的 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 是否覆盖本次请求。不能仅凭状态码为 2xx 就判断浏览器一定会放行。
curl 不执行浏览器的同源策略,因此“curl 能上传”不能证明网页跨域配置正确。这个命令用于观察服务端如何响应预检,最终仍要回到真实网页验证。
在允许来源的测试网页执行以下代码,signedUrl 应由自己的可信后端生成,且只允许写入指定测试对象。不要把长期密钥放进页面。
async function uploadProbe(signedUrl) {
const response = await fetch(signedUrl, {
method: 'PUT',
headers: { 'Content-Type': 'text/plain' },
body: 'cors-probe\n',
credentials: 'omit'
});
if (!response.ok) {
throw new Error(`上传失败:HTTP ${response.status}`);
}
return { status: response.status, etag: response.headers.get('ETag') };
}如果生成签名时把 Content-Type 纳入签名,上传时必须使用一致的值。请求方法、对象路径或已签名请求头发生变化,都应从签名链路排查,而不是继续扩大 CORS 规则。
若返回成功但 etag 为 null,先在 Network 面板确认服务端确实返回了 ETag,再检查 ExposeHeader。ETag 在这里用于验证响应头可读,不应一概当作文件 MD5。
从允许的来源上传一个小文件,确认 PUT 成功、脚本能读取 ETag,并在对象列表确认文件。再从未授权来源测试,确认浏览器不会向脚本开放跨域访问。后者不代表非浏览器客户端也无法访问,真正的安全边界仍是对象权限与签名。
修改规则后还要考虑预检缓存。清理相关缓存或换一个干净的浏览器会话复测,记录实际 Origin、请求头和响应头;不要为了消除报错把来源永久设成通配符,也不要把私有桶改为公有写。
以上为排障步骤与示例,实际效果需在自己的桶和网页来源上验证。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。