Générer des jetons

Ce guide explique comment générer un jeton et présente les champs obligatoires et facultatifs pour les jetons.

Pour créer un jeton, vous devez composer une chaîne à signer, que nous appellerons une valeur signée dans le présent guide. La valeur signée inclut des paramètres qui décrivent le contenu que vous protégez, le délai d'expiration de la valeur signée, etc.

Vous utilisez la valeur signée lors de la création d'une chaîne de jeton. Vous créez une chaîne de jeton en composant les paramètres du jeton, par exemple un code d'authentification des messages basé sur le hachage (HMAC, Hash-based Message Authentication Code) avec clé symétrique de la valeur signée.

Media CDN utilise le jeton composé final pour protéger votre contenu.

Créer un jeton

  1. Créez une valeur signée en concaténant une chaîne contenant les champs de jeton requis et les champs de jeton facultatifs souhaités. Séparez chaque champ et tous les paramètres à l'aide d'un caractère tilde ~.

  2. Signez la valeur signée avec une signature Ed25519 ou un HMAC à clé symétrique.

  3. Composez le jeton en concaténant une chaîne contenant les champs de jeton obligatoires et les champs de jeton facultatifs. Séparez chaque champ et tous les paramètres à l'aide d'un caractère tilde ~.

    Lors de la composition du jeton, les valeurs de chacun des paramètres sont les mêmes entre la valeur signée et la chaîne de jeton, à l'exception des cas suivants:

    • FullPath
    • Headers

L'exemple de code suivant montre comment créer un jeton par programmation:

Python

Pour vous authentifier auprès de Media CDN, configurez les Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement 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

Pour vous authentifier auprès de Media CDN, configurez les Identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement 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 + '\''
          + '}';
    }
  }

}

Les sections suivantes décrivent les champs utilisés par les jetons.

Champs de jeton obligatoires

Les champs suivants sont obligatoires pour chaque jeton:

  • Expires
  • L'une des options suivantes :
    • PathGlobs
    • URLPrefix
    • FullPath
  • Choisissez l'une des options suivantes :
    • Signature
    • hmac

Sauf indication contraire, les noms de paramètres et leurs valeurs sont sensibles à la casse.

Le tableau suivant explique chaque paramètre :

Nom / Alias du champ Paramètres de jeton Valeur signée

Expires

exp

Nombre entier de secondes écoulées depuis l'époque Unix (1970-01-01T00:00:00Z) Expires=EXPIRATION_TIME, après quoi le jeton n'est plus valide.

PathGlobs

paths, acl

Liste de cinq segments de chemin d'accès au maximum auxquels accorder l'accès. Les segments peuvent être délimités par des virgules (,) ou des points d'exclamation (!), mais pas les deux.

PathGlobs accepte les caractères génériques dans les chemins à l'aide d'astérisques (*) et de points d'interrogation (?). Un seul caractère astérisque (*) s'étend sur un nombre illimité de segments de chemin, contrairement à la syntaxe de correspondance de modèle pour pathMatchTemplate.

Les paramètres de chemin, indiqués à l'aide de points-virgules (;), ne sont pas autorisés, car ils créent une ambiguïté lors de la mise en correspondance.

Pour cette raison, assurez-vous que votre URL ne contient pas les caractères spéciaux suivants: ,!*?;

PathGlobs=PATHS
URLPrefix

URL encodée en base64 sécurisée pour le Web, y compris le protocole http:// ou https:// jusqu'à un point de votre choix.

Par exemple, voici quelques valeurs valides pour URLPrefix pour "https://example.com/foo/bar.ts" : "https://example.com", "https://example.com/foo" et "https://example.com/foo/bar".

URLPrefix=BASE_64_URL_PREFIX
FullPath Aucun Lorsque vous spécifiez FullPath dans un jeton, ne dupliquez pas le chemin d'accès que vous avez spécifié dans la valeur signée. Dans un jeton, incluez le nom du champ sans =. FullPath=FULL_PATH_TO_OBJECT
Signature Version de la signature encodée en base64 adapté au Web. Non applicable
hmac Version de la valeur HMAC encodée en base64 adaptée au Web. Non applicable

Syntaxe des caractères génériques PathGlobs

Le tableau suivant explique la syntaxe des caractères génériques PathGlobs.

Opérateur Correspondance établie Examples
* (astérisque) Correspond à zéro ou plusieurs caractères dans le chemin d'accès de l'URL, y compris les caractères de barre oblique (/).
  • /videos/* correspond à tout chemin commençant par /videos/.
  • /videos/s*/4k/* correspond à /videos/s/4k/ et /videos/s01/4k/main.m3u8.
  • /manifests/*/4k/* correspond à /manifests/s01/4k/main.m3u8 et /manifests/s01/e01/4k/main.m3u8. Il ne correspond pas à /manifests/4k/main.m3u8.
? (point d'interrogation) Correspond à un seul caractère dans le chemin d'accès de l'URL, sans inclure les caractères de barre oblique (/). /videos/s?main.m3u8 correspond à /videos/s1main.m3u8. Il ne correspond à aucun des éléments /videos/s01main.m3u8 ou /videos/s/main.m3u8.

Les expressions génériques doivent commencer par un astérisque (*) ou une barre oblique (/) pour les chemins d'URL.

Étant donné que * et /* correspondent à tous les chemins d'URL, nous vous déconseillons de les utiliser dans vos jetons signés. Pour une protection maximale, assurez-vous que vos globs correspondent au contenu auquel vous souhaitez accorder l'accès.

Champs de jeton facultatifs

Sauf indication contraire, les noms de paramètres et leurs valeurs sont sensibles à la casse.

Le tableau suivant explique les noms de paramètres, les éventuels alias et les détails des paramètres facultatifs:

Nom / Alias du champ Paramètres Valeur signée

Starts

st

Nombre entier de secondes écoulées depuis l'époque Unix (1970-01-01T00:00:00Z) Starts=START_TIME
IPRanges

Liste incluant jusqu'à cinq adresses IPv4 et IPv6 au format CIDR pour lesquelles cette URL est valide au format base64 web-safe. Par exemple, pour spécifier les plages d'adresses IP "192.6.13.13/32,193.5.64.135/32", spécifiez IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy.

Les plages d'adresses IP peuvent ne pas être utiles à inclure dans les jetons lorsque les clients sont susceptibles d'effectuer des migrations WAN ou dans les cas où le chemin d'accès réseau à l'interface de l'application est différent du chemin de distribution. Media CDN rejette les clients disposant d'un code HTTP 403 lorsqu'ils se connectent avec une adresse IP ne faisant pas partie de la requête signée.

Les cas suivants peuvent entraîner le rejet des clients avec le code HTTP 403 dans Media CDN :

  • Environnements double pile (IPv4, IPv6)
  • Migration de connexion (Wi-Fi vers mobile et mobile vers Wi-Fi)
  • Réseaux mobiles utilisant la passerelle NAT (CGNAT ou CGN)
  • TCP multi-chemin (MPTCP)

Tous ces facteurs peuvent contribuer à ce qu'un client donné ait une adresse IP non déterministe pendant une session de lecture vidéo. Si l'adresse IP du client change après avoir accordé l'accès et que le client tente de télécharger un segment vidéo dans son tampon de lecture, il reçoit une erreur HTTP 403 de Media CDN.

IPRanges=BASE_64_IP_RANGES

SessionID

id

Chaîne arbitraire, utile pour l'analyse des journaux ou le traçage de la lecture.

Pour éviter de créer un jeton non valide, utilisez des chaînes encodées en base64 ou encodées en %. Les caractères suivants ne doivent pas être utilisés pour SessionID, car ils rendent le jeton non valide : "~", "&" ou " " (espace).

SessionID=SESSION_ID_VALUE

Data

data, payload

Chaîne arbitraire, utile pour l'analyse des journaux.

Pour éviter de créer un jeton non valide, utilisez des chaînes encodées en base64 ou encodées en %. Les caractères suivants ne doivent pas être utilisés pour Data, car ils rendent le jeton non valide : "~", "&" ou " " (espace).

data=DATA_VALUE
Headers Liste des noms des champs d'en-tête séparés par une virgule. Les noms d'en-tête ne sont pas sensibles à la casse pour les recherches dans la requête. Les noms d'en-tête dans les valeurs signées sont sensibles à la casse. Si un en-tête est manquant, la valeur est une chaîne vide. Si plusieurs copies d'un en-tête existent, elles sont concatenatées par des virgules. Headers=HEADER_1_NAME=HEADER_1_EXPECTED_VALUE, HEADER_2_NAME=HEADER_2_EXPECTED_VALUE

Examples

Les sections suivantes présentent des exemples de génération de jetons.

Exemple utilisant FullPath

Prenons l'exemple suivant utilisant le champ FullPath:

  • Article demandé: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Date d'expiration: 160000000

La valeur signée est la suivante:

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

Pour créer un jeton, signez la valeur signée avec une signature Ed25519 ou un HMAC à clé symétrique.

Voici des exemples de jetons créés à partir d'une valeur signée:

Signature Ed25519

Expires=160000000~FullPath~Signature=SIGNATURE_OF_SIGNED_VALUE

SIGNATURE_OF_SIGNED_VALUE est la signature ED25519 de la valeur signée créée précédemment.

HMAC à clé symétrique

Expires=160000000~FullPath~hmac=HMAC_OF_SIGNED_VALUE

HMAC_OF_SIGNED_VALUE est le HMAC à clé symétrique de la valeur signée créée précédemment.

Dans les exemples précédents, FullPath est fourni dans le jeton, mais la valeur n'est pas répétée à partir du chemin d'accès spécifié dans la valeur signée. Vous pouvez ainsi signer le chemin d'accès complet de la requête sans dupliquer la requête dans le jeton.

Exemple utilisant URLPrefix

Prenons l'exemple suivant utilisant le champ URLPrefix:

  • Article demandé: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Date d'expiration: 160000000

La valeur signée est la suivante:

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4

Dans l'exemple précédent, nous avons remplacé le chemin d'accès à l'élément demandé, http://example.com/tv/my-show/s01/e01/playlist.m3u8, par le chemin d'accès à l'élément au format Base64 sécurisé pour le Web, aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4.

Pour créer un jeton, signez la valeur signée avec une signature Ed25519 ou un HMAC à clé symétrique.

Voici des exemples de jetons créés à partir d'une valeur signée:

Signature Ed25519

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~Signature=SIGNATURE_OF_SIGNED_VALUE

SIGNATURE_OF_SIGNED_VALUE est la signature ED25519 de la valeur signée créée précédemment.

HMAC à clé symétrique

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~hmac=HMAC_OF_SIGNED_VALUE

HMAC_OF_SIGNED_VALUE est le HMAC à clé symétrique de la valeur signée créée précédemment.

Exemple utilisant Headers

Prenons l'exemple suivant utilisant le champ Headers:

  • Article demandé: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Date d'expiration: 160000000
  • Valeur PathGlobs: *
  • En-têtes de requête attendus :
    • user-agent: browser
    • accept: text/html

La valeur signée est la suivante:

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

Pour créer un jeton, signez la valeur signée avec une signature Ed25519 ou un HMAC à clé symétrique.

Voici des exemples de jetons créés à partir d'une valeur signée:

Signature Ed25519

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

SIGNATURE_OF_SIGNED_VALUE est la signature ED25519 de la valeur signée créée précédemment.

HMAC à clé symétrique

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

HMAC_OF_SIGNED_VALUE est le HMAC à clé symétrique de la valeur signée créée précédemment.

Dans les exemples précédents, Headers=user-agent,accept est fourni dans le jeton, mais les valeurs d'en-tête attendues ne sont pas répétées à partir de la valeur signée. Cela vous permet de signer des paires clé-valeur d'en-tête de requête spécifiques sans dupliquer les valeurs dans le jeton.