JSON 웹 토큰 만들기 (JWT)

이 항목에서는 Brightcove 재생 제한과 함께 사용할 수 있는JSON Web Token (JWT) 를 만드는 방법을 알아봅니다.

서문

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

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

작업 흐름

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

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

공개-개인 키 쌍 생성

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

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

배쉬 스크립트 예제:

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

#!/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 알고리즘은 지원되지 않습니다.

Brightcove에서 정의한 일부 비공개 클레임과 함께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자, A-Z, a-z, 0-9 및 =/, @_ .+- 로 제한됨). 그러나 사용 사례에 따라 Brightcove는 사용자당 세션을 추적하기 위한 사용자 식별자 또는 유료 계정당 세션을 추적하기 위한 계정 식별자를 권장합니다.

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

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

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

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

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

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

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

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

이전에 허용된 장치는 이후 요청에서dlimit값을 삭제해도 계속 작동합니다.

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

값을 로1변경해도 사용자는 Playback Rights API로 기기를 관리하여 기기를 수동으로 취소하지 않는 한 3개의 기기 A, B, C 모두에서 계속 플레이할 수있습니다.

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

계층별 청구

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

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

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

토큰 생성

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

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

배쉬 스크립트 예제:

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}