生成签名

本指南介绍了如何创建签名,以及签名的必填字段和选填字段。

如需创建签名,请组成要签名的字符串,我们在本指南中将其称为有符号值。带符号的值包括描述您要保护的内容的参数、带符号值的到期时间等。

您可在创建签名字符串时使用带符号的值。您可以通过组合签名参数(例如签名值的非对称密钥 Ed25519 签名)来创建签名字符串。

媒体 CDN 使用最终组合签名来帮助保护您的内容。

支持的签名格式

媒体 CDN 支持以下签名请求格式。

格式 行为 示例
查询参数(确切网址)

确切网址,用于授予对特定网址的访问权限。

完全匹配:

https://media.example.com/content/manifest.m3u8?
Expires=EXPIRATION
&KeyName=KEY_NAME
&Signature=SIGNATURE

查询参数(网址前缀) 通过指定 URLPrefix,您可以为前缀签名,并在播放器或清单生成内容中向多个网址附加相同的查询参数。

签名内容:

URLPrefix=PREFIX
&Expires=EXPIRATION
&KeyName=KEY_NAME
&Signature=SIGNATURE

PREFIX 替换为要授予访问权限的前缀,包括架构、主机和部分路径。

路径组件

前缀:允许访问前缀在 "/edge-cache-token=[...]" 组成部分之前的任何网址。

这样,相对清单网址便可以在提取子资源时自动继承签名网址组成部分。

https://media.example.com/video/edge-cache-token=Expires=EXPIRATION
&KeyName=KEY_NAME
&Signature=SIGNATURE/manifest_12382131.m3u8
签名 Cookie 前缀:Cookie 允许访问具有签名的 URLPrefix 值中指定的前缀的任何网址。

Edge-Cache-Cookie:

URLPrefix=PREFIX:
Expires=EXPIRATION:
KeyName=KEY_NAME:
Signature=SIGNATURE

创建签名

  1. 串联包含必需签名字段和所需可选签名字段的字符串,以创建有符号值。

    如果指定,则 URLPrefix 必须在前面,后跟 ExpiresKeyName,然后是任何可选参数。

    请使用以下内容分隔每个字段和所有参数:

    • 对于 Cookie,请使用英文冒号 : 字符。
    • 对于查询参数和路径组成部分,请使用“和”& 字符。
  2. 使用 Ed25519 签名为已签名的值签名。

  3. 在字符串末尾附加一个字段分隔符(:&),后跟 Signature= 和 Ed25519 签名。

创建签名网址

以下代码示例展示了如何以编程方式创建签名网址。

Go

如需向媒体 CDN 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

import (
	"crypto/ed25519"
	"encoding/base64"
	"fmt"
	"io"
	"strings"
	"time"
)

// signURL prints the signed URL string for the specified URL and configuration.
func signURL(w io.Writer, url, keyName string, privateKey []byte, expires time.Time) error {
	// url := "http://example.com"
	// keyName := "your_key_name"
	// privateKey := "[]byte{34, 31, ...}"
	// expires := time.Unix(1558131350, 0)

	sep := '?'
	if strings.ContainsRune(url, '?') {
		sep = '&'
	}
	toSign := fmt.Sprintf("%s%cExpires=%d&KeyName=%s", url, sep, expires.Unix(), keyName)
	sig := ed25519.Sign(privateKey, []byte(toSign))

	fmt.Fprintf(w, "%s&Signature=%s", toSign, base64.RawURLEncoding.EncodeToString(sig))

	return nil
}

Python

如需向媒体 CDN 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

import base64
import datetime

import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519


from six.moves import urllib

def sign_url(
    url: str, key_name: str, base64_key: str, expiration_time: datetime.datetime
) -> str:
    """Gets the Signed URL string for the specified URL and configuration.

    Args:
        url: URL to sign as a string.
        key_name: name of the signing key as a string.
        base64_key: signing key as a base64 encoded byte string.
        expiration_time: expiration time as a UTC datetime object.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified configuration.
    """
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    url_pattern = "{url}{separator}Expires={expires}&KeyName={key_name}"

    url_to_sign = url_pattern.format(
        url=stripped_url,
        separator="&" if query_params else "?",
        expires=expiration_timestamp,
        key_name=key_name,
    )

    digest = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_key).sign(
        url_to_sign.encode("utf-8")
    )
    signature = base64.urlsafe_b64encode(digest).decode("utf-8")
    signed_url = "{url}&Signature={signature}".format(
        url=url_to_sign, signature=signature
    )

    return signed_url

创建签名网址前缀

以下代码示例展示了如何以编程方式创建签名网址前缀。

Go

如需向媒体 CDN 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

import (
	"crypto/ed25519"
	"encoding/base64"
	"fmt"
	"io"
	"strings"
	"time"
)

// signURLPrefix prints the signed URL string for the specified URL prefix and configuration.
func signURLPrefix(w io.Writer, urlPrefix, keyName string, privateKey []byte, expires time.Time) error {
	// urlPrefix := "https://examples.com"
	// keyName := "your_key_name"
	// privateKey := "[]byte{34, 31, ...}"
	// expires := time.Unix(1558131350, 0)

	sep := '?'
	if strings.ContainsRune(urlPrefix, '?') {
		sep = '&'
	}

	toSign := fmt.Sprintf(
		"URLPrefix=%s&Expires=%d&KeyName=%s",
		base64.RawURLEncoding.EncodeToString([]byte(urlPrefix)),
		expires.Unix(),
		keyName,
	)
	sig := ed25519.Sign(privateKey, []byte(toSign))

	fmt.Fprintf(
		w,
		"%s%c%s&Signature=%s",
		urlPrefix,
		sep,
		toSign,
		base64.RawURLEncoding.EncodeToString(sig),
	)

	return nil
}

Python

如需向媒体 CDN 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

import base64
import datetime

import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519


from six.moves import urllib

def sign_url_prefix(
    url: str,
    url_prefix: str,
    key_name: str,
    base64_key: str,
    expiration_time: datetime.datetime,
) -> str:
    """Gets the Signed URL string for the specified URL prefix and configuration.

    Args:
        url: URL of request.
        url_prefix: URL prefix to sign as a string.
        key_name: name of the signing key as a string.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as a UTC datetime object.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(parsed_url.query, keep_blank_values=True)
    encoded_url_prefix = base64.urlsafe_b64encode(
        url_prefix.strip().encode("utf-8")
    ).decode("utf-8")
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = (
        "URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}"
    )
    policy = policy_pattern.format(
        encoded_url_prefix=encoded_url_prefix,
        expires=expiration_timestamp,
        key_name=key_name,
    )

    digest = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_key).sign(
        policy.encode("utf-8")
    )
    signature = base64.urlsafe_b64encode(digest).decode("utf-8")
    signed_url = "{url}{separator}{policy}&Signature={signature}".format(
        url=stripped_url,
        separator="&" if query_params else "?",
        policy=policy,
        signature=signature,
    )
    return signed_url

以下代码示例展示了如何以编程方式创建签名网址 Cookie。

创建签名路径组件

以下代码示例展示了如何以编程方式创建签名路径组件。

Python

如需向媒体 CDN 进行身份验证,请设置应用默认凭据。如需了解详情,请参阅为本地开发环境设置身份验证

import base64
import datetime
import hashlib
import hmac

import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519


def base64_encoder(value: bytes) -> str:
    """
    Returns a base64-encoded string compatible with Media CDN.

    Media CDN uses URL-safe base64 encoding and strips off the padding at the
    end.
    """
    encoded_bytes = base64.urlsafe_b64encode(value)
    encoded_str = encoded_bytes.decode("utf-8")
    return encoded_str.rstrip("=")


def sign_path_component(
    url_prefix: str,
    filename: str,
    key_name: str,
    base64_key: str,
    expiration_time: datetime.datetime,
) -> str:
    """Gets the Signed URL string for the specified URL prefix and configuration.

    Args:
        url_prefix: URL Prefix to sign as a string.
        filename: The filename of the sample request
        key_name: The name of the signing key as a string.
        base64_key: The signing key as a base64 encoded string.
        expiration_time: Expiration time as a UTC datetime object with timezone.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """

    expiration_duration = expiration_time.astimezone(
        tz=datetime.timezone.utc
    ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = "{url_prefix}edge-cache-token=Expires={expires}&KeyName={key_name}"
    policy = policy_pattern.format(
        url_prefix=url_prefix,
        expires=int(expiration_duration.total_seconds()),
        key_name=key_name,
    )

    digest = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_key).sign(
        policy.encode("utf-8")
    )
    signature = base64_encoder(digest)

    signed_url = "{policy}&Signature={signature}/{filename}".format(
        policy=policy, signature=signature, filename=filename
    )

    return signed_url

必需的签名字段

每个签名都必须包含以下字段:

  • Expires
  • KeyName
  • Signature

如果存在查询参数,则必须将它们作为网址中的最后一个参数归为一组。除非另有说明,否则参数名称及其值区分大小写。

下表介绍了各个参数:

字段名称 签名参数 有符号值
Expires 自 Unix 纪元 (1970-01-01T00:00:00Z) 起经过的整数秒数 Expires=EXPIRATION_TIME,之后签名将失效。
KeyName 用于对此请求签名的 EdgeCacheKeyset 的名称。KeyName 是指整个密钥集,而不是密钥集本身中的单个密钥。 KeyName=EDGE_CACHE_KEYSET
Signature 签名的 base-64 编码版本。 不适用

可选签名字段

如果存在查询参数,则必须将它们作为网址中的最后一个参数归为一组。除非另有说明,否则参数名称及其值区分大小写。

下表介绍了每个参数的名称以及可选签名参数的详细信息:

字段名称 签名参数 有符号值
HeaderName

已命名的请求标头字段名称,该名称必须出现在请求中。

签名时必须小写,因为标头字段名称区分大小写。媒体 CDN 会在验证签名之前将标头转换为小写。

HeaderName=HEADER_NAME
HeaderValue 已命名的请求标头字段值,该值必须存在于请求中。通常为用户 ID 或其他不透明标识符。包含 HeaderValue 但不包含 HeaderName 的请求会被拒绝。 HeaderValue=HEADER_VALUE
IPRanges

一个 CIDR 格式的 IPv4 和 IPv6 地址列表(最多五个),此网址采用可保障网络安全的 base64 格式。例如,如需指定 IP 范围“192.6.13.13/32,193.5.64.135/32”,您可以指定 IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy

当客户端面临 WAN 迁移风险或应用前端的网络路径与传送路径不同时,在签名中添加 IP 地址范围可能没有帮助。如果客户端所连接 IP 地址不属于签名请求,则媒体 CDN 会拒绝包含 HTTP 403 代码的客户端。

以下情况可能会导致媒体 CDN 拒绝包含 HTTP 403 代码的客户端:

  • 双栈(IPv4、IPv6)环境
  • 连接迁移(从 Wi-Fi 到移动网络,以及从移动网络到 Wi-Fi)
  • 使用运营商网关 NAT(CGNAT 或 CGN)的移动网络
  • 多路径 TCP (MPTCP)

所有这些因素都会导致给定客户端在视频播放会话期间具有不确定的 IP 地址。如果客户端 IP 地址在您授予访问权限后发生更改,而客户端尝试随后将视频片段下载到其播放缓冲区,则它们会从媒体 CDN 收到 HTTP 403

IPRanges=BASE_64_IP_RANGES
URLPrefix 要授予访问权限的 base64(网址安全)网址前缀。通过指定 URLPrefix,您可以为前缀签名,并在播放器或清单生成内容中向多个网址附加相同的查询参数。 使用签名 Cookie 格式时,必须提供 URLPrefix URLPrefix=BASE_64_URL_PREFIX