Google Cloud CDN 提供两种签名方式:签名 URL 和签名 Cookie。签名 URL 适合一次性分享、按次付费下载、视频试看等场景,它把 Expires、KeyName、Signature 三个查询参数附加到资源地址上,由边缘节点在回源或返回缓存之前校验。只要签名无效或过期,节点直接返回 403,源站根本不会收到请求。

这种机制的核心价值是把访问控制前移到边缘。假设你的课程视频存放在 Cloud Storage 中,源站只负责生成短时 URL,真正的鉴权由全球 CDN 节点完成。签名密钥保存在 GCP 项目里,不需要写进业务代码,也不随 URL 暴露,只有后端能持有密钥。
一、签名参数与 HMAC 签名流程
一个完整的 Cloud CDN 签名 URL 通常长这样:https://cdn.ipipp.com/video/lesson1.mp4?Expires=1710000000&KeyName=my-key&Signature=...。其中 Expires 是 Unix 时间戳,表示访问截止时间;KeyName 是你在 Cloud CDN 控制台创建的密钥名称;Signature 是对固定字符串做 HMAC-SHA1 后得到的 URL 安全 Base64 值。CDN 节点收到请求后,会取出这三个参数,用自己保存的密钥重新计算签名,并判断当前时间是否超过 Expires。
签名的输入串只包含资源路径和两个参数,不包含域名、协议和 Signature 本身。资源路径必须以斜杠开头,并且要与请求路径严格一致。假设路径是 /video/lesson1.mp4,Expires 是 1710000000,KeyName 是 my-key,那么待签名字符串就是:/video/lesson1.mp4?Expires=1710000000&KeyName=my-key。这个字符串先用 UTF-8 编码,再用密钥做 HMAC-SHA1,最后进行 URL 安全的 Base64 编码并去掉末尾等号。
下面的 Python 函数可以直接生成短期签名 URL。生产环境中可以把密钥放在 Secret Manager,由 Cloud Run 或 Cloud Functions 调用。
import base64
import hashlib
import hmac
import time
def build_signed_url(domain, path, key_name, secret, ttl_seconds=600):
expires = int(time.time()) + ttl_seconds
unsigned = f"{path}?Expires={expires}&KeyName={key_name}"
digest = hmac.new(secret.encode("utf-8"), unsigned.encode("utf-8"), hashlib.sha1).digest()
signature = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("utf-8")
return f"https://{domain}{path}?Expires={expires}&KeyName={key_name}&Signature={signature}"
调用时传入域名、路径、密钥名和密钥值即可。过期时间通常设置得比业务允许的最长观看或下载时间略长,比如十分钟试看可以设置 600 秒。这个值必须在 CDN 节点校验时仍未过去,因此源站服务器需要开启 NTP 时间同步,避免因时钟偏移导致刚生成的链接立即失效。
需要注意的是,签名 URL 的 Expires 不是客户端本地时间,也不受用户设备时区影响,校验发生在 Google 边缘节点,因此判断来源是 GCP 的服务器时间。即使攻击者修改本地时钟,也无法让一个已经过期的 URL 重新有效。
二、按次付费视频的十分钟访问窗口案例
以一个在线课程平台为例。平台把正课视频放在私有 Cloud Storage 桶里,CDN 连接该桶作为源站。用户购买某节课后,如果后端返回存储桶的公开 URL,这份 URL 会被转发到社交平台,任何拿到链接的人都能长期访问。改造方案是在订单完成后,由 API 服务调用签名函数,为当前用户的播放器生成一个 10 分钟有效的 URL。
播放器拿到 URL 后直接向 CDN 发起请求。第一个用户请求到达边缘节点时,节点验证签名并回源拉取视频,后续用户在有效期内共享同一条 URL 时,只要签名参数一致且未过期,CDN 默认会直接命中缓存。这里要澄清一个误解:签名参数不会让缓存失效,但未带有效签名的请求不会命中这个缓存。边缘节点的鉴权在缓存查找之前完成,因此公开 URL 即使路径相同也不会免费拿到内容。
该案例还展示了时间窗口的取舍。窗口太短,播放器在缓冲或断网后恢复时可能遇到 403;窗口太长,被分享后的风险会变大。可以先按视频时长的 1.5 倍加 2 分钟作为初始窗口,再根据实际播放中断率调整。例如 8 分钟的视频,可以签发 14 分钟。对于下载类资源,建议 60 秒到 180 秒,因为下载开始后连接已经建立,过期不会中断正在传输的响应,只会阻止新的请求。
如果想要更细粒度的控制,可以在生成 URL 前调用自己的鉴权 API 验证用户身份,只有订单有效才签发。Cloud CDN 本身不负责登录态判断,它只验证签名和过期时间,因此业务鉴权仍要保留。这种方式把业务逻辑和边缘安全职责分开,后端无需处理大文件流量。
三、签名失败的排查方法与密钥管理
签名 URL 返回 403 时,最先检查的是 Expires 是否已经过期。这个错误最常见,但往往被忽略。可以在本地用 date +%s 对比 Expires,或者在 GCP 控制台查看 CDN 日志中的响应码。第二种常见原因是路径不一致:签名时使用 /video/lesson1.mp4,但实际请求经过重写或 URL 编码后变成了 /video/lesson1.mp4?extra=1,签名输入却没有包含 extra 参数,节点就会拒绝。签名路径必须和最终到达边缘节点的路径完全一致,包括大小写和尾部斜杠。
第三种原因与密钥有关。Cloud CDN 密钥有名称和值,项目里可以同时存在多个密钥。如果 URL 中的 KeyName 写错,或者生成签名时使用的密钥值并不属于该 KeyName,校验就会失败。排查时建议先用一个最小示例固定路径、过期时间和密钥,确认签名算法输出正确,再逐步替换为业务参数。官方文档中提供了签名输入串示例,可以把程序生成的 unsigned 值打印出来对比。
密钥管理方面,不要把所有环境共用一个密钥。可以给开发、预发、生产分别创建密钥,并对生产密钥设置定期轮换。签名 URL 一旦生成就无法服务端主动失效,只能等待过期,所以短生命周期是控制泄露影响的最有效手段。对于需要即时失效的强场景,可以改用签名 Cookie 配合更短过期时间,或者通过 Cloud Armor 在边缘增加 IP 级限制,但这已经超出 Signed URLs 本身的职责范围。
调试时可以用 curl 观察状态码:curl -s -o /dev/null -w '%{http_code}' 'https://cdn.ipipp.com/video/lesson1.mp4?Expires=1710000000&KeyName=my-key&Signature=abc'。如果返回 403,再分别去掉签名、改路径、改过期时间做对照,基本能定位问题。
Google Cloud CDN签名URL访问时间限制修改时间:2026-09-19 07:14:30