1

我无法获得与 Google Cload CDN 的 URLPrefix 一起使用的签名 URL。

我已经设置了一个存储桶,它是我的 Cloud CDN 实例的后端存储桶。我已成功设置 URL 签名密钥并为特定路径生成了有效的签名 URL,所有这些都使用https://cloud.google.com/cdn/docs/using-signed-urls?hl=en_US中的说明

使用下面的 signCdnUrl2 函数,我可以为特定资源生成有效的签名 url,例如

https://example.com/foo.mp4?Expires=[EXPIRATION]&KeyName=[KEY_NAME]&Signature=[SIGNATURE]

export function signCdnUrl2(fileName: string, opts: SignedUrlOptions, urlPrefix?: string) {
    const expireVal = '' + new Date().getTime() + opts.expires;
    const urlToSign = `${opts.baseUrl}/${fileName}?Expires=${expireVal}&KeyName=${opts.keyName}`;

    // Compute signature
    const keyBuffer = Buffer.from(opts.keyBase64, 'base64');
    let signature = createHmac('sha1', keyBuffer).update(urlToSign).digest('base64');
    signature = Base64urlUtil.escape(signature);

    // Add signature to urlToSign and return signedUrl
    return urlToSign + `&Signature=${signature}`;
}

我想避免“需要为每个不同的 URL 创建一个新签名”,所以我按照https://cloud.google.com/cdn/docs/using-signed-urls?hl=en_US#url-上的说明进行操作前缀以添加 URL 前缀选项。

我无法成功生成带有前缀的有效签名 url。我目前的尝试如下

export function signCdnUrl3(fileName: string, opts: SignedUrlOptions, urlPrefix?: string) {

    const expireVal = '' + new Date().getTime() + opts.expires;

    const urlPrefixCombined = `${opts.baseUrl}${urlPrefix}`;
    // UrlPrefix param if provided otherwise empty string
    const urlPrefixEncoded = urlPrefix ? Base64urlUtil.encode(urlPrefixCombined) : '';

    // Param string to be signed with key
    const paramsToSign = `URLPrefix=${urlPrefixEncoded}&Expires=${expireVal}&KeyName=${opts.keyName}`;

    // Compute signature
    const keyBuffer = Buffer.from(opts.keyBase64, 'base64');
    let signature = createHmac('sha1', keyBuffer).update(paramsToSign).digest('base64');
    signature = Base64urlUtil.escape(signature);

    // Add signature to url
    return `${opts.baseUrl}/${fileName}?${paramsToSign}&Signature=${signature}`;
}

如果我尝试访问给定前缀下的任何资源(在存储桶的根目录下),我会从云 cdn 收到 403 响应

403响应

来自负载均衡器的日志条目显示它正在将其检测为无效签名

负载均衡器日志

说明中有什么我解释错了,还是我在执行过程中遗漏了一些东西?任何指导将不胜感激。

添加了 Base64Util 代码以确保完整性

export class Base64urlUtil {

    public static encode(str: string, encoding: any = 'utf8'): string {
        const buffer: Buffer = Buffer.from(str, encoding);
        const encodedStr: string = buffer.toString('base64');
        const final: string = Base64urlUtil.escape(encodedStr);
        return final;
    }

    public static decode(str: string, encoding?: string): string {
        return Buffer.from(Base64urlUtil.unescape(str), 'base64').toString(encoding || 'utf8');
    }

    public static escape(str: string): string {
        return str.replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }

    public static unescape(str: string): string {
        return (str + '==='.slice((str.length + 3) % 4))
            .replace(/-/g, '+')
            .replace(/_/g, '/');
    }
}

更新

使用@elithrar https://stackoverflow.com/a/61315372/4330441提供的实现,我将signedParams 中的样本值换成了我自己的实时值。

let signedParams = signURLPrefix(
  "https://<my-server>/sample/360p/",
  1588291200,
  "<my-key>",
  "<valid-key>"
)

结果是这样的:

URLPrefix=aHR0cHM6Ly9zcHluYWwucmNmc29mdHdhcmUuaW8vc2FtcGxlLzM2MHAv&Expires=1588291200&KeyName=my-key-name&Signature=wrbOloT+m31ZnQZei2Csqq0XaGY=

然后,当我附加这些查询参数以在此地址调用云 CDN 端点时:

https://my-server/sample/360p/video.mp4?URLPrefix=aHR0cHM6Ly9zcHluYWwucmNmc29mdHdhcmUuaW8vc2FtcGxlLzM2MHAv&Expires=1588291200&KeyName=my-key-name&Signature=wrbOloT+m31ZnQZei2Csqq0XaGY=

我在 cdn 日志中得到相同的 403 响应和匹配的无效签名

CDN日志

尝试使用两个不同的签名密钥,它们可以很好地签名没有 url 前缀的单个特定 url。

4

2 回答 2

1

目前尚不完全清楚出了什么问题 - 我怀疑您可能正在对签名进行双 base64 编码,但未Base64urlUtil包含在您提供的代码段中。

这是一个工作版本,它生成与Go 示例代码的测试相同的签名:

const crypto = require("crypto")

export function signURLPrefix(
  urlPrefix: string,
  expires: number,
  keyName: string,
  key: string
) {
  const expireVal = expires
  const urlPrefixEncoded = Buffer.from(urlPrefix)
     .toString("base64")
     .replace(/_/g, '/')
     .replace(/-/g, '+')

  // Param string to be signed with key
  const paramsToSign = `URLPrefix=${urlPrefixEncoded}&Expires=${expireVal}&KeyName=${keyName}`

  // Compute signature
  const keyBytes = Buffer.from(key, "base64")
  // Expected key: []byte{0x9d, 0x9b, 0x51, 0xa2, 0x17, 0x4d, 0x17, 0xd9,
  // 0xb7, 0x70, 0xa3, 0x36, 0xe0, 0x87, 0x0a, 0xe3}
  let signature = crypto
    .createHmac("sha1", keyBytes)
    .update(paramsToSign)
    .digest("base64")
    .replace(/_/g, '/')
    .replace(/-/g, '+')

  return `${paramsToSign}&Signature=${signature}`
}

let signedParams = signURLPrefix(
  "https://media.example.com/segments/",
  1558131350,
  "my-key",
  "nZtRohdNF9m3cKM24IcK4w=="
)

let expected =
  "URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS9zZWdtZW50cy8=&Expires=1558131350&KeyName=my-key&Signature=HWE5tBTZgnYVoZzVLG7BtRnOsgk="

if (signedParams === expected) {
  console.log("✔️ Signature matches")
} else {
  console.error(
    `❌ Does not match: \n\tgot ${signedParams},\n\twant ${expected}`
  )
}

输出:

➜  ts-node signed_prefix.ts
✔️ Signature matches
于 2020-04-20T04:57:29.220 回答
1

我有同样的问题。带有 URLPrefix 的签名 url 和签名 cookie 都不起作用。我曾尝试使用 Golang/Ruby 来实现,并且我确信签名逻辑与 Golang 示例相同。

在询问 Google 支持后,他们说:“由于此功能最近移至 GA,他们发现您的项目未正确启用它。正在实施修复以解决此问题,我希望它能够完全推出下周。一旦应用了修复程序,我会更新你。我认为您的项目也没有正确启用它。

下周应用修复后,我将重试并更新信息。

更新

我们收到来自 Google 支持的最新回复,称我们的项目已启用该功能。我的代码无需任何修改即可工作。

于 2020-04-23T02:06:38.683 回答