如何在 Casdoor 中使用 CDN 托管静态文件:S3、MinIO、Azure Blob 及其他存储提供商
要在 CDN 上托管静态文件,请将它们放入对象存储(Amazon S3、MinIO、Azure Blob Storage、Google Cloud Storage、Alibaba Cloud OSS 或 Tencent Cloud COS),让 CDN 以该存储桶作为源站拉取内容,并提供基于 CDN 域名的 URL。在 Casdoor 中,这只是一个设置项:为存储桶创建一个存储提供商,并在其 Domain 字段中填入 CDN 地址。Casdoor 通过提供商的 API 上传头像和其他文件,并保存以该域名开头的 URL。Casdoor 自身的前端也可以通过 app.conf 中的 frontendCdnUrl 和 staticBaseUrl 从 CDN 加载。
存储提供商概览
存储提供商决定了 Casdoor 将上传的文件写入何处:用户头像、身份证图片、服务条款页面,以及在 Resources 页面上传的任何文件。你将提供商添加到应用中后,该应用中的上传操作就会使用该提供商。存储提供商概述列出了所有类型。下表涵盖了人们通常询问的类型:
| 存储 | Casdoor 提供商类型 | Casdoor 是否使用 S3 API? | 可自托管 | 公共 URL 如何设置 |
|---|---|---|---|---|
| 本地文件系统 | Local File System | 否,磁盘上的普通文件 | 是,即 Casdoor 服务器 | 由 Casdoor 在 /files/ 下提供服务。Domain 为只读 |
| Amazon S3 | AWS S3 | 是 | 否 | Domain:存储桶 URL 或 CloudFront 域名。Endpoint (Intranet):VPC 端点 |
| MinIO | MinIO | 是,使用路径风格请求 | 是 | Domain:MinIO URL 后跟存储桶名称,或 CDN 域名 |
| Alibaba Cloud OSS | Aliyun OSS | 否,使用 OSS SDK | 否 | Domain:CDN 域名。若留空,则变为 bucket.endpoint。Endpoint (Intranet):内部端点 |
| Tencent Cloud COS | Tencent Cloud COS | 否,使用 COS API | 否 | Domain:CDN 域名 |
| Azure Blob Storage | Azure Blob | 否,使用 Blob API | 否 | Domain:自定义域名或 Front Door 域名。Casdoor 会在路径中添加容器名称 |
| Google Cloud Storage | Google Cloud Storage | 否,使用 Google Go 客户端 | 否 | Domain:若留空,则变为 https://storage.googleapis.com。Casdoor 会在路径中添加存储桶名称 |
| Synology NAS | Synology | 否,使用 DSM File Station API | 是 | 无 Domain 字段。URL 基于 Endpoint |
Casdoor 还支持 Qiniu Cloud Kodo、CUCloud OSS 以及 Casdoor(后者将文件存储在另一个 Casdoor 实例中)。针对 Amazon S3、MinIO、Azure Blob、Google Cloud Storage、Alibaba Cloud OSS、Tencent Cloud COS、Synology NAS 和 本地文件系统 的各提供商指南展示了每个字段的取值。
Endpoint 与 Domain 的区别
这两个字段的作用不同,大多数 CDN 问题都源于混淆了它们。
- Endpoint 是 Casdoor 用于访问存储 API 的地址:上传、删除、列表查询。CDN 不涉及此过程。
- 当在
app.conf中设置isCloudIntranet = true时,Endpoint (Intranet) 会替代 Endpoint。这使上传流量保持在提供商的内部网络中,适用于 Casdoor 运行在同一云环境中的情况。该字段适用于 AWS S3、Aliyun OSS、Azure Blob 和 CUCloud OSS。 - Domain 是 Casdoor 保存的 URL 以及浏览器请求的 URL 的基础部分。如果您有 CDN,请在此处填写其域名。
- Path prefix 会添加到每个对象键的前面。如果前缀为
casdoor,则头像最终将位于casdoor/avatar/...下。
Casdoor 根据 Domain 的协议和主机名构建文件 URL,然后是 Domain 的路径,接着是路径前缀,最后是文件路径。如果没有协议,Domain 会自动添加 https://。Domain 的路径部分也会成为对象键的一部分,因此 https://cdn.example.com/casdoor/ 会将文件存储在存储桶的 casdoor/ 目录下。对于 Azure Blob 和 Google Cloud Storage,Casdoor 会在主机名后添加容器或存储桶名称作为第一个路径段。您的 CDN 需要能够服务该路径。
如果 Domain 为空,Casdoor 会在首次使用提供商时从提供商的端点填充它并保存。有时结果可行,例如 OSS 和 GCS。有时则不行:对于不包含存储桶名称的 S3 端点,或者对于 MinIO(其中路径样式请求将存储桶名称放在路径中),生成的 URL 会缺少存储桶名称。请自行设置 Domain,上传一个测试头像,并在其他人使用之前打开 Casdoor 返回的 URL。
Casdoor 用于社交登录复制头像的提供商来自 app.conf 中的 defaultStorageProvider。它是属于 admin 的提供商的名称(参见 配置参考):
defaultStorageProvider = provider_storage_s3
isCloudIntranet = false
公开存储桶、私有存储桶和签名 URL
Casdoor 在用户的 avatar 字段和资源记录中保存一个普通的永久 URL。它不会为这些文件创建签名或过期 URL。因此,Domain 处的内容必须能够响应匿名 GET 请求。有两种方法可以实现这一点。
将对象设为公开。 对于 AWS S3 和 MinIO,Casdoor 使用 public-read 预设 ACL 上传每个对象。对于 Aliyun OSS,客户端库默认执行相同操作。在 S3 上,这与新存储桶的默认设置冲突:根据 AWS 的说法,新存储桶使用 Bucket owner enforced 设置,这会禁用 ACL,而设置 public-read 等 ACL 的上传会因 AccessControlListNotSupported 而失败。这就是为什么 Amazon S3 指南 要求您启用 ACL 并关闭 Block all public access。在 OSS 上,对象 ACL 优先于存储桶 ACL,因此即使在私有存储桶中,public-read 对象也可以被读取。MinIO 不使用 ACL 进行匿名读取,而是使用存储桶策略:
mc anonymous set download myminio/casdoor
Azure 需要在存储账户上允许匿名访问,并将容器设置为 Blob 访问级别。Google Cloud Storage 需要在存储桶上将 Storage Object Viewer 角色 (roles/storage.objectViewer) 授予 allUsers。
保持存储桶私有,仅让 CDN 读取它。 CDN 对其到源站的请求进行签名,而查看者通过 CDN 访问时无需签名。每个供应商都有自己的版本:
- CloudFront 具有源站访问控制 (OAC)。AWS 要求 OAC 使用 Bucket owner enforced,如果您需要 ACL,则使用 Bucket owner preferred。
- 阿里云 CDN 在您授权后可以从未公开的 OSS 存储桶中获取数据。
- 腾讯云 CDN 对私有 COS 存储桶具有回源鉴权功能。
- Azure Front Door Premium 可以通过专用链接连接到存储账户。
- Google 的 Cloud CDN 指南也涵盖了私有存储桶访问,其中 Storage Object Viewer 角色授予服务账户而不是
allUsers。
这隐藏了存储桶自身的 URL,但文件仍可通过 CDN 公开访问。对于 AWS S3 和 Aliyun OSS 类型,由于 Casdoor 设置了 public-read ACL,对象在其存储桶 URL 处也保持可读。
如果头像完全不能公开,则在对象存储前放置 CDN 是错误的工具。将这些文件保留在您控制的存储类型上,并限制谁能访问该主机。
缓存标头和头像陷阱
在 S3 和 MinIO 上,Casdoor 在上传时根据文件扩展名设置 Content-Type。它在任何提供商上都不设置 Cache-Control。这意味着应用 CDN 的默认 TTL。在 CloudFront 上,没有 Cache-Control 的对象会被缓存默认的 TTL,如果不使用缓存策略,则为 24 小时。要按对象控制此行为,请在存储桶中添加 Cache-Control 元数据。对于整个路径,请在 CDN 上使用缓存规则。
头像需要较短的 TTL。当用户更改其图片时,Casdoor 将新文件写入相同的键 avatar/<organization>/<username>.<extension>,并保存相同的 URL。较长的 TTL 意味着人们会看到旧图片,直到其过期或您清除或失效该路径。其他上传在名称已被占用时会获得编号后缀 (-1, -2, ...),因此新上传会获得新的 URL,并且可以长时间缓存。
一种有效的布局:
| 路径 | 建议缓存 |
|---|---|
avatar/* | 短 TTL(分钟),或在更改时清除 |
resource/* 和其他上传 | 长 TTL |
哈希前端资源 (assets/*) | 非常长的 TTL。文件名在每次构建时都会更改 |
CDN 上的 Casdoor 自身前端
除了用户上传之外,还有三种方法可以将 Casdoor 自身的静态文件移出 Casdoor 服务器。
frontendCdnUrl。 如果您从 casdoor-web npm 包安装前端,请使用此项。将其指向 npm CDN,例如:
frontendCdnUrl = "https://cdn.jsdelivr.net/npm/{name}@{version}"
Casdoor 从构建文件夹中的 package.json 读取包名和版本。它将 index.html 中的脚本和样式表链接重写为该 CDN,并将本地路径保留为后备方案。如果文件加载失败,浏览器会从 Casdoor 加载它,并且 cookie 会使该浏览器在会话剩余时间内继续使用本地文件。您自己构建的前端没有这样的 package.json,因此该设置对其无效。
部署脚本。 对于从源码构建的前端,将前端文件托管在 CDN 上解释了 deployment/deploy_test.go。它通过你的某个存储提供商上传 web/build/assets/,并将 index.html 指向该提供商的 Domain(必须以 / 结尾)。Vite 会对这些文件名进行哈希处理,因此它们适合上表中的长 TTL。
staticBaseUrl。 此设置指定默认图片(如徽标、提供商图标和默认头像)的来源。默认值为 https://cdn.casbin.org,它提供 casbin/static 仓库的内容。在没有互联网访问权限的网络中,你可以自行托管该仓库的副本,并将 staticBaseUrl 指向它,如在内网托管静态文件中所述。
选择一种方案
- 单服务器、小规模用户群:
Local File System。如果 Casdoor 运行在容器中,请将files目录挂载到卷上。Casdoor 会向这些文件发送X-Content-Type-Options: nosniff和一个沙箱化的Content-Security-Policy,因此上传的 HTML 或 SVG 文件无法在你的 Casdoor 域名上执行脚本。 - 自托管且兼容 S3:
MinIO。文档也建议其他兼容 S3 的存储使用此类型。 - 已在云端: 使用该云的对象存储和 CDN,在 Casdoor 运行的同一云中设置 Endpoint (Intranet),并将 Domain 设置为 CDN。
参考资料
- Casdoor 存储提供商及源代码:
object/storage.go、storage/、routers/frontend_cdn.go - AWS:控制对象所有权并禁用 ACL,限制对 Amazon S3 源的访问,管理内容在缓存中的保留时间
- MinIO:
mc anonymous set - Microsoft:配置容器和 Blob 的匿名读取访问,将 Azure Storage 账户与 Azure Front Door 集成
- Google Cloud:使数据公开,使用后端存储桶设置 Cloud CDN
- Alibaba Cloud:对象 ACL,从私有 OSS 存储桶回源
- Tencent Cloud:为 COS 设置 CDN 加速