Introduction
Pour ajouter un niveau de protection supplémentaire lors de l'accès à votre vidéothèque ou pour appliquer des restrictions au niveau utilisateur à votre contenu, vous pouvez passer un JSON Web Token (JWT) avec votre appel à l'API Brightcove Playback.
Si vous êtes nouveau JWT's, consultez les points suivants :
Flux de travail
Pour créer un compte JSON Web Token (JWT) et vous enregistrer auprès de Brightcove, procédez comme suit :
Générer une paire de clés publique-privée
Vous (l'éditeur) allez générer une paire de clés publique-privée et fournir la clé publique à Brightcove. Vous utiliserez la clé privée pour signer des jetons. La clé privée n'est pas partagée avec Brightcove.
Il existe de nombreuses façons de générer la paire de clés publique-privée. Voici quelques exemples:
Exemple de script bash :
Exemple de script pour générer la paire de clés :
#!/bin/bash
set -euo pipefail
NAME=${1:-}
test -z "${NAME:-}" && NAME="brightcove-playback-auth-key-$(date +%s)"
mkdir "$NAME"
PRIVATE_PEM="./$NAME/private.pem"
PUBLIC_PEM="./$NAME/public.pem"
PUBLIC_TXT="./$NAME/public_key.txt"
ssh-keygen -t rsa -b 2048 -m PEM -f "$PRIVATE_PEM" -q -N ""
openssl rsa -in "$PRIVATE_PEM" -pubout -outform PEM -out "$PUBLIC_PEM" 2>/dev/null
openssl rsa -in "$PRIVATE_PEM" -pubout -outform DER | base64 > "$PUBLIC_TXT"
rm "$PRIVATE_PEM".pub
echo "Public key to saved in $PUBLIC_TXT"
Exécutez le script :
$ bash keygen.sh
Exemple d'utilisation Go
Exemple d'utilisation du langage Go de programmation pour générer la paire de clés :
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"time"
)
func main() {
var out string
flag.StringVar(&out, "output-dir", "", "Output directory to write files into")
flag.Parse()
if out == "" {
out = "rsa-key_" + strconv.FormatInt(time.Now().Unix(), 10)
}
if err := os.MkdirAll(out, os.ModePerm); err != nil {
panic(err.Error())
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err.Error())
}
privBytes := x509.MarshalPKCS1PrivateKey(priv)
pubBytes, err := x509.MarshalPKIXPublicKey(priv.Public())
if err != nil {
panic(err.Error())
}
privOut, err := os.OpenFile(path.Join(out, "private.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
panic(err.Error())
}
if err := pem.Encode(privOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes}); err != nil {
panic(err.Error())
}
pubOut, err := os.OpenFile(path.Join(out, "public.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
panic(err.Error())
}
if err := pem.Encode(pubOut, &pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes}); err != nil {
panic(err.Error())
}
var pubEnc = base64.StdEncoding.EncodeToString(pubBytes)
var pubEncOut = path.Join(out, "public_key.txt")
if err := ioutil.WriteFile(pubEncOut, []byte(pubEnc+"\n"), 0600); err != nil {
panic(err.Error())
}
fmt.Println("Public key saved in " + pubEncOut)
}
Exemple utilisant node.js
Exemple utilisant node.js pour générer la paire de clés :
var crypto = require("crypto");
var fs = require("fs");
var now = Math.floor(new Date() / 1000);
var dir = "rsa-key_" + now;
fs.mkdirSync(dir);
crypto.generateKeyPair(
"rsa",
{modulusLength: 2048},
(err, publicKey, privateKey) => {
fs.writeFile(
dir + "/public.pem",
publicKey.export({ type: "spki", format: "pem" }),
err => {}
);
fs.writeFile(
dir + "/public_key.txt",
publicKey.export({ type: "spki", format: "der" }).toString("base64") +
"\n",
err => {}
);
fs.writeFile(
dir + "/private.pem",
privateKey.export({ type: "pkcs1", format: "pem" }),
err => {}
);
}
);
console.log("Public key saved in " + dir + "/public_key.txt");
Enregistrer la clé publique
Vous possédez la clé privée et vous l'utiliserez pour générer des jetons signés. Vous partagerez la clé publique avec Brightcove pour vérifier vos jetons. L'API de clé vous permet d'enregistrer votre clé publique auprès de Brightcove.
Pour plus de détails sur l'API, consultez le Utilisation des API d'authentification document.
Créer un JSON Web Token
Les éditeurs créent un JSON Web Token (JWT). Le jeton est signé avec l'algorithme RSA à l'aide de l'algorithme de hachage SHA-256 (identifié dans la spécification JWT comme "RS256") Aucun autre algorithme JWT ne sera pris en charge.
Un sous-ensemble de la norme JSON Web Token claims sera utilisé, ainsi que certaines revendications privées définies par Brightcove. Vous allez créer une JSON Web Token signature avec votre clé privée.
Réclamations pour la livraison d'URL statiques
Les revendications suivantes peuvent être utilisées avec la diffusion d'URL statiques de Brightcove.
Réclamation | Type | Obligatoire | Description |
---|---|---|---|
accid |
Chaîne | L'identifiant du compte qui possède le contenu en cours de lecture | |
iat |
Entier | Heure à laquelle ce jeton a été émis, en secondes depuis l'époque | |
exp |
Entier |
Temps pendant lequel ce jeton ne sera plus valide, en secondes depuis l'époque. Ne doit pas être plus de 30 jours à compter de iat
|
|
drules |
Chaîne de caractères[] | Liste des ID d'action des règles de livraison à appliquer. Pour plus de détails, voir le document Mise en œuvre des règles de livraison . Si le paramètre de config_id requête est également défini, il sera ignoré, car cette affirmation l'annule.
|
|
conid |
Chaîne | S'il est présent, ce jeton n'autorisera qu'un identifiant vidéo spécifique du nuage de vidéos. Il peut s'agir d'un flux DRM/HLSe ou d'un bien non DRM. Doit être un ID vidéo valide. Notez que l'ID de référence n'est pas pris en charge. |
|
pro |
Chaîne | Spécifie un type de protection dans le cas où plusieurs sont disponibles pour une seule vidéo. Valeurs :
|
|
vod |
Objet | Contient des options de configuration spécifiques pour la vidéo à la demande. | |
vod.ssai |
Chaîne | Votre ID de configuration d'insertion d'annonces côté serveur (SSAI). Cette revendication est requise pour récupérer un HLS ou un DASH VMAP. |
Voici un exemple des revendications JSON Web Token (JWT) que vous pourriez utiliser :
{
// account id: JWT is only valid for this accounts
"accid":"4590388311111",
// issued at: timestamp when the JWT was created
"iat":1575484132,
// expires: timestamp when JWT expires
"exp":1577989732,
// drules: list of delivery rule IDs to be applied
"drules": ["0758da1f-e913-4f30-a587-181db8b1e4eb"],
// content id: JWT is only valid for video ID
"conid":"5805807122222",
// protection: specify a protection type in the case where multiple are available for a single video
"pro":"aes128",
// VOD specific configuration options
"vod":{
// SSAI configuration to apply
"ssai":"efcc566-b44b-5a77-a0e2-d33333333333"
}
}
Réclamations pour restrictions de lecture
Les revendications suivantes peuvent être utilisées avec Restrictions de lecture Brightcove. Dans le cadre des restrictions de lecture, vous pouvez mettre en œuvre les éléments suivants :
- Droits de lecture
- Protection des clés de licence
- Restrictions de lecture en direct
- Flux simultanés
- Limites de l'appareil
- Règles de livraison
Caractéristique | Réclamation | Type | Requis pour la fonctionnalité | DRM uniquement | Description |
---|---|---|---|---|---|
Général | accid |
Chaîne | Oui | L'identifiant du compte qui possède le contenu en cours de lecture | |
iat |
Entier | Oui | Heure à laquelle ce jeton a été émis, en secondes depuis l'époque | ||
exp |
Entier | Oui |
Non requis, mais fortement recommandé. Heure à laquelle ce jeton ne sera plus valide, en secondes depuis l'époque. Ne doit pas être plus de 30 jours à compter de iat
|
||
nbf |
Entier | Heure à laquelle ce jeton commencera à être valide, en secondes depuis l'époque. S'il n'est pas spécifié, le jeton est disponible immédiatement. |
|||
Droits de lecture | prid |
Chaîne | A playback_rights_id utilisé pour remplacer l'identifiant défini dans le catalogue pour cette vidéo Ce champ n'est pas validé |
||
tags |
Tableau <chaînes> | s'il est présent, ce jeton n'est valide que pour les vidéos qui ont les valeurs de balises répertoriées. Seules ces vidéos sont autorisées pour la lecture. | |||
vids |
Tableau <chaînes> | S'il est présent, ce jeton n'autorisera la recherche de licences que pour un ensemble d'identifiants vidéo. |
|||
Protection des clés de licence | ua |
Chaîne | S'il est présent, ce token ne sera valable que pour les requêtes de cet User-Agent. Ce champ n'a pas à suivre un format particulier. La protection des clés de licence doit être activée. |
||
conid |
Chaîne | S'il est présent, ce jeton n'autorisera la récupération de licence que pour un identifiant vidéo Video Cloud spécifique. Il doit s'agir d'un identifiant vidéo valide. La protection des clés de licence doit être activée. |
|||
maxip |
Entier | Oui | S'il est présent, ce token ne pourra être utilisé que par ce nombre d'adresses IP différentes. Requis pour le suivi des sessions ; HLSE (AES-128) uniquement . La protection des clés de licence doit être activée. |
||
maxu |
Entier | Oui |
S'il est présent, ce jeton ne sera valide que pour ce nombre de demandes de licence.
. La protection des clés de licence doit être activée. |
||
Flux simultanés | uid |
Chaîne | Oui | Oui | L'identifiant de l'utilisateur final. Ce champ est utilisé pour mettre en corrélation plusieurs sessions afin d'appliquer la concurrence de flux. Vous pouvez utiliser un identifiant arbitraire (64 caractères maximum, limités à A-Z, a-z, 0-9, et =/,@_.+-). Cependant, selon votre cas d'utilisation, Brightcove recommande l'utilisation d'un identifiant d'utilisateur pour suivre les sessions par utilisateur ou d'un identifiant de compte pour suivre les sessions par compte payant. Obligatoire pour la concurrence de session |
climit |
Entier | Oui | Oui | Lorsque ce champ est inclus, il active la vérification de la concurrence Stream ainsi que les demandes de renouvellement de licence. Cette valeur indique le nombre d'observateurs simultanés autorisés. Obligatoire pour la concurrence de session |
|
cbeh |
Chaîne | Oui | Réglez la valeur sur BLOCK_NEW pour permettre aux limites de flux simultanés de bloquer toute nouvelle demande, même du même utilisateur, lorsque le nombre maximum de flux est atteint. Définissez la valeur sur BLOCK_NEW_USER pour bloquer toute nouvelle demande d'un nouvel utilisateur uniquement lorsque le nombre maximum de flux est atteint. La valeur par défaut bloque le flux le plus ancien lorsque le nombre maximal de flux est atteint. |
||
sid |
Chaîne | Oui |
La spécification de l'ID de session du flux actuel vous permet de contrôler la manière dont une session est définie. Par défaut, une session est définie comme User-Agent (navigateur) + adresse IP + identifiant vidéo.
Par exemple, vous pouvez élargir la définition de la session à l'adresse IP + l'identifiant vidéo |
||
Limites de l'appareil | uid |
Chaîne | Oui | Oui | L'identifiant de l'utilisateur final. Ce champ est utilisé pour mettre en corrélation plusieurs sessions afin d'appliquer la concurrence de flux. Vous pouvez utiliser un identifiant arbitraire (64 caractères maximum, limités à A-Z, a-z, 0-9, et =/,@_.+-). Cependant, selon votre cas d'utilisation, Brightcove recommande l'utilisation d'un identifiant d'utilisateur pour suivre les sessions par utilisateur ou d'un identifiant de compte pour suivre les sessions par compte payant. Obligatoire pour l'enregistrement de l'appareil |
dlimit |
Entier | Oui | Oui | Lorsque ce champ est inclus, il contrôle le nombre d'appareils pouvant être associés à l'utilisateur spécifié (uid ). La valeur doit être > 0 . Les périphériques précédemment autorisés continueront à fonctionner si la dlimit valeur est supprimée dans les requêtes ultérieures. Exemple : si la valeur est définie sur 3 , l'utilisateur peut jouer sur les appareils A, B et C (tout serait autorisé). Essayer de jouer sur l'appareil D serait refusé. Si la valeur est modifiée 1 , l'utilisateur peut toujours jouer sur les 3 appareils A, B et C, sauf si les périphériques sont révoqués manuellement en gérant les appareils avec l' API Droits de lecture. Obligatoire pour l'enregistrement de l'appareil |
|
Règles de livraison | drules |
Chaîne de caractères[] | Liste des ID d'action des règles de livraison à appliquer. Pour plus de détails, voir le document Mise en œuvre des règles de livraison .
|
Réclamations par niveau
Plusieurs packages de sécurité sont disponibles pour les restrictions de lecture. Pour plus de détails, consultez la présentation : Document sur les restrictions de lecture Brightcove.
Voici les revendications disponibles pour chaque paquet de restrictions de lecture :
Caractéristique | Réclamations | Niveau de sécurité 1 | Niveau de sécurité 2 | Niveau de sécurité 3 |
---|---|---|---|---|
Général | accident | Oui | Oui | Oui |
iat | Oui | Oui | Oui | |
exp | Oui | Oui | Oui | |
nbf | Oui | Oui | Oui | |
Droits de lecture [1] | prid | Oui | Oui | Oui |
tags | Oui | Oui | Oui | |
vidéos | Oui | Oui | Oui | |
Protection des clés de licence | ua | Non | Oui | Oui |
conid | Non | Oui | Oui | |
maxip | Non | Oui | Oui | |
maxu | Non | Oui | Oui | |
Flux simultanés | UID | Non | Non | Oui |
climat | Non | Non | Oui | |
cbeh | Non | Non | Oui | |
sid | Non | Non | Oui | |
Flux génériques simultanés | UID | Non | Non | Oui |
climat | Non | Non | Oui | |
sid | Non | Non | Oui | |
Enregistrement de l'appareil | UID | Non | Non | Oui |
dlimit | Non | Non | Oui |
Générer un jeton
Les bibliothèques sont généralement disponibles pour générer des jetons JWT. Pour plus de détails, consultez le JSON Web Tokens site.
Un outil de conversion d'horodatage Epoch & Unix peut s'avérer utile lorsque vous travaillez avec des champs temporels.
Exemple de script bash :
Exemple de script pour générer le jeton JWT :
#! /usr/bin/env bash
# Static header fields.
HEADER='{
"type": "JWT",
"alg": "RS256"
}'
payload='{
"accid": "{your_account_id}"
}'
# Use jq to set the dynamic `iat` and `exp`
# fields on the payload using the current time.
# `iat` is set to now, and `exp` is now + 1 hour. Note: 3600 seconds = 1 hour
PAYLOAD=$(
echo "${payload}" | jq --arg time_str "$(date +%s)" \
'
($time_str | tonumber) as $time_num
| .iat=$time_num
| .exp=($time_num + 60 * 60)
'
)
function b64enc() { openssl enc -base64 -A | tr '+/' '-_' | tr -d '='; }
function rs_sign() { openssl dgst -binary -sha256 -sign playback-auth-keys/private.pem ; }
JWT_HDR_B64="$(echo -n "$HEADER" | b64enc)"
JWT_PAY_B64="$(echo -n "$PAYLOAD" | b64enc)"
UNSIGNED_JWT="$JWT_HDR_B64.$JWT_PAY_B64"
SIGNATURE=$(echo -n "$UNSIGNED_JWT" | rs_sign | b64enc)
echo "$UNSIGNED_JWT.$SIGNATURE"
Exécutez le script :
$ bash jwtgen.sh
Exemple d'utilisation Go
Voici un exemple d' Go implémentation de référence (en tant qu'outil cli) pour générer des jetons sans l'utilisation d'une bibliothèque tierce :
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
)
// Header is the base64UrlEncoded string of a JWT header for the RS256 algorithm
const RSAHeader = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
// Header is the base64UrlEncoded string of a JWT header for the EC256 algorithm
const ECHeader = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"
// Claims represents constraints that should be applied to the use of the token
type Claims struct {
Iat float64 `json:"iat,omitempty"` // Issued At
Exp float64 `json:"exp,omitempty"` // Expires At
Accid string `json:"accid,omitempty"` // Account ID
Conid string `json:"conid,omitempty"` // Content ID
Maxu float64 `json:"maxu,omitempty"` // Max Uses
Maxip float64 `json:"maxip,omitempty"` // Max IPs
Ua string `json:"ua,omitempty"` // User Agent
}
func main() {
var key, algorithm string
c := Claims{Iat: float64(time.Now().Unix())}
flag.StringVar(&key, "key", "", "Path to private.pem key file")
flag.StringVar(&c.Accid, "account-id", "", "Account ID")
flag.StringVar(&c.Conid, "content-id", "", "Content ID (eg, video_id or live_job_id)")
flag.Float64Var(&c.Exp, "expires-at", float64(time.Now().AddDate(0, 0, 1).Unix()), "Epoch timestamp (in seconds) for when the token should stop working")
flag.Float64Var(&c.Maxu, "max-uses", 0, "Maximum number of times the token is valid for")
flag.Float64Var(&c.Maxip, "max-ips", 0, "Maximum number of unique IP addresses the token is valid for")
flag.StringVar(&c.Ua, "user-agent", "", "User Agent that the token is valid for")
flag.StringVar(&algorithm, "algo", "", "Key algorithm to use for signing. Valid: ec256, rsa256")
flag.Parse()
if key == "" {
fmt.Printf("missing required flag: -key\n\n")
flag.Usage()
os.Exit(1)
}
if algorithm == "" {
fmt.Printf("missing required flag: -algo\n\n")
flag.Usage()
os.Exit(2)
}
if algorithm != "rsa256" && algorithm != "ec256" {
fmt.Printf("missing valid value for -algo flag. Valid: rsa256, ec256\n\n")
flag.Usage()
os.Exit(3)
}
if c.Accid == "" {
fmt.Printf("missing required flag: -account-id\n\n")
flag.Usage()
os.Exit(4)
}
bs, err := json.Marshal(c)
if err != nil {
fmt.Println("failed to marshal token to json", err)
os.Exit(5)
}
kbs, err := ioutil.ReadFile(key)
if err != nil {
fmt.Println("failed to read private key", err)
os.Exit(6)
}
if algorithm == "rsa256" {
processRSA256(kbs, bs)
} else {
processEC256(kbs, bs)
}
}
func processRSA256(kbs, bs []byte) {
block, _ := pem.Decode(kbs)
if block == nil {
fmt.Println("failed to decode PEM block containing private key")
os.Exit(7)
}
if block.Type != "RSA PRIVATE KEY" {
fmt.Println("failed to decode PEM block containing private key")
os.Exit(8)
}
pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
fmt.Println("failed to parse rsa private key", err)
os.Exit(9)
}
message := RSAHeader + "." + base64.RawURLEncoding.EncodeToString(bs)
hash := crypto.SHA256
hasher := hash.New()
_, _ = hasher.Write([]byte(message))
hashed := hasher.Sum(nil)
r, err := rsa.SignPKCS1v15(rand.Reader, pKey, hash, hashed)
if err != nil {
fmt.Println("failed to sign token", err)
os.Exit(10)
}
sig := strings.TrimRight(base64.RawURLEncoding.EncodeToString(r), "=")
fmt.Println(message + "." + sig)
}
func processEC256(kbs, bs []byte) {
block, _ := pem.Decode(kbs)
if block == nil {
fmt.Println("failed to decode PEM block containing private key")
os.Exit(7)
}
if block.Type != "EC PRIVATE KEY" {
fmt.Println("failed to decode PEM block containing private key")
os.Exit(8)
}
pkey, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
fmt.Println("failed to parse ec private key", err)
os.Exit(9)
}
message := ECHeader + "." + base64.RawURLEncoding.EncodeToString(bs)
hash := sha256.Sum256([]byte(message))
r, s, err := ecdsa.Sign(rand.Reader, pkey, hash[:])
if err != nil {
fmt.Println("failed to sign token", err)
os.Exit(10)
}
curveBits := pkey.Curve.Params().BitSize
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes++
}
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
sig := base64.RawURLEncoding.EncodeToString(out)
fmt.Println(message + "." + sig)
}
Résultats
Voici un exemple de jeton décodé utilisant https://JWT.io spécifiant l'ensemble complet des revendications :
ENTÊTE:
{
"alg": "RS256",
"type": "JWT"
}
CHARGE UTILE:
{
"accid": "1100863500123",
"conid": "51141412620123",
"exp": 1554200832,
"iat": 1554199032,
"maxip": 10,
"maxu": 10,
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"
}
Tester la lecture
Bien que cela ne soit pas obligatoire, vous souhaiterez peut-être tester la lecture vidéo avant de configurer un lecteur.
Livraison d'URL statique
Demander la lecture :
curl -X GET \
https://edge.api.brightcove.com/playback/v1/accounts/{account_id}/videos/{video_id}/master.m3u8?bcov_auth={jwt}
Pour une liste des points d'extrémité des URL statiques, voir le document Static URL Delivery .
Restrictions de lecture
Demander la lecture :
curl -X GET \
-H 'Authorization: Bearer {JWT}' \
https://edge-auth.api.brightcove.com/playback/v1/accounts/{your_account_id}/videos/{your_video_id}