生成签名

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

如需创建签名,您需要构建要签名的字符串,我们在本指南中将其称为已签名值。已签名值包含用于描述您要保护的内容、已签名值的到期时间等的参数。

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

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

支持的签名格式

Media 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

如需向 Media 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

如需向 Media 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

如需向 Media 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

如需向 Media 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

如需向 Media 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

请求中必须存在的命名请求标头字段名称。

签名时必须采用小写形式,因为标头字段名称区分大小写。Media CDN 会先将标头转换为小写,然后再验证签名。

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

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

如果客户端有 WAN 迁移风险,或者应用前端的网络路径与传送路径不同,则在签名中添加 IPRanges 可能没有用处。 如果客户端使用未包含在签名请求中的 IP 地址进行连接,Media CDN 会使用 HTTP 403 代码拒绝该客户端。

以下情况可能会导致 Media CDN 使用 HTTP 403 代码拒绝客户端:

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

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

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