Cloudflare R2自定义域名访问控制:从预签名URL到Cloudflare Workers实践方案

2025-04-29T00:00:00Z | 7分钟阅读 | 更新于 2025-04-29T00:00:00Z

@
Cloudflare R2自定义域名访问控制:从预签名URL到Cloudflare Workers实践方案

Cloudflare 全家桶以其强大的基础服务和慷慨的免费套餐,正成为越来越多开发者构建 Web 应用的基石。从 R2 对象存储、Workers 计算到 Pages 静态网站托管,Cloudflare 提供了一站式的解决方案,尤其对于起步阶段的项目,极大地降低了初期投入和后期运维的门槛。

我们团队之前的项目,前端普遍采用 Vue3 构建,编译打包后直接部署到 Cloudflare Pages。配合自定义域名解析,不仅轻松实现了 HTTPS 和 CDN 加速,后续维护也几乎是“零操心”模式。

最近,在设计开发一个基于 OpenAI API 的 AIGC(图生图、文生图)Web 应用时,我们遇到了一个核心需求:如何安全、可靠、低成本地长期存储用户上传和 AI 生成的图片?答案几乎是显而易见的——Cloudflare R2。

Cloudflare R2 Free tier(免费套餐):

Free
Storage 10 GB-month / month
Class A Operations 1 million requests / month
Class B Operations 10 million requests / month
Egress (data transfer to Internet) Free
  • Class A 操作:通常指写入、修改类操作 (如 PutObject, CopyObject)。
  • Class B 操作:通常指读取类操作 (如 GetObject)。

免费的出口流量尤其亮眼,这意味着用户访问存储在 R2 上的资源(如图图片)时,我们无需担心高昂的流量费用。

R2 支持绑定自定义域名。最简单的方式是将你的域名(或子域名,如 images.yourdomain.com)通过 CNAME 解析到 R2 分配的公共访问地址 (public.your-bucket.your-account-id.r2.cloudflarestorage.com)。设置完成后,你就可以通过 https://images.yourdomain.com/your-object.jpg 这样的 URL 来访问 R2 资源了。

然而,便利往往伴随着风险。 这种直接 CNAME 的方式相当于将你的 R2 存储桶“裸奔”在公网上,没有任何权限验证。任何人只要知道你的域名和对象键(文件名),就能直接访问甚至盗用你的资源。对于我们这个 AIGC 应用来说,用户上传和生成的图片往往具有一定的私密性,必须进行严格的访问控制。防盗链、身份验证、访问时效性判断,这些都是必不可少的安全措施。

那么,如何在享受 R2 自定义域名便利的同时,确保资源的安全性呢?

方案一: 预签名URL - 安全但有限制

Cloudflare R2 兼容 AWS S3 API。这意味着我们可以利用 Cloudflare API 或兼容 S3 的 SDK(如 AWS SDK for Go/Java/Python 等)为存储桶中的特定对象生成一个临时的、带有签名凭证和过期时间的访问 URL。

实现流程大致如下:

  1. 前端请求:前端页面加载需要的某个图片。
  2. 后端生成签名: 前端向后端服务(例如,一个 Go 语言实现的 RESTful API)发起请求,告知需要访问哪个图片。
  3. 后端映射R2对象: 后端服务检索图片,映射到对应 R2 对象。
  4. 后端签名: 后端服务使用 R2 的访问密钥(Access Key ID 和 Secret Access Key)为该对象生成一个预签名 URL。这个 URL 包含了访问凭证和设定的过期时间(例如,5 分钟后失效)。
  5. 返回 URL: 后端服务将生成的预签名 URL 返回给前端。
  6. 前端访问: 前端拿到这个临时的、带签名的 URL,通过 HTML 的 <img> 标签或其他方式去请求 R2 资源。

优点:

  • 安全性高: 每个 URL 都是临时的,带有访问凭证和过期时间,有效防止未授权访问和资源盗用。
  • 控制精细: 可以为每个对象、每次请求生成独立的访问凭证。

缺点:

  • 无法使用自定义域名: 生成的预签名 URL 强制使用 R2 的原始访问域名 (your-bucket.your-account-id.r2.cloudflarestorage.com)。如果希望通过自己的品牌域名(如 images.mydomain.com)来访问,并隐藏底层的 R2 桶信息,这种方式就无法满足需求了。

对于有一致性和隐藏底层实现细节的场景,预签名 URL 显然不是完美的解决方案。

方案二:Cloudflare Workers - 自定义域名访问控制

既然预签名 URL 无法满足自定义域名的需求,那有没有一种方法可以让我们鱼与熊掌兼得呢?答案是肯定的:Cloudflare Workers。

Cloudflare Workers 允许我们在 Cloudflare 的边缘节点上运行 JavaScript 代码,拦截并处理 HTTP 请求。

我们可以将自定义域名(如 images.mydomain.com)指向一个部署好的 Worker,这个 Worker 就如同一个智能的“守门人”,负责:

  1. 接收所有通过自定义域名发往 R2 资源的请求。
  2. 验证请求的合法性(例如,检查 Token、校验来源、判断权限等)。
  3. 如果验证通过,则由 Worker 内部去访问实际的 R2 存储桶获取资源。
  4. 将从 R2 获取的数据流式返回给原始请求的客户端。

这样一来,对外的接口始终是我们的自定义域名,而底层的 R2 访问和权限控制逻辑则被封装在 Worker 内部,完美解决了预签名 URL 的限制。

策略A:基于HTTP Header的Token认证(JWT-like)

这是我最初尝试的思路,借鉴了常见的 API 认证方式。

实现流程:

  1. 域名指向: 确保自定义域名 images.mydomain.com 已解析并指向部署好的 Cloudflare Worker。
  2. 前端请求资源: 前端需要访问 R2 上的 image001.png。
  3. 获取 Token: 前端首先从后端服务(或专门的授权服务)获取一个有时效性的认证 Token(类似于 JWT,可以包含用户身份、权限、过期时间等信息)。
  4. 携带 Token 请求: 前端在请求 https://images.mydomain.com/image001.png 时,将获取到的 Token 放入 HTTP 请求的 Header 中,通常是 Authorization: Bearer <your_token>。
  5. Worker 拦截与验证: Cloudflare Worker 接收到请求,从 Authorization Header 中提取 Token。
  6. Worker 校验: Worker 验证 Token 的签名、检查是否过期、解析其中包含的权限信息等。
  7. Worker 代理访问 R2: 验证通过后,Worker 内部构造指向 R2 存储桶中 image001.png 的实际请求(需要配置 Worker 绑定 R2 存储桶的权限),并从 R2 获取数据。
  8. Worker 返回响应: Worker 创建一个新的 HTTP Response,将从 R2 获取到的数据流式传输回给前端。

遇到的问题:

这个方案在逻辑设计和 Worker 代码实现上都没有问题。然而,在前端集成时却遇到了一个问题:浏览器原生 <img> 标签的限制。

项目中,图片通常是直接使用 HTML 的 <img> 标签来加载的:

<img src="https://images.mydomain.com/image001.png" alt="Image">

浏览器在处理 <img> 标签发出的图片请求时,通常不允许我们像使用 fetch 或 XMLHttpRequest 那样,方便地自定义添加 Authorization 这样的 HTTP Header。

这意味着,如果坚持使用 Header 传输 Token,前端的图片加载方式就需要做较大改动:

  1. 不能直接使用 <img>src 属性。
  2. 需要使用 fetch API 发起请求,并在 Header 中带上 Token。
  3. 将 fetch 返回的响应体(Response Body)转换成 Blob 对象。
  4. 使用 URL.createObjectURL() 为 Blob 对象生成一个临时的本地 URL。
  5. 最后将这个 Blob URL 赋值给 <img> 标签的 src 属性。
// 示例:使用 fetch 加载带 Token 的图片
const imageUrl = 'https://images.mydomain.com/image001.png';
const token = 'your_bearer_token';

fetch(imageUrl, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
})
.then(response => response.blob())
.then(blob => {
  const objectURL = URL.createObjectURL(blob);
  const imgElement = document.getElementById('myImageElement'); // 获取<img>元素
  imgElement.src = objectURL;
})
.catch(error => console.error('Error loading image:', error));

虽然可行,但这无疑增加了前端的复杂性,也改变了我们习惯的图片加载方式,对于现有代码的兼容性也不友好。

策略B:基于URL查询参数的Token认证

为了继续沿用简洁的 <img> 标签加载方式,我调整了策略,将认证 Token 从 HTTP Header 移到了 URL 的查询参数(Query Parameter)中。

实现流程:

  1. 域名指向: 同样,自定义域名 images.mydomain.com 指向 Cloudflare Worker。
  2. 前端请求资源: 前端需要访问 R2 上的 image001.png。
  3. 获取带 Token 的 URL: 前端向后端服务请求图片的访问 URL。后端服务生成一个有时效性的 Token,并将其附加到图片的 URL 后面作为查询参数,形成类似 https://images.mydomain.com/image001.png?token=xxxxxxx 的格式。
  4. 后端返回 URL: 后端将这个带有 Token 的完整 URL 返回给前端。
  5. 前端直接使用 URL: 前端拿到这个 URL 后,可以直接将其赋值给 <img> 标签的 src 属性。
  6. Worker 拦截与验证: Cloudflare Worker 接收到请求,从 URL 的查询参数中提取 token。
  7. Worker 校验: Worker 验证 token 的有效性(解密、签名校验、检查过期时间等)。
  8. Worker 代理访问 R2: 验证通过后,Worker 忽略查询参数部分,根据路径 (/image001.png) 从绑定的 R2 存储桶获取对象数据。
  9. Worker 返回响应: Worker 将从 R2 获取的数据流式返回给前端。
<img src="https://images.mydomain.com/image001.png?token=xxxxxxx" alt="Image">

优点:

  • 前端友好: 对前端代码的侵入性最小,可以继续使用标准的 <img> 标签,无需修改图片加载逻辑。
  • 实现简单: 整体流程相对直观。

潜在缺点:

  • Token 暴露在 URL 中: Token 会出现在浏览器地址栏、访问日志、Referrer 头等地方,相对 Header 传输,安全性略低。但通过设置较短的 Token 有效期(例如几分钟),可以有效缓解这个问题。

考虑到我们项目中对前端简洁性的要求,以及 Token 短时效可以接受的安全模型,我们最终选择了基于 URL 查询参数的 Token 认证方案。

总结

Cloudflare R2 提供了极具吸引力的对象存储服务,尤其是其慷慨的免费额度和免出口流量费策略。然而,直接通过 CNAME 绑定自定义域名并公开访问存在严重的安全隐患。

为了实现对 R2 自定义域名的安全访问控制,我们探索了两种主流方案:

  1. 预签名 URL: 安全性高,控制粒度细,但无法与自定义域名直接结合使用,会暴露 R2 的原始域名。
  2. Cloudflare Workers 代理: 可以完美结合自定义域名和访问控制。
  • Header 认证: 理论上更符合 API 认证规范,但与原生 <img> 标签存在兼容性问题,需要前端采用更复杂的加载方式。
  • URL 参数认证: 对前端最友好,可直接用于 <img> 标签,实现简单。需注意 Token 时效性管理以降低暴露风险。

最终,基于对前端易用性的考量,我们采用 Cloudflare Workers 结合 URL 参数 Token 的方式,成功构建了一套既能使用自定义域名,又能精细控制访问权限的 R2 资源服务。

这再次证明了 Cloudflare 生态系统内部组件(R2 + Workers)组合的强大威力与灵活性。

comments powered by Disqus

© 2020 - 2025 Dank's Blog - 发现问题,分享解决.

Powered by Dank

🇨🇳 中文简体
关于我

20多年,依然在写代码的开发者。

  • 2000年参与第一波互联网,太年轻没赚到钱
  • 2004年最早淘宝电商,自建管理系统,20多个加盟商,第一批皇冠店铺
  • 2009年AWS早期用户,云计算技术开发布道
  • 2014年Docker 1.0发布,尝试容器集群运营工具开发
  • 2024年再出发,AI应用EatEase开发者。