本页面简要介绍签名网址以及将其用于 Cloud CDN 的说明。拥有签名网址的任何人都会获得限时的资源访问权限,无论该用户是否拥有 Google 账号都是如此。
签名网址提供可用于发出请求的有限权限和时间。签名网址的查询字符串中包含身份验证信息,这样,用户不需要提供凭据也可对资源执行特定操作。 生成签名网址时,您需要指定一个用户或服务账号,且该用户或服务账号具有的权限必须足以发出与该网址相关的请求。
生成签名网址后,拥有该网址的任何人都可以使用该网址,在指定的时间段内执行指定的操作(例如读取对象)。
签名网址还支持可选的 URLPrefix
参数,允许您提供访问具有相同前缀的多个网址的权限。
如果要限制对特定网址前缀的访问权限,请考虑使用签名 Cookie。
准备工作
在使用签名网址之前,请执行以下操作:
确保已启用 Cloud CDN;如需了解相关说明,请参阅使用 Cloud CDN。您可以先在后端配置签名网址,然后再启用 Cloud CDN;在启用 Cloud CDN 之前,相关配置不会生效。
如有必要,请更新到 Google Cloud CLI 的最新版本:
gcloud components update
如需查看概览,请参阅签名网址和签名 Cookie。
配置签名请求密钥
为签名网址或签名 Cookie 创建密钥需要执行几个步骤,详情请参阅下面几部分内容。
安全注意事项
在下列情况下,Cloud CDN 不会验证请求:
- 请求未签名。
- 请求的后端服务或后端存储分区未启用 Cloud CDN。
系统必须始终先在源站验证签名的请求,然后再传送响应。这是因为源站可用于传送签名的和未签名的混合内容,而且客户端可能会直接访问源站。
- Cloud CDN 不会屏蔽没有
Signature
查询参数或Cloud-CDN-Cookie
HTTP Cookie 的请求。它会拒绝请求参数无效(或格式不正确)的请求。 - 当您的应用检测到无效签名时,请确保其使用
HTTP 403 (Unauthorized)
响应代码来作出响应。HTTP 403
响应代码不可缓存。 - 签名请求和未签名请求的响应会单独缓存,因此对有效签名请求的成功响应绝不会用于传送未签名的请求。
- 如果您的应用向无效请求发送了可缓存的响应代码,则未来的有效请求可能会被错误地拒绝。
对于 Cloud Storage 后端,请务必移除公共访问权限,以便 Cloud Storage 可以拒绝缺少有效签名的请求。
下表总结了这种行为。
请求具有签名 | 缓存命中 | 行为 |
---|---|---|
否 | 否 | 转发到后端源站。 |
否 | 是 | 从缓存传送。 |
是 | 否 | 验证签名。如果有效,则转发到后端源站。 |
是 | 是 | 验证签名。如果有效,则从缓存传送。 |
创建签名请求密钥
如需启用对 Cloud CDN 签名网址和签名 Cookie 的支持,您可以对启用了 Cloud CDN 的后端服务和/或后端存储分区创建一个或多个密钥。
您可以根据自己的安全需求,为每个后端服务或后端存储分区创建和删除密钥。每个后端最多可以同时配置三个密钥。我们建议您定期轮换密钥,即删除最旧的密钥,然后添加新密钥,并在为网址或 Cookie 签名时使用新密钥。
您可以在多个后端服务和后端存储分区中使用相同的密钥名称,因为各组密钥是彼此独立的。密钥名称最长为 63 个字符。要命名密钥,请使用字符 A-Z、a-z、0-9、_(下划线)、-(连字符)。
创建密钥后,务必妥善保管好密钥,因为只要拥有其中一个密钥,任何人都可以创建 Cloud CDN 接受的签名网址或签名 Cookie,直到该密钥从 Cloud CDN 中删除为止。密钥存储在生成签名网址或签名 Cookie 的计算机上。Cloud CDN 也会存储这些密钥以验证请求签名。
为了确保密钥的机密性,请勿在任何 API 请求的响应中包含密钥值。如果您丢失了某个密钥,则必须创建一个新密钥。
如需创建已签名的请求密钥,请按以下步骤操作。
控制台
- 在 Google Cloud 控制台中,转到 Cloud CDN 页面。
- 点击您要为其添加密钥的来源的名称。
- 在来源详情页面上,点击修改按钮。
- 在 Origin basics(来源基本信息)部分中,点击下一步以打开主机和路径规则部分。
- 在主机和路径规则部分中,点击下一步以打开缓存性能部分。
- 在受限内容部分中,选择使用签名网址和签名 Cookie 限制访问权限。
点击添加签名密钥。
- 为新签名密钥指定一个唯一的名称。
在密钥创建方法部分中,选择自动生成。或者,点击让我输入,然后指定签名键值。
对于前一个选项,请将自动生成的签名密钥值复制到一个私有文件中,您可以使用该值来创建签名网址。
点击完成。
在缓存条目最长存在时间部分,输入一个值,然后选择一个时间单位。
点击完成。
gcloud
gcloud
命令行工具会从您指定的本地文件中读取密钥。密钥文件必须按照如下方式创建:生成强随机 128 位代码,使用 base64 对该代码进行编码,然后将字符 +
替换为 -
、将字符 /
替换为 _
。如需了解详情,请参阅 RFC 4648。必须确保密钥具有强随机性。在类似 UNIX 的系统上,您可以使用以下命令生成强随机密钥并将密钥存储在密钥文件中:
head -c 16 /dev/urandom | base64 | tr +/ -_ > KEY_FILE_NAME
如需将密钥添加到后端服务,请执行以下操作:
gcloud compute backend-services \ add-signed-url-key BACKEND_NAME \ --key-name KEY_NAME \ --key-file KEY_FILE_NAME
如需将密钥添加到后端存储分区,请执行以下操作:
gcloud compute backend-buckets \ add-signed-url-key BACKEND_NAME \ --key-name KEY_NAME \ --key-file KEY_FILE_NAME
配置 Cloud Storage 权限
如果您使用 Google Cloud Storage,并且限制了可以读取对象的用户,则必须向 Cloud CDN 授予读取对象的权限,方法是将 Cloud CDN 服务账号添加到 Cloud Storage ACL 中。
您无需创建服务账号。当您首次将密钥添加到项目中的后端存储分区时,系统将自动创建服务账号。
在运行以下命令之前,请向项目中的后端存储分区添加至少一个密钥。否则,该命令会失败并显示错误,这是因为您必须先为项目添加一个或多个密钥,系统才会创建 Cloud CDN 缓存填充服务账号。
gcloud storage buckets add-iam-policy-binding gs://BUCKET \ --member=serviceAccount:service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com \ --role=roles/storage.objectViewer
将 PROJECT_NUM
替换为您的项目编号,将 BUCKET
替换为您的存储桶。
Cloud CDN 服务账号 service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com
不会出现在您项目的服务账号列表中。这是因为 Cloud CDN 服务账号归 Cloud CDN 所有,不属于您的项目。
如需详细了解项目编号,请参阅 Google Cloud 控制台帮助文档中的查找项目 ID 和项目编号。
自定义最长缓存时间
无论后端的 Cache-Control
标头如何,Cloud CDN 都会缓存针对签名请求的响应。响应无需重新验证的最长缓存时间由 signed-url-cache-max-age
标志设置,默认设为 1 小时,您可以按照此处所示的方法进行修改。
要设置后端服务或后端存储分区的最长缓存时间,请运行以下命令之一:
gcloud compute backend-services update BACKEND_NAME --signed-url-cache-max-age MAX_AGE
gcloud compute backend-buckets update BACKEND_NAME --signed-url-cache-max-age MAX_AGE
列出签名请求密钥名称
要列出后端服务或后端存储分区上的密钥,请运行以下命令之一:
gcloud compute backend-services describe BACKEND_NAME
gcloud compute backend-buckets describe BACKEND_NAME
删除签名请求密钥
如果采用特定密钥签名的网址不再受到支持,请运行以下某个命令以从后端服务或后端存储分区中删除该密钥:
gcloud compute backend-services \ delete-signed-url-key BACKEND_NAME --key-name KEY_NAME
gcloud compute backend-buckets \ delete-signed-url-key BACKEND_NAME --key-name KEY_NAME
为网址签名
最后一步是对网址进行签名并分发网址。您可以使用 gcloud compute sign-url
命令或使用自己编写的代码对网址进行签名。如果您需要大量签名网址,则自定义代码的性能更佳。
创建签名网址
按照以下说明使用 gcloud compute sign-url
命令创建签名网址。此步骤假定您已创建密钥。
控制台
您无法使用 Google Cloud 控制台创建签名网址。您可以使用 Google Cloud CLI,也可以使用以下示例编写自定义代码。
gcloud
Google Cloud CLI 包含用于对网址进行签名的命令。该命令可实现编写自己的代码部分所述的算法。
gcloud compute sign-url \ "URL" \ --key-name KEY_NAME \ --key-file KEY_FILE_NAME \ --expires-in TIME_UNTIL_EXPIRATION \ [--validate]
该命令会读取 KEY_FILE_NAME
中采用 base64url 编码的密钥值并对其进行解码,然后输出一个签名网址;您可以将该签名网址用于针对指定网址发出的 GET
或 HEAD
请求。
例如:
gcloud compute sign-url \ "https://example.com/media/video.mp4" \ --key-name my-test-key \ --expires-in 30m \ --key-file sign-url-key-file
URL
必须是包含路径某个组成部分的有效网址。例如,http://example.com
无效,不过 https://example.com/
和 https://example.com/whatever
均为有效网址。
如果指定了可选的 --validate
标志,则此命令会发送包含生成的网址的 HEAD
请求,并输出 HTTP 响应代码。如果签名网址是正确的,则响应代码与您的后端发送的结果代码相同。如果响应代码不相同,请重新检查 KEY_NAME
以及指定的文件的内容,并确保 TIME_UNTIL_EXPIRATION
的值至少有几秒钟。
如果未指定 --validate
标志,则系统不会验证以下内容:
- 输入
- 生成的网址
- 生成的签名网址
以编程方式创建签名网址
以下代码示例演示了如何以编程方式创建签名网址。
Go
Ruby
.NET
Java
Python
PHP
以编程方式创建具有网址前缀的签名网址
以下代码示例演示了如何以编程方式创建具有网址前缀的签名网址。
Go
Java
Python
生成自定义签名网址
当您编写自己的代码来生成签名网址时,您的目标是使用以下格式或算法来创建网址;所有网址参数均区分大小写,并且必须符合所示的顺序:
https://example.com/foo?Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE
要生成签名网址,请按以下步骤操作:
确保用于签名的网址不带
Signature
查询参数。确定网址的到期时间,并附加含所需到期时间的
Expires
查询参数。到期时间以世界协调时间 (UTC) 为准,表示从世界协调时间 (UTC) 1970-01-01 00:00:00 开始计算的秒数。为了最大限度地提高安全性,请将此值设置为适用于您的用例的最短时间段。签名网址的有效期越长,从您这里获得该网址的用户将网址分享给其他用户的风险就越大(无论有意还是无意分享)。设置密钥名称。您必须使用传送网址的后端服务或后端存储分区的密钥对该网址进行签名。最好使用最近添加的签名网址密钥来进行密钥轮替。通过附加
&KeyName=KEY_NAME
将密钥添加到网址中。将KEY_NAME
替换为在创建签名请求密钥中创建的所选密钥的名称。为网址签名。按照如下步骤创建签名网址。确保查询参数遵循第 1 步之前显示的顺序,并确保签名网址中的任何内容都没有改变大小写。
a. 使用 HMAC-SHA1 对整个网址(包括开头的
http://
或https://
以及末尾的&KeyName...
)进行哈希处理,并使用与上述选定密钥名称所对应的密钥。使用原始的 16 字节密钥,而非 base64url 编码密钥。如有必要,请进行解码。b. 使用 base64url 编码对结果进行编码。
c. 在网址中附加
&Signature=
,后跟经过编码的签名。
为签名网址使用网址前缀
您不必为带有 Expires
和 KeyName
查询参数的完整请求网址签名,而是可以仅对 URLPrefix
、Expires
和 KeyName
查询参数进行签名。这样一来,您就可以在多个与 URLPrefix
相匹配的网址中重复使用 URLPrefix
、Expires
、KeyName
和 Signature
查询参数的给定组合,而不需要为每个不同网址创建一个新签名。
在以下示例中,突出显示的文本是您签名的参数。Signature
照常作为最后一个参数附加。
https://media.example.com/videos/id/master.m3u8?userID=abc123&starting_profile=1&URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv&Expires=1566268009&KeyName=mySigningKey&Signature=8NBSdQGzvDftrOIa3WHpp646Iis=
与对完整请求网址进行签名不同,使用 URLPrefix
签名时,您并非对任何查询参数进行签名,因此查询参数可以自由添加到网址中。与完整的请求网址签名不同,这些额外的查询参数可以同时出现在构成签名的查询参数之前和之后。因此,以下内容也是包含签名网址前缀的有效网址:
https://media.example.com/videos/id/master.m3u8?userID=abc123&URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv&Expires=1566268009&KeyName=mySigningKey&Signature=8NBSdQGzvDftrOIa3WHpp646Iis=&starting_profile=1
URLPrefix
表示可在网址中安全使用的 Base64 编码网址前缀,其中包含该签名应对其有效的所有路径。
URLPrefix
会对架构(http://
或 https://
)、FQDN 和可选路径进行编码。您可以选择是否使用以 /
结尾的路径,但我们建议您使用。前缀不应包含查询参数或者 ?
或 #
这样的片段。
例如,https://media.example.com/videos
会将请求匹配到以下两项:
https://media.example.com/videos?video_id=138183&user_id=138138
https://media.example.com/videos/137138595?quality=low
前缀的路径作为文本子字符串使用,而不是严格意义上的目录路径。
例如,前缀 https://example.com/data
会授予对以下两个路径的访问权限:
/data/file1
/database
要避免这种错误,我们建议您在所有前缀的结尾处使用 /
,除非您有意选择使用部分文件名(例如 https://media.example.com/videos/123
)作为前缀的结尾,以授予对以下路径的访问权限:
/videos/123_chunk1
/videos/123_chunk2
/videos/123_chunkN
如果请求的网址与 URLPrefix
不匹配,Cloud CDN 会拒绝请求并向客户端返回一条 HTTP 403
错误。
验证签名网址
验证签名网址的过程与生成签名网址的过程基本相同。例如,假设您要验证以下签名网址:
https://example.com/PATH?Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE
您可以使用名为 KEY_NAME
的 Secret 密钥来为以下网址单独生成签名:
https://example.com/PATH?Expires=EXPIRATION&KeyName=KEY_NAME
然后,您便可以验证其与 SIGNATURE
是否匹配。
假设您要验证具有 URLPrefix
的签名网址,如下所示:
https://example.com/PATH?URLPrefix=URL_PREFIX&Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE
首先,验证 URL_PREFIX
的 base64-decoded 值是否为 https://example.com/PATH
的前缀。在这种情况下,您可以计算以下内容的签名:
URLPrefix=URL_PREFIX&Expires=EXPIRATION&KeyName=KEY_NAME
然后,您可以验证其是否匹配 SIGNATURE
。
对于基于网址的签名方法(签名是查询参数的一部分或嵌入为网址路径组件),系统会在将请求发送到来源之前从网址中移除签名和相关参数。这样可以防止签名在源处理请求时导致路由问题。如需验证这些请求,您可以检查 x-client-request-url
请求标头,其中包含移除已签名组件之前的原始(已签名)客户端请求网址。
移除对 Cloud Storage 存储桶的公开访问权限
为确保签名网址正确地保护内容,请务必不要让源服务器授予对内容的公共访问权限。使用 Cloud Storage 存储分区时,出于测试目的临时性地将一些对象公开是一种常见做法。启用签名网址后,请务必移除对该存储桶的 allUsers
和 allAuthenticatedUsers
(如果有)READ 权限(也就是 Storage Object Viewer Identity and Access Management 角色)。
停用对存储分区的公开访问权限后,个别用户仍可以在无签名网址的情况下访问 Cloud Storage,前提是他们拥有访问权限,例如 OWNER 权限。
如需移除对 Cloud Storage 存储分区的公共 allUsers
READ 权限,请逆向执行将存储分区中的所有对象设为可公开读取中所述的操作。
分发和使用签名网址
从 Google Cloud CLI 返回的网址或由您的自定义代码生成的网址可根据您的需求进行分发。我们建议仅对 HTTPS 网址进行签名,因为 HTTPS 提供的安全传输可防止签名网址中的 Signature
组成部分遭到拦截。同样,您应该通过安全传输协议(例如 TLS/HTTPS)分发签名网址。