配方:R2 生产交付、跨域与私有上传
分清自定义域名、CORS 与预签名 URL 的职责,把图片、PDF 和附件安全交付到生产环境。
编辑与核验:橙宝书编辑团队 ·
先给结论
公开读取走自定义域名,浏览器直传私有 bucket 时由服务端生成短时预签名 URL,再用精确 CORS 放行你的应用域名。r2.dev 只用于非生产测试;预签名 URL 使用 R2 的 S3 API 域名,不能换成自定义域名。
三个能力不要混用
| 能力 | 解决什么 | 不解决什么 |
|---|---|---|
| 自定义域名 | 公开读取、缓存,以及 WAF、Access、Bot 等 Cloudflare 能力 | 不替代私有对象授权 |
| CORS | 允许指定浏览器来源读取响应或发起上传 | 不是认证;非浏览器客户端不受它保护 |
| 预签名 URL | 在有效期内授权一个具体 S3 API 操作 | 不是永久公开链接,也不能隐藏拿到 URL 的人 |
公开素材
私有上传
私有下载
生产落地
先画出访问矩阵
列出每类对象的读取者、写入者、是否公开、允许的 Content-Type、最大尺寸和保留期。不要让同一个 bucket 同时承担“永久公开素材”和“敏感临时文件”,除非你已设计清晰前缀与授权边界。
给公开读取绑定自定义域名
在 R2 bucket 的设置中绑定已由当前 Cloudflare 账号管理的域名。验证自定义域名后,如果不再需要测试入口,关闭 r2.dev;否则读者仍可能绕过只配置在自定义域名上的保护。
curl -I https://media.example.com/public/sample.webp检查状态码、Content-Type、Cache-Control、ETag 与 CF-Cache-Status,不要只确认“浏览器能打开”。
为浏览器配置最小 CORS
来源必须是精确的 scheme://host[:port],不能带路径。开发域名与生产域名分别列出,不要为了省事使用 * 允许私有上传。
[
{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET", "PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]自定义域名已经缓存过旧的 CORS 响应时,修改配置后还要清理对应缓存对象,再重新发起预检请求。
只在服务端生成预签名 URL
签名前先验证登录状态、租户、对象 key、操作类型、Content-Type 与业务配额。URL 应尽量短效,并通过 HTTPS 返回;R2 预签名 URL 最长可设置为 7 天,但这不是推荐默认值。它支持 GET、HEAD、PUT、DELETE,不支持 S3 的 HTML form POST 上传。
用真实浏览器来源验证两条分支
read -r -s -p '粘贴短时预签名 URL:' R2_SIGNED_URL
curl -i -X OPTIONS "$R2_SIGNED_URL" \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: PUT' \
-H 'Access-Control-Request-Headers: content-type'
unset R2_SIGNED_URL使用服务端刚刚生成、尚未过期的 URL。允许来源应返回匹配的 CORS 头;把 Origin 改成未授权域名后,不应得到同样的放行结果。最后在真实应用中上传一个小文件,并验证错误 Content-Type、过期 URL 与越权 key 都失败。
预签名 URL 是 bearer token
任何拿到 URL 的人都能在有效期内执行被签名的操作。不要写入日志、分析事件或工单正文;不要在浏览器生成签名;不要把账号级 R2 密钥交给客户端。
发布与回退清单
- 先用测试 bucket 和测试域名验证,再配置生产 bucket。
- 保留旧读取入口,直到新域名的命中、错误率和下载完整性稳定。
- 若上传失败,先停止签发新 URL,不要立即删除旧对象。
- 回退时恢复旧应用入口;CORS、DNS 或缓存规则一次只撤回一项并重新验证。
准备迁移已有对象时继续看 R2 迁移手册;先比较成本模型可读 R2、S3、OSS 与 B2 对比。
官方来源
这篇内容帮你完成目标了吗?
内测反馈只在当前浏览器生成,不会自动上传。