Creación de un token web JSON (JWT)

En este tema, aprenderá a crear un JSON Web Token (JWT) que se puede utilizar con las restricciones de reproducción de Brightcove.

Introducción

Para agregar un nivel adicional de protección al acceder a su biblioteca de videos, o para aplicar restricciones de nivel de usuario para su contenido, puede pasar un JSON Web Token (JWT) con su llamada a Brightcove Playback API.

Si eres nuevo en JWT's , revisa lo siguiente:

Flujo de trabajo

Para crear un JSON Web Token (JWT) y regístrese en Brightcove, siga estos pasos:

  1. Generar par de claves público-privadas
  2. Registre la clave pública con Brightcove
  3. Crear un JSON Web Token
  4. Prueba de reproducción

Generar par de claves público-privadas

Usted (el editor) generará un par de claves pública-privada y proporcionará la clave pública a Brightcove. Utilizará la clave privada para firmar tokens. La clave privada no se comparte con Brightcove.

Hay muchas formas de generar el par de claves pública-privada. Aquí hay unos ejemplos:

Ejemplo de script de bash:

Script de ejemplo para generar el par de claves:

#!/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"

Ejecute el script:

$ bash keygen.sh
Ejemplo usando Go

Ejemplo usando el Go lenguaje de programación para generar el par de claves:

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)
  }
  

Ejemplo usando node.js

Ejemplo usando node.js para generar el par de claves:

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");

Registrar clave pública

Eres propietario de la clave privada y la usarás para generar tokens firmados. Compartirá la clave pública con Brightcove para verificar sus tokens. La API de claves le permite registrar su clave pública con Brightcove.

Para obtener detalles de la API, consulte la Usar API de autenticación documento.

Crear un JSON Web Token

Los editores crean un JSON Web Token (JWT). El token está firmado con el algoritmo RSA utilizando el algoritmo hash SHA-256 (identificado en la especificación JWT como "RS256") No se admitirán otros algoritmos JWT.

Un subconjunto del estándar JSON Web Token claims se utilizará, junto con algunas reclamaciones privadas definidas por Brightcove. Crearás un JSON Web Token firmado con su clave privada.

Reclamaciones de entrega de URL estáticas

Las siguientes afirmaciones se pueden utilizar con la entrega de URL estática de Brightcove .

Afirmar Tipo Requerido Descripción
accid cuerda La identificación de la cuenta que posee el contenido que se está reproduciendo.
iat Entero Hora en que se emitió esta ficha, en segundos desde la Época
exp Entero Hora en que esta ficha ya no será válida, en segundos desde la Época. No debe ser más de 30 días desde iat
drules Cuerda[] Lista de ID de acción de regla de entrega para aplicar. Para obtener más información, consulte el documento Implementación de reglas de entrega .
Si el config_id el parámetro de consulta también se establece, se ignorará, ya que este reclamo lo anula.
conid cuerda Si está presente, este token solo autorizará una ID de video específica de Brightcove. Puede ser una transmisión DRM/HLSe o un activo que no sea DRM.

Debe ser una identificación de video válida. Tenga en cuenta que no se admite el ID de referencia.
pro cuerda Especifica un tipo de protección en el caso de que haya varios disponibles para un solo video.

Valores:
  • "" (predeterminado para contenido claro)
  • "aes128"
  • "widevine"
  • "listo para jugar"
  • "fairplay"
vod Objeto Contiene opciones de configuración específicas para Video-On-Demand.
vod.ssai cuerda Su ID de configuración de inserción de anuncios del lado del servidor (SSAI). Esta afirmación es requerido para recuperar un HLS o un DASH VMAP.

A continuación se muestra un ejemplo de JSON Web Token (JWT) afirma que podría usar:

{
// 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"
}
}

Reclamaciones por restricciones de reproducción

Las siguientes afirmaciones se pueden utilizar con Restricciones de reproducción de Brightcove. Como parte de las Restricciones de reproducción, puede implementar lo siguiente:

Característica Afirmar Tipo Requerido para la función Solo DRM Descripción
General accid cuerda La identificación de la cuenta que posee el contenido que se está reproduciendo.
iat Entero Hora en que se emitió esta ficha, en segundos desde la Época
exp Entero No es obligatorio, pero se recomienda encarecidamente.

Tiempo en que este token ya no será válido, en segundos desde la Época. No debe ser más de 30 días desde iat
nbf Entero Tiempo en que este token comenzará a ser válido, en segundos desde la Época.
Si no se especifica, el token está disponible de inmediato.
Derechos de reproducción prid cuerda A playback_rights_id se usa para anular la identificación establecida en el catálogo para este video

Este campo no está validado

tags Matriz <Strings> si está presente, este token solo es válido para videos que tienen los valores de etiquetas enumerados. Solo estos videos están autorizados para su reproducción.
vids Matriz <Strings> Si está presente, este token solo autorizará la obtención de licencias para un conjunto de ID de video.

Protección de claves de licencia ua cuerda Si está presente, este token solo será válido para solicitudes de este agente de usuario.

Este campo no tiene que seguir ningún formato en particular.
Debes tener Protección de claves de licencia activado.
conid cuerda Si está presente, este token solo autorizará la obtención de licencias para una identificación de video de Video Cloud específica.

Debe ser una identificación de video válida
Debes tener Protección de claves de licencia activado.
maxip Entero Si está presente, este token solo podrá ser utilizado por este número de direcciones IP diferentes.

Requerido para seguimiento de sesiones; HLSe (AES-128) solamente
Debes tener Protección de claves de licencia activado.
maxu Entero Si está presente, este token solo será válido para este número de solicitudes de licencia.

  • Para HLSe, los jugadores realizarán múltiples solicitudes al reproducir un video, generalmente una por reproducción. La maxu debe establecerse lo suficientemente alto para tener en cuenta estas solicitudes adicionales.
Requerido para seguimiento de sesiones; HLSe (AES-128) solamente
Debes tener Protección de claves de licencia activado.
Flujos concurrentes uid cuerda El ID de usuario del visor final. Este campo se utiliza para correlacionar varias sesiones para hacer cumplir Stream Concurrency.

Puede usar una identificación arbitraria (máx. 64 caracteres, limitado a AZ, az, 0-9 y =/,@_.+-). Pero, según su caso de uso, Brightcove recomienda un identificador de usuario para realizar un seguimiento de las sesiones por usuario o un identificador de cuenta para realizar un seguimiento de las sesiones por cuenta de pago.

Requerido para la concurrencia de sesiones
climit Entero Cuando se incluye este campo, habilita la comprobación de la simultaneidad de la transmisión junto con las solicitudes de renovación de licencia. Este valor indica la cantidad de observadores simultáneos permitidos.

Requerido para la concurrencia de sesiones
cbeh cuerda Establezca el valor en BLOCK_NEW para habilitar los límites de transmisión simultánea para bloquear cualquier solicitud nueva, incluso del mismo usuario, cuando se alcanza el número máximo de transmisiones.

Establezca el valor en BLOCK_NEW_USER para bloquear cualquier solicitud nueva solo de un nuevo usuario cuando se alcanza el número máximo de transmisiones.

El valor predeterminado bloquea la transmisión más antigua cuando se alcanza el número máximo de transmisiones.
sid cuerda Especificar el ID de sesión de la transmisión actual le permite controlar cómo se define una sesión. De forma predeterminada, una sesión se define como User-Agent (navegador) + dirección IP + identificación de video.

Por ejemplo, puede aflojar la definición de sesión a dirección IP + ID de video

Límites de dispositivos uid cuerda El ID de usuario del visor final. Este campo se utiliza para correlacionar varias sesiones para hacer cumplir Stream Concurrency.

Puede usar una identificación arbitraria (máx. 64 caracteres, limitado a AZ, az, 0-9 y =/,@_.+-). Pero, según su caso de uso, Brightcove recomienda un identificador de usuario para realizar un seguimiento de las sesiones por usuario o un identificador de cuenta para realizar un seguimiento de las sesiones por cuenta de pago.

Requerido para el registro del dispositivo
dlimit Entero Cuando se incluye este campo, controla cuántos dispositivos se pueden asociar con el usuario especificado (uid). El valor debe ser> 0.

Los dispositivos previamente permitidos seguirán funcionando si el dlimit el valor se elimina en solicitudes posteriores.

Ejemplo: si el valor se establece en 3 , el usuario puede jugar en los dispositivos A, B y C (todos estarían permitidos). Intentar jugar en el dispositivo D sería denegado.

Si el valor se cambia a 1 , el usuario aún puede jugar en los 3 dispositivos A, B y C, a menos que los dispositivos se revoquen manualmente mediante la administración de dispositivos con el API de derechos de reproducción.

Requerido para el registro del dispositivo
Reglas de entrega drules Cuerda[] Lista de ID de acción de regla de entrega para aplicar. Para obtener más información, consulte el documento Implementación de reglas de entrega .

Reclamaciones por nivel

Hay varios paquetes de seguridad disponibles para restricciones de reproducción. Para obtener más detalles, consulte la Descripción general: Documento de restricciones de reproducción de Brightcove.

Estas son las reclamaciones disponibles para cada paquete de restricción de reproducción:

Característica Reclamación (es Nivel de seguridad 1 Nivel de seguridad 2 Nivel de seguridad 3
General ácido
Yo en
Exp
nbf
Derechos de reproducción [1] orgullo
etiquetas
videos
Protección de claves de licencia tu No
conido No
máximo No
maximo No
Flujos concurrentes uid No No
límite No No
cbeh No No
Sid No No
Transmisiones simultáneas genéricas uid No No
límite No No
Sid No No
Registro de dispositivo uid No No
delimitar No No

Genera un token

Las bibliotecas están comúnmente disponibles para generar tokens JWT. Para obtener más detalles, consulte la JSON Web Tokens sitio.

una época & La herramienta de conversión de marca de tiempo de Unix puede ser útil cuando se trabaja con campos de tiempo.

Ejemplo de script de bash:

Script de ejemplo para generar el token 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"

Ejecute el script:

$ bash jwtgen.sh

Ejemplo usando Go

A continuación se muestra un ejemplo de referencia. Go implementación (como una herramienta cli) para generar tokens sin el uso de ninguna biblioteca de terceros:

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)
}

Resultados

Aquí hay un ejemplo de un token decodificado usando https://JWT.io especificando el conjunto completo de reclamaciones:

ENCABEZAMIENTO:

{
  "alg": "RS256",
  "type": "JWT"
}

CARGA ÚTIL:

{
  "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"
}

Prueba de reproducción

Aunque no es obligatorio, es posible que desee probar la reproducción de video antes de configurar un reproductor.

Entrega de URL estática

Solicitar reproducción:

curl -X GET \
https://edge.api.brightcove.com/playback/v1/accounts/{account_id}/videos/{video_id}/master.m3u8?bcov_auth={jwt}

Para obtener una lista de puntos finales de URL estáticas, consulte el documento Entrega de URL estáticas .

Restricciones de reproducción

Solicitar reproducción:

curl -X GET \
-H 'Authorization: Bearer {JWT}' \
https://edge-auth.api.brightcove.com/playback/v1/accounts/{your_account_id}/videos/{your_video_id}