簡介
要在訪問視頻庫時添加額外的保護,或對您的內容應用用戶級限制,您可以將呼叫傳遞給 Brightcove Playback API。JSON Web Token (JWT)
如果您是新手JWT's,請檢閱以下內容:
工作流程
要創建JSON Web Token (JWT)並註冊 Brightcove,請按照以下步驟操作:
產生公開私密金鑰組
您(發布者)將生成一對公私密鑰並將公鑰提供給 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將使用標準的一部分,以及 Brightcove 定義的一些私人索賠。您將創建一個用您的私鑰JSON Web Token簽名。
靜態 URL 傳遞的宣告
以下聲明可用於 Brightcove 的靜態 URL 交付。
| 宣稱 | 類型 | 必填 | 描述 | 
|---|---|---|---|
accid | 
          字串 | 擁有播放內容的帳號 ID | |
iat | 
          整數 | 這個令牌發出的時間,以秒為單位,自紀元 | |
exp | 
          整數 | 
            這個令牌的時間將不再有效,以秒為單位,自紀元以來。必須不超過 30 天iat
            
           | 
        |
drules | 
          串[] | 要應用的傳送規則操作 ID 列表。有關詳細信息,請參閱 實施交付規則 文檔。 如果還設置了 config_id查詢參數,它將被忽略,因為這個聲明覆蓋它。
            
           | 
        |
conid | 
          字串 | 如果存在,此令牌將僅授權特定的 Video Cloud 視頻 ID。這可以是 DRM/HLSe 流或非 DRM 資產。 必須是有效的視頻 ID。請注意,不支持參考 ID。  | 
        |
pro | 
          字串 | 在單一視訊可用多個情況下,指定保護類型。 值: 
  | 
        |
vod | 
          物件 | 包含視訊隨選視訊的特定組態選項。 | |
vod.ssai | 
          字串 | 您的伺服器端廣告插入 (SSAI) 組態識別碼。擷取 HLS 或破折號 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 |  
          整數 | 是 | 這個令牌發出的時間,以秒為單位,自紀元 | ||
exp | 
          整數 | 是 | 
            不是必需的,但強烈推薦。 此令牌不再有效的時間,自紀元以來的秒數。必須不超過 30 天 iat
            
           | 
        ||
nbf | 
          整數 | 此令牌開始生效的時間,自紀元以來的秒數。 如果未指定,令牌將立即可用。  | 
        |||
| 播放權限 | prid | 
          字串 | playback_rights_id用來覆寫此影片目錄中設定的 ID 此欄位未驗證  | 
        ||
tags | 
          陣列 < 字串 > | 如果存在,此令牌僅對具有列出的標籤值的視頻有效。只有這些視頻才有權播放。 | |||
vids | 
          陣列 < 字串 > | 如果存在,此令牌將只授權獲取一組視頻 ID 的許可證。  | 
        |||
| 許可證密鑰保護 | ua | 
          字串 | 如果存在,此令牌將僅對來自此用戶代理的請求有效。 該字段不必遵循任何特定格式。 你必須有許可證密鑰保護啟用。  | 
        ||
conid | 
          字串 | 如果存在,此權杖只會授權擷取特定 Video Cloud 視訊 ID 的授權。 必須是有效的視頻 ID 你必須有許可證密鑰保護啟用。  | 
        |||
maxip | 
          整數 | 是 | 如果存在,則此令牌將只能由此數量的不同 IP 地址使用。 必需的用於會話跟踪;僅限 HLSe (AES-128) 你必須有許可證密鑰保護啟用。  | 
        ||
maxu | 
          整數 | 是 | 
            如果存在,此令牌將僅對此數量的許可請求有效。 
 你必須有許可證密鑰保護啟用。  | 
        ||
| 並發流 | uid | 
          字串 | 是 | 是 | 最終檢視器的使用者 ID。該字段用於關聯多個會話以實施流並發。 您可以使用任意 ID(最多 64 個字符,限於 AZ、az、0-9 和 =/、@_.+-)。但是,根據您的用例,Brightcove 建議使用用戶標識符來跟踪每個用戶的會話,或者使用帳戶標識符來跟踪每個付費帳戶的會話。 工作階段並行需要  | 
          
        
climit | 
          整數 | 是 | 是 | 包括此字段後,它將啟用流並發檢查以及許可證續訂請求。此值表示允許並行觀察者的數目。 工作階段並行需要  | 
          
        |
cbeh | 
          字串 | 是 | 將值設定為BLOCK_NEW以啟用並行串流限制,以便在達到串流數目上限時封鎖任何新要求,即使來自相同使用者也是如此。將該值設定為 BLOCK_NEW_USER,只有在達到串流數目上限時,才封鎖來自新使用者的任何新要求。當達到最大流數時,默認值將阻止最早的流。  | 
          
        ||
sid | 
          字串 | 是 | 
            通過指定當前流的會話ID,您可以控制如何定義會話。默認情況下,會話定義為用戶代理(瀏覽器)+ IP地址+視頻ID。
              
             比如可以把session的定義放寬到IP地址+視頻ID  | 
          
        ||
| 裝置限制 | uid | 
          字串 | 是 | 是 | 最終檢視器的使用者 ID。該字段用於關聯多個會話以實施流並發。 您可以使用任意 ID(最多 64 個字符,限於 AZ、az、0-9 和 =/、@_.+-)。但是,根據您的用例,Brightcove 建議使用用戶標識符來跟踪每個用戶的會話,或者使用帳戶標識符來跟踪每個付費帳戶的會話。 裝置註冊時需要  | 
          
        
dlimit | 
          整數 | 是 | 是 | 包含此欄位時,它會控制可以與指定的使用者 ( uid ) 關聯的裝置數目。值必須是 > 0。如果在稍後的要求中捨棄該 dlimit值,先前允許的裝置將繼續運作。範例:如果值設定為 3,則使用者可以在裝置 A、B 和 C 上播放 (所有將被允許)。嘗試在裝置 D 上播放將被拒絕。如果將值更改為 1,用戶仍然可以在A,B和C的所有3種設備上播放,除非通過使用以下命令管理設備來手動撤消設備播放權限API。裝置註冊時需要  | 
          
        |
| 傳送規則 | drules | 
          串[] | 要應用的傳送規則操作 ID 列表。有關詳細信息,請參閱 實施交付規則 文檔。
            
             | 
        
按等級索賠
有幾個安全包可用於播放限制。有關詳細信息,請參見概述:布萊特灣播放限制文件。
以下是每個播放限制包的可用聲明:
| 功能 | 理賠 | 安全等級 1 | 安全等級 2 | 安全等級 3 | 
|---|---|---|---|---|
| 一般 | 酸 | 是 | 是 | 是 | 
| 我在 | 是 | 是 | 是 | |
| exp | 是 | 是 | 是 | |
| nbf | 是 | 是 | 是 | |
| 播放權 [1] | 驕傲 | 是 | 是 | 是 | 
| 標籤 | 是 | 是 | 是 | |
| 西元 | 是 | 是 | 是 | |
| 許可證密鑰保護 | ua | 否 | 是 | 是 | 
| 錐度 | 否 | 是 | 是 | |
| 最大的 | 否 | 是 | 是 | |
| 最大 | 否 | 是 | 是 | |
| 並發流 | uid | 否 | 否 | 是 | 
| 極限 | 否 | 否 | 是 | |
| cbeh | 否 | 否 | 是 | |
| 資料庫 | 否 | 否 | 是 | |
| 通用並發流 | uid | 否 | 否 | 是 | 
| 極限 | 否 | 否 | 是 | |
| 資料庫 | 否 | 否 | 是 | |
| 設備註冊 | uid | 否 | 否 | 是 | 
| 限制 | 否 | 否 | 是 | 
產生權杖
庫通常可用於生成 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}