Gerar tokens

Neste guia, explicamos como gerar um token e os campos obrigatórios e opcionais para tokens.

Para criar um token, escreva uma string a ser assinada, que chamaremos de valor assinado neste guia. O valor assinado inclui parâmetros que descrevem o conteúdo que você está protegendo, o prazo de validade do valor assinado e assim por diante.

Você usa o valor assinado ao criar uma string de token. Você cria uma string de token compondo os parâmetros do token, como um código de autenticação de mensagem baseado em hash de chave simétrica (HMAC) do valor assinado.

O Media CDN usa o token composto final para ajudar a proteger seu conteúdo.

Criar um token

  1. Crie um valor assinado concatenando uma string que contém os campos de token obrigatórios e os campos de token opcionais desejados. Separe cada campo e quaisquer parâmetros com um caractere til ~.

  2. Assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

  3. Componha o token concatenando uma string que contém os campos de token obrigatórios e os campos de token opcionais. Separe cada campo e todos os parâmetros com um caractere til ~.

    Ao compor o token, os valores de cada um dos parâmetros são os mesmos entre o valor assinado e a string do token, com as seguintes exceções:

    • FullPath
    • Headers

O exemplo de código a seguir mostra como criar um token de maneira programática:

Python

Para autenticar no Media CDN, configure o Application Default Credentials. Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento local.

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_token(
    base64_key: bytes,
    signature_algorithm: str,
    start_time: datetime.datetime = None,
    expiration_time: datetime.datetime = None,
    url_prefix: str = None,
    full_path: str = None,
    path_globs: str = None,
    session_id: str = None,
    data: str = None,
    headers: str = None,
    ip_ranges: str = None,
) -> str:
    """Gets the Signed URL Suffix string for the Media CDN' Short token URL requests.
    One of (`url_prefix`, `full_path`, `path_globs`) must be included in each input.
    Args:
        base64_key: Secret key as a base64 encoded string.
        signature_algorithm: Algorithm can be either `SHA1` or `SHA256` or `Ed25519`.
        start_time: Start time as a UTC datetime object.
        expiration_time: Expiration time as a UTC datetime object. If None, an expiration time 1 hour from now will be used.
        url_prefix: the URL prefix to sign, including protocol.
                    For example: http://example.com/path/ for URLs under /path or http://example.com/path?param=1
        full_path:  A full path to sign, starting with the first '/'.
                    For example: /path/to/content.mp4
        path_globs: a set of ','- or '!'-delimited path glob strings.
                    For example: /tv/*!/film/* to sign paths starting with /tv/ or /film/ in any URL.
        session_id: a unique identifier for the session
        data: data payload to include in the token
        headers: header name and value to include in the signed token in name=value format.  May be specified more than once.
                    For example: [{'name': 'foo', 'value': 'bar'}, {'name': 'baz', 'value': 'qux'}]
        ip_ranges: A list of comma separated ip ranges. Both IPv4 and IPv6 ranges are acceptable.
                    For example: "203.0.113.0/24,2001:db8:4a7f:a732/64"

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

    decoded_key = base64.urlsafe_b64decode(base64_key)
    algo = signature_algorithm.lower()

    # For most fields, the value we put in the token and the value we must sign
    # are the same.  The FullPath and Headers use a different string for the
    # value to be signed compared to the token.  To illustrate this difference,
    # we'll keep the token and the value to be signed separate.
    tokens = []
    to_sign = []

    # check for `full_path` or `path_globs` or `url_prefix`
    if full_path:
        tokens.append("FullPath")
        to_sign.append(f"FullPath={full_path}")
    elif path_globs:
        path_globs = path_globs.strip()
        field = f"PathGlobs={path_globs}"
        tokens.append(field)
        to_sign.append(field)
    elif url_prefix:
        field = "URLPrefix=" + base64_encoder(url_prefix.encode("utf-8"))
        tokens.append(field)
        to_sign.append(field)
    else:
        raise ValueError(
            "User Input Missing: One of `url_prefix`, `full_path` or `path_globs` must be specified"
        )

    # check & parse optional params
    if start_time:
        epoch_duration = start_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
        field = f"Starts={int(epoch_duration.total_seconds())}"
        tokens.append(field)
        to_sign.append(field)

    if not expiration_time:
        expiration_time = datetime.datetime.now() + datetime.timedelta(hours=1)
        epoch_duration = expiration_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
    else:
        epoch_duration = expiration_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
    field = f"Expires={int(epoch_duration.total_seconds())}"
    tokens.append(field)
    to_sign.append(field)

    if session_id:
        field = f"SessionID={session_id}"
        tokens.append(field)
        to_sign.append(field)

    if data:
        field = f"Data={data}"
        tokens.append(field)
        to_sign.append(field)

    if headers:
        header_names = []
        header_pairs = []
        for each in headers:
            header_names.append(each["name"])
            header_pairs.append("%s=%s" % (each["name"], each["value"]))
        tokens.append(f"Headers={','.join(header_names)}")
        to_sign.append(f"Headers={','.join(header_pairs)}")

    if ip_ranges:
        field = f"IPRanges={base64_encoder(ip_ranges.encode('ascii'))}"
        tokens.append(field)
        to_sign.append(field)

    # generating token
    to_sign = "~".join(to_sign)
    to_sign_bytes = to_sign.encode("utf-8")
    if algo == "ed25519":
        digest = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_key).sign(
            to_sign_bytes
        )
        tokens.append("Signature=" + base64_encoder(digest))
    elif algo == "sha256":
        signature = hmac.new(
            decoded_key, to_sign_bytes, digestmod=hashlib.sha256
        ).hexdigest()
        tokens.append("hmac=" + signature)
    elif algo == "sha1":
        signature = hmac.new(
            decoded_key, to_sign_bytes, digestmod=hashlib.sha1
        ).hexdigest()
        tokens.append("hmac=" + signature)
    else:
        raise ValueError(
            "Input Missing Error: `signature_algorithm` can only be one of `sha1`, `sha256` or `ed25519`"
        )
    return "~".join(tokens)

Java

Para autenticar no Media CDN, configure o Application Default Credentials. Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento local.


import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.util.encoders.Hex;

public class DualToken {

  public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
    // TODO(developer): Replace these variables before running the sample.
    // Secret key as a base64 encoded string.
    byte[] base64Key = new byte[]{};
    // Algorithm can be one of these: SHA1, SHA256, or Ed25519.
    String signatureAlgorithm = "ed25519";
    // (Optional) Start time as a UTC datetime object.
    DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
    Optional<Instant> startTime = Optional.empty();
    // Expiration time as a UTC datetime object.
    // If None, an expiration time that's an hour after the current time is used.
    Instant expiresTime = Instant.from(formatter.parse("2022-09-13T12:00:00Z"));

    // ONE OF (`urlPrefix`, `fullPath`, `pathGlobs`) must be included in each input.
    // The URL prefix and protocol to sign.
    // For example: http://example.com/path/ for URLs under /path or http://example.com/path?param=1
    Optional<String> urlPrefix = Optional.empty();
    // A full path to sign, starting with the first '/'.
    // For example: /path/to/content.mp4
    Optional<String> fullPath = Optional.of("http://10.20.30.40/");
    // A set of path glob strings delimited by ',' or '!'.
    // For example: /tv/*!/film/* to sign paths starting with /tv/ or /film/ in any URL.
    Optional<String> pathGlobs = Optional.empty();

    // (Optional) A unique identifier for the session.
    Optional<String> sessionId = Optional.empty();
    // (Optional) Data payload to include in the token.
    Optional<String> data = Optional.empty();
    // (Optional) Header name and value to include in the signed token in name=value format.
    // May be specified more than once.
    // For example: [{'name': 'foo', 'value': 'bar'}, {'name': 'baz', 'value': 'qux'}]
    Optional<List<Header>> headers = Optional.empty();
    // (Optional) A list of comma-separated IP ranges. Both IPv4 and IPv6 ranges are acceptable.
    // For example: "203.0.113.0/24,2001:db8:4a7f:a732/64"
    Optional<String> ipRanges = Optional.empty();

    DualToken.signToken(
        base64Key,
        signatureAlgorithm,
        startTime,
        expiresTime,
        urlPrefix,
        fullPath,
        pathGlobs,
        sessionId,
        data,
        headers,
        ipRanges);
  }

  // Gets the signed URL suffix string for the Media CDN short token URL requests.
  // Result:
  //     The signed URL appended with the query parameters based on the
  // specified URL prefix and configuration.
  public static void signToken(
      byte[] base64Key, String signatureAlgorithm, Optional<Instant> startTime,
      Instant expirationTime, Optional<String> urlPrefix, Optional<String> fullPath,
      Optional<String> pathGlobs, Optional<String> sessionId, Optional<String> data,
      Optional<List<Header>> headers, Optional<String> ipRanges)
      throws NoSuchAlgorithmException, InvalidKeyException {

    String field = "";
    byte[] decodedKey = Base64.getUrlDecoder().decode(base64Key);

    // For most fields, the value in the token and the value to sign
    // are the same. Compared to the token, the FullPath and Headers
    // use a different string for the value to sign. To illustrate this difference,
    // we'll keep the token and the value to be signed separate.
    List<String> tokens = new ArrayList<>();
    List<String> toSign = new ArrayList<>();

    // Check for `fullPath` or `pathGlobs` or `urlPrefix`.
    if (fullPath.isPresent()) {
      tokens.add("FullPath");
      toSign.add(String.format("FullPath=%s", fullPath.get()));
    } else if (pathGlobs.isPresent()) {
      field = String.format("PathGlobs=%s", pathGlobs.get().trim());
      tokens.add(field);
      toSign.add(field);
    } else if (urlPrefix.isPresent()) {
      field = String.format("URLPrefix=%s",
          base64Encoder(urlPrefix.get().getBytes(StandardCharsets.UTF_8)));
      tokens.add(field);
      toSign.add(field);
    } else {
      throw new IllegalArgumentException(
          "User Input Missing: One of `urlPrefix`, `fullPath` or `pathGlobs` must be specified");
    }

    // Check & parse optional params.
    long epochDuration;
    if (startTime.isPresent()) {
      epochDuration = ChronoUnit.SECONDS.between(Instant.EPOCH, startTime.get());
      field = String.format("Starts=%s", epochDuration);
      tokens.add(field);
      toSign.add(field);
    }

    if (expirationTime == null) {
      expirationTime = Instant.now().plus(1, ChronoUnit.HOURS);
    }
    epochDuration = ChronoUnit.SECONDS.between(Instant.EPOCH, expirationTime);
    field = String.format("Expires=%s", epochDuration);
    tokens.add(field);
    toSign.add(field);

    if (sessionId.isPresent()) {
      field = String.format("SessionID=%s", sessionId.get());
      tokens.add(field);
      toSign.add(field);
    }

    if (data.isPresent()) {
      field = String.format("Data=%s", data.get());
      tokens.add(field);
      toSign.add(field);
    }

    if (headers.isPresent()) {
      List<String> headerNames = new ArrayList<>();
      List<String> headerPairs = new ArrayList<>();

      for (Header entry : headers.get()) {
        headerNames.add(entry.getName());
        headerPairs.add(String.format("%s=%s", entry.getName(), entry.getValue()));
      }
      tokens.add(String.format("Headers=%s", String.join(",", headerNames)));
      toSign.add(String.format("Headers=%s", String.join(",", headerPairs)));
    }

    if (ipRanges.isPresent()) {
      field = String.format("IPRanges=%s",
          base64Encoder(ipRanges.get().getBytes(StandardCharsets.US_ASCII)));
      tokens.add(field);
      toSign.add(field);
    }

    // Generate token.
    String toSignJoined = String.join("~", toSign);
    byte[] toSignBytes = toSignJoined.getBytes(StandardCharsets.UTF_8);
    String algorithm = signatureAlgorithm.toLowerCase();

    if (algorithm.equalsIgnoreCase("ed25519")) {
      Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(decodedKey, 0);
      Ed25519Signer signer = new Ed25519Signer();
      signer.init(true, privateKey);
      signer.update(toSignBytes, 0, toSignBytes.length);
      byte[] signature = signer.generateSignature();
      tokens.add(String.format("Signature=%s", base64Encoder(signature)));
    } else if (algorithm.equalsIgnoreCase("sha256")) {
      String sha256 = "HmacSHA256";
      Mac mac = Mac.getInstance(sha256);
      SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, sha256);
      mac.init(secretKeySpec);
      byte[] signature = mac.doFinal(toSignBytes);
      tokens.add(String.format("hmac=%s", Hex.toHexString(signature)));
    } else if (algorithm.equalsIgnoreCase("sha1")) {
      String sha1 = "HmacSHA1";
      Mac mac = Mac.getInstance(sha1);
      SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, sha1);
      mac.init(secretKeySpec);
      byte[] signature = mac.doFinal(toSignBytes);
      tokens.add(String.format("hmac=%s", Hex.toHexString(signature)));
    } else {
      throw new Error(
          "Input Missing Error: `signatureAlgorithm` can only be one of `sha1`, `sha256` or "
              + "`ed25519`");
    }
    // The signed URL appended with the query parameters based on the
    // specified URL prefix and configuration.
    System.out.println(String.join("~", tokens));
  }

  // Returns a base64-encoded string compatible with Media CDN.
  // Media CDN uses URL-safe base64 encoding and strips off the padding at the
  // end.
  public static String base64Encoder(byte[] value) {
    byte[] encodedBytes = Base64.getUrlEncoder().withoutPadding().encode(value);
    return new String(encodedBytes, StandardCharsets.UTF_8);
  }

  public static class Header {

    private String name;
    private String value;

    public Header(String name, String value) {
      this.name = name;
      this.value = value;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getValue() {
      return value;
    }

    public void setValue(String value) {
      this.value = value;
    }

    @Override
    public String toString() {
      return "Header{"
          + "name='" + name + '\''
          + ", value='" + value + '\''
          + '}';
    }
  }

}

As seções a seguir descrevem os campos usados pelos tokens.

Campos de token obrigatórios

Os campos a seguir são obrigatórios para cada token:

  • Expires
  • Uma das seguintes opções:
    • PathGlobs
    • URLPrefix
    • FullPath
  • Uma das seguintes opções:
    • Signature
    • hmac

A menos que especificado de outra forma, os nomes dos parâmetros e os valores deles diferenciam maiúsculas de minúsculas.

A tabela a seguir explica cada parâmetro:

Nome / aliases do campo Parâmetros de token Valor assinado

Expires

exp

Segundos inteiros que passaram desde a época do Unix (1970-01-01T00:00:00Z) Expires=EXPIRATION_TIME. Após essa data, o token não será mais válido.

PathGlobs

paths, acl

Uma lista de até cinco segmentos de caminho aos quais conceder acesso. Eles podem ser delimitados por vírgulas (,) ou pontos de exclamação (!), mas não por ambos.

PathGlobs é compatível com caracteres curinga nos caminhos usando asteriscos (*) e pontos de interrogação (?). Um único caractere asterisco (*) abrange qualquer número de segmentos de caminho, ao contrário da sintaxe de correspondência de padrão para pathMatchTemplate.

Os parâmetros de caminho, indicados com ponto e vírgula (;), não são permitidos porque criam ambiguidade durante a correspondência.

Por esses motivos, verifique se o URL não contém os seguintes caracteres especiais: ,!*?;

PathGlobs=PATHS
URLPrefix

Um URL codificado em base64 seguro para a Web, incluindo o protocolo http:// ou https:// até um ponto de sua escolha.

Por exemplo, alguns valores válidos de URLPrefix para "https://example.com/foo/bar.ts" são "https://example.com", "https://example.com/foo" e "https://example.com/foo/bar".

URLPrefix=BASE_64_URL_PREFIX
FullPath Nenhuma. Ao especificar FullPath em um token, não duplique o caminho especificado no valor assinado. Em um token, inclua o nome do campo sem um =. FullPath=FULL_PATH_TO_OBJECT
Signature Uma versão da assinatura codificada em base64 e segura para a Web. Não aplicável
hmac Uma versão codificada em base64 segura para a Web do valor HMAC. Não aplicável

Sintaxe de caractere curinga PathGlobs

A tabela a seguir explica a sintaxe de caractere curinga PathGlobs.

Operador Corresponde a Examples
* (asterisco) Corresponde a zero ou mais caracteres no caminho do URL, incluindo caracteres de barra (/).
  • /videos/* corresponde a qualquer caminho que começa com /videos/.
  • /videos/s*/4k/* corresponde a /videos/s/4k/ e /videos/s01/4k/main.m3u8.
  • /manifests/*/4k/* corresponde a /manifests/s01/4k/main.m3u8 e /manifests/s01/e01/4k/main.m3u8. Ele não corresponde a /manifests/4k/main.m3u8.
? (ponto de interrogação) Corresponde a um único caractere no caminho do URL, sem incluir caracteres de barra (/). /videos/s?main.m3u8 corresponde a /videos/s1main.m3u8. Ele não corresponde a /videos/s01main.m3u8 ou /videos/s/main.m3u8.

Os Globs precisam começar com um asterisco (*) ou uma barra (/) para caminhos de URL.

Como * e /* correspondem a todos os caminhos de URL, não recomendamos usar nenhum deles nos tokens assinados. Para proteção máxima, verifique se os globs correspondem ao conteúdo ao qual você pretende conceder acesso.

Campos de token opcionais

A menos que especificado de outra forma, os nomes dos parâmetros e os valores deles diferenciam maiúsculas de minúsculas.

A tabela a seguir explica nomes de parâmetros, aliases e detalhes de parâmetros opcionais:

Nome / aliases do campo Parâmetros Valor assinado

Starts

st

Segundos inteiros desde a época do Unix (1970-01-01T00:00:00Z) Starts=START_TIME
IPRanges

Uma lista de até cinco endereços IPv4 e IPv6 no formato CIDR para os quais esse URL é válido no formato base64 seguro para a Web. Por exemplo, para especificar os intervalos de IP "192.6.13.13/32,193.5.64.135/32", especifique IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy.

Pode não ser útil incluir IPRanges em tokens quando os clientes estão em risco de migrações de WAN ou casos em que o caminho de rede para o front-end do aplicativo é diferente do caminho de entrega. O Media CDN rejeita clientes com um código HTTP 403 quando eles se conectam com um endereço IP que não faz parte da solicitação assinada.

Veja a seguir casos que podem fazer com que o Media CDN rejeite clientes com um código HTTP 403:

  • Ambientes de pilha dupla (IPv4, IPv6)
  • Migração da conexão (Wi-Fi para celular e celular para Wi-Fi)
  • Redes móveis que usam Carrier Gateway NAT (CGNAT ou CGN)
  • TCP de vários caminhos (MPTCP)

Todos esses fatores podem contribuir para que um determinado cliente tenha um endereço IP não determinístico durante uma sessão de reprodução de vídeo. Se o endereço IP do cliente mudar após a emissão do acesso e o cliente tentar fazer o download de um trecho de vídeo no buffer de reprodução, ele receberá um HTTP 403 do Media CDN.

IPRanges=BASE_64_IP_RANGES

SessionID

id

Uma string arbitrária, útil para análise de registros ou rastreamento de reprodução.

Para evitar a criação de um token inválido, use strings codificadas com % ou seguras para a Web em base64. Os caracteres a seguir não podem ser usados para SessionID porque fazem com que o token seja inválido: "~", "&" ou " " (espaço).

SessionID=SESSION_ID_VALUE

Data

data, payload

Uma string arbitrária, útil para análise de registros.

Para evitar a criação de um token inválido, use strings codificadas com % ou seguras para a Web em base64. Os caracteres a seguir não podem ser usados para Data porque fazem com que o token seja inválido: "~", "&" ou " " (espaço).

data=DATA_VALUE
Headers Uma lista delimitada por vírgulas de nomes de campos de cabeçalho. Os nomes de cabeçalho não diferenciam maiúsculas de minúsculas nas pesquisas da solicitação. Os nomes de cabeçalho nos valores assinados diferenciam maiúsculas de minúsculas. Se um cabeçalho estiver ausente, o valor será a string vazia. Se houver várias cópias de um cabeçalho, elas serão concatenadas por vírgulas. Headers=HEADER_1_NAME=HEADER_1_EXPECTED_VALUE, HEADER_2_NAME=HEADER_2_EXPECTED_VALUE

Examples

As seções a seguir mostram exemplos para gerar tokens.

Exemplo de uso de FullPath

Considere o exemplo a seguir usando o campo FullPath:

  • Item solicitado: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de validade: 16.000.000

O valor assinado é:

Expires=160000000~FullPath=/tv/my-show/s01/e01/playlist.m3u8

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Confira a seguir exemplos de tokens criados com um valor assinado:

Assinatura Ed25519

Expires=160000000~FullPath~Signature=SIGNATURE_OF_SIGNED_VALUE

Em que SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

Expires=160000000~FullPath~hmac=HMAC_OF_SIGNED_VALUE

Em que HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Nos exemplos anteriores, FullPath é fornecido no token, mas o valor não é repetido a partir do caminho especificado no valor assinado. Isso permite que você assine o caminho completo da solicitação sem duplicá-la no token.

Exemplo de uso de URLPrefix

Considere o exemplo a seguir usando o campo URLPrefix:

  • Item solicitado: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de validade: 16.000.000

O valor assinado é:

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4

No exemplo anterior, substituímos o caminho para o item solicitado, http://example.com/tv/my-show/s01/e01/playlist.m3u8, pelo caminho para o item no formato Base64 seguro para a Web, aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4.

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Confira a seguir exemplos de tokens criados com um valor assinado:

Assinatura Ed25519

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~Signature=SIGNATURE_OF_SIGNED_VALUE

Em que SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~hmac=HMAC_OF_SIGNED_VALUE

Em que HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Exemplo de uso de Headers

Considere o exemplo a seguir usando o campo Headers:

  • Item solicitado: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de validade: 16.000.000
  • Valor de PathGlobs: *
  • Cabeçalhos de solicitação esperados:
    • user-agent: browser
    • accept: text/html

O valor assinado é:

Expires=160000000~PathGlobs=*~Headers=user-agent=browser,accept=text/html

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Confira a seguir exemplos de tokens criados com um valor assinado:

Assinatura Ed25519

Expires=160000000~PathGlobs=*~Headers=user-agent,accept~Signature=SIGNATURE_OF_SIGNED_VALUE

Em que SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

Expires=160000000~PathGlobs=*~Headers=user-agent,accept~hmac=HMAC_OF_SIGNED_VALUE

Em que HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Nos exemplos anteriores, Headers=user-agent,accept é fornecido no token, mas os valores de cabeçalho esperados não são repetidos a partir do valor assinado. Isso permite assinar pares de chave-valor do cabeçalho da solicitação específicos sem duplicar os valores no token.