JSON 웹 토큰 만들기 (JWT)

이 항목에서는 Brightcove 재생 제한과 함께 사용할 수 있는 JSON 웹 토큰(JWT)을 생성하는 방법을 배웁니다.

서문

비디오 라이브러리에 액세스할 때 추가 보호 수준을 추가하거나 콘텐츠에 사용자 수준 제한을 적용하려면JSON Web Token (JWT)호출과 함께 를 Brightcove Playback API로 전달할 수 있습니다.

를JWT's처음 사용하는 경우 다음을 검토하십시오.

작업 흐름

Brightcove를JSON Web Token (JWT)만들고 등록하려면 다음 단계를 따르십시오.

  1. 공개-개인 키 쌍 생성
  2. 브라이트코브에 공개 키 등록
  3. 생성JSON Web Token
  4. 재생 테스트

공개-개인 키 쌍 생성

귀하(게시자)는 공개-개인 키 쌍을 생성하고 공개 키를 Brightcove에 제공합니다. 개인 키를 사용하여 토큰에 서명합니다. 개인 키는 Brightcove와 공유되지 않습니다.

공개-개인 키 쌍을 생성하는 방법에는 여러 가지가 있습니다. 다음은 몇 가지 예입니다.

bash 스크립트의 예:

키 쌍을 생성하는 예제 스크립트:

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

스크립트를 실행합니다.

$ bash keygen.sh
사용 예제Go

Go프로그래밍 언어를 사용하여 키 쌍을 생성하는 예:

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

node.js 사용 예제

node.js 를 사용하여 키 쌍을 생성하는 예:

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

공개 키 등록

개인 키를 소유하고 있으며 이를 사용하여 서명된 토큰을 생성합니다. 공개 키를 Brightcove와 공유하여 토큰을 확인합니다. 키 API를 사용하면 공개 키를 Brightcove에 등록할 수 있습니다.

API에 대한 자세한 내용은 인증 API 사용 문서를 참조하세요.

생성JSON Web Token

게시자는JSON Web Token (JWT) 를 만듭니다. 토큰은 SHA-256 해시 알고리즘 (JWT 사양에서 " RS256“으로 식별됨) 을 사용하여 RSA 알고리즘으로 서명됩니다. 다른 JWT 알고리즘은 지원되지 않습니다.

브라이트코브가 정의한 일부 비공개 소유권 주장과 함께JSON Web Token claims표준의 하위 집합이 사용됩니다. 개인 키로JSON Web Token서명된 을 만듭니다.

정적 URL 전송에 대한 클레임

다음 클레임은 Brightcove의 정적 URL 전달 과 함께 사용할 수 있습니다.

주장하다 유형 필수 설명
accid 문자열 재생 중인 콘텐츠를 소유한 계정 ID입니다.
iat 정수 이 토큰이 발행된 시간 (Epoch 이후 초)
exp 정수 이 토큰이 더 이상 유효하지 않은 시간 (Epoch 이후 초) 입니다. 30 일을 넘지 않아야합니다. iat
drules 문자열 [] 적용할 배달 규칙 작업 ID 목록입니다. 자세한 내용은 전달 규칙 구현 문서를 참조하십시오.
config_id쿼리 매개 변수도 설정하면 이 클레임이 이를 무시하므로 쿼리 매개 변수가 무시됩니다.
conid 스트링 있는 경우 이 토큰은 특정 Video Cloud 비디오 ID만 인증합니다. 이것은 DRM/HLSe 스트림이거나 DRM이 아닌 자산일 수 있습니다.

유효한 동영상 ID여야 합니다. 참조 ID는 지원되지 않습니다.
pro 문자열 단일 비디오에 대해 여러 개를 사용할 수 있는 경우 보호 유형을 지정합니다.

값:
  • “" (내용 지우기 기본값)
  • 분류: 애스128
  • "widevine"
  • “재생 준비”
  • "fairplay"
vod 객체 주문형 비디오에 대한 특정 구성 옵션이 포함되어 있습니다.
vod.ssai 스트링 서버 측 광고 삽입 (SSAI의) 구성 ID입니다. 이 클레임은 HLS 또는 DASH VMAP을 검색하는데 필요합니다 .

다음은 사용할 수있는JSON Web Token (JWT) 클레임의 예입니다.

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

재생 제한에 대한 클레임

다음 클레임은 Brightcove 재생 제한 과 함께 사용할 수 있습니다. 재생 제한의 일부로 다음을 구현할 수 있습니다.

기능 주장하다 유형 기능에 필요 DRM 전용 설명
일반 accid 문자열 예 재생 중인 콘텐츠를 소유한 계정 ID입니다.
iat 정수 예 이 토큰이 발행된 시간 (Epoch 이후 초)
exp 정수 예 필수는 아니지만 강력하게 권장합니다.

이 토큰이 더 이상 유효하지 않게 되는 시간(Epoch 이후 초)입니다. 30 일을 넘지 않아야합니다. iat
nbf 정수 이 토큰이 유효하기 시작하는 시간(Epoch 이후 초)입니다.
지정하지 않으면 토큰을 즉시 사용할 수 있습니다.
재생 권한 prid 스트링 이 비디오의 카탈로그에 설정된 ID를 재정의하는playback_rights_id데 사용됩니다.

이 필드는 검증되지 않았습니다.

tags 배열 < 문자열 > 있는 경우 이 토큰은 나열된 태그 값이 있는 비디오에만 유효합니다. 이 비디오만 재생이 승인됩니다.
vids 배열 < 문자열 > 있는 경우 이 토큰은 비디오 ID 집합에 대한 라이선스 가져오기만 승인합니다.

라이센스 키 보호 ua 문자열 존재하는 경우, 이 토큰은이 사용자 에이전트의 요청에 대해서만 유효합니다.

이 필드는 특정 형식을 따를 필요가 없습니다.
라이선스 키 보호를활성화해야 합니다.
conid 문자열 이 토큰이 있는 경우 특정 Video Cloud 비디오 ID에 대한 라이선스 가져오기만 승인합니다.

유효한 비디오
ID여야 합니다. 라이선스 키 보호를활성화해야 합니다.
maxip 정수 예 있는 경우 이 토큰은 이 숫자의 다른 IP 주소에서만 사용할 수 있습니다.

필수의세션 추적용 HLSe(AES-128) 전용
당신은 가지고 있어야합니다라이센스 키 보호가능합니다.
maxu 정수 예 있는 경우 이 토큰은 이 라이센스 요청 수에 대해서만 유효합니다.

  • HLse의 경우 플레이어는 비디오를 재생할 때 여러 번 요청하며 일반적으로 렌디션당 하나씩 요청을 합니다. 은 이러한 추가 요청을 처리할 수 있을 만큼 충분히 높게 설정해야 합니다. maxu
필수의세션 추적용 HLSe(AES-128) 전용
당신은 가지고 있어야합니다라이센스 키 보호가능합니다.
동시 스트림 uid 문자열 예 예 엔드 뷰어의 사용자 ID입니다. 이 필드는 스트림 동시성을 적용하기 위해 여러 세션을 상호 연관시키는 데 사용됩니다.

임의의 ID를 사용할 수 있습니다(최대 64자, AZ, az, 0-9 및 =/,@_.+-로 제한됨). 그러나 사용 사례에 따라 Brightcove는 사용자당 세션을 추적하기 위한 사용자 식별자 또는 유료 계정당 세션을 추적하기 위한 계정 식별자를 권장합니다.

세션동시성에 필요
climit 정수 예 예 이 필드를 포함하면 라이선스 갱신 요청과 함께 스트림 동시성 검사를 사용할 수 있습니다. 이 값은 허용되는 동시 관찰자 수를 나타냅니다.

세션동시성에 필요
cbeh 문자열 예 값을 로 설정하면 동시 스트림 제한을BLOCK_NEW활성화하여 최대 스트림 수에 도달했을 때 동일한 사용자의 새 요청을 포함하여 차단할 수 있습니다.

최대 스트림 수에 도달했을 때 새 사용자의 새 요청만BLOCK_NEW_USER차단하려면 값을 로 설정합니다.

기본값은 최대 스트림 수에 도달하면 가장 오래된 스트림을 차단합니다.
sid 문자열 예 현재 스트림의 세션 ID를 지정하면 세션 정의 방법을 제어 할 수 있습니다. 기본적으로 세션은 User-Agent (브라우저) + IP 주소 + 비디오 ID로 정의됩니다.

예를 들어 세션 정의를 IP 주소 + 비디오 ID로 풀 수 있습니다.

디바이스 제한 uid 문자열 예 예 엔드 뷰어의 사용자 ID입니다. 이 필드는 스트림 동시성을 적용하기 위해 여러 세션을 상호 연관시키는 데 사용됩니다.

임의의 ID를 사용할 수 있습니다(최대 64자, AZ, az, 0-9 및 =/,@_.+-로 제한됨). 그러나 사용 사례에 따라 Brightcove는 사용자당 세션을 추적하기 위한 사용자 식별자 또는 유료 계정당 세션을 추적하기 위한 계정 식별자를 권장합니다.

디바이스등록에 필요함
dlimit 정수 예 예 이 필드가 포함되면 지정된 사용자 ( uid ) 와 연결할 수 있는 장치 수를 제어합니다. 값은 이어야 합니다 > 0 .

이후 요청에서dlimit값이 삭제되면 이전에 허용된 장치가 계속 작동합니다.

예: 값을 로 설정하면 사용자는 장치 A 3 , B 및 C에서 재생할 수 있습니다 (모두 허용됨). 장치 D에서 재생하려고 하면 거부됩니다.

값을 로1변경하면 재생권한 API로 장치를 관리하여 장치를 수동으로 취소하지 않는 한 사용자는 3개의 장치 A, B, C 모두에서 재생할수 있습니다.

디바이스등록에 필요함
배달 규칙 drules 문자열 [] 적용할 배달 규칙 작업 ID 목록입니다. 자세한 내용은 전달 규칙 구현 문서를 참조하십시오.

계층별 청구

재생 제한에 대해 여러 보안 패키지를 사용할 수 있습니다. 자세한 내용은개요 : 브라이트코브 재생 제한문서.

각 재생 제한 패키지에 사용할 수 있는 클레임은 다음과 같습니다.

기능 클레임 보안 계층 1 보안 계층 2 보안 계층 3
일반 사고 예 예 예
앗 예 예 예
경험치 예 예 예
nbf 예 예 예
재생 권한 [1] 프라이드 예 예 예
태그 예 예 예
비디오 예 예 예
라이센스 키 보호 어 아니요 예 예
원추형 아니요 예 예
맥시프 아니요 예 예
맥수 아니요 예 예
동시 스트림 액체 아니요 아니요 예
등반 아니요 아니요 예
씨베 아니요 아니요 예
시드 아니요 아니요 예
일반 동시 스트림 액체 아니요 아니요 예
등반 아니요 아니요 예
시드 아니요 아니요 예
장치 등록 액체 아니요 아니요 예
dlimit 아니요 아니요 예

토큰 생성

라이브러리는 일반적으로 JWT 토큰을 생성하는 데 사용할 수 있습니다. 자세한 내용은JSON Web Tokens사이트를 참조하십시오.

시대 & Unix 타임스탬프 변환 도구는 시간 필드로 작업할 때 유용할 수 있습니다.

bash 스크립트의 예:

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"

스크립트를 실행합니다.

$ bash jwtgen.sh

사용 예제Go

다음은 타사 라이브러리를 사용하지 않고 토큰을 생성하기위한 참조Go구현 (cli 도구) 의 예입니다.

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

성과

다음은https://JWT.io을 사용하여 전체 클레임 집합을 지정하는 디코딩된 토큰의 예입니다.

헤더:

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

페이로드:

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

재생 테스트

필수는 아니지만 플레이어를 구성하기 전에 비디오 재생을 테스트할 수 있습니다.

정적 URL 전송

재생 요청:

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

정적 URL 엔드포인트 목록은 정적 URL 전달 문서를 참조하십시오.

재생 제한

재생 요청:

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