簡介
要在訪問您的視頻庫時添加額外級別的保護,或對您的內容應用用戶級別限制,您可以通過JSON Web Token (JWT)通過調用 Brightcove Playback API。
如果您不熟悉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 |
字串 | A 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 列表。有關詳細信息,請參閱 實施交付規則 文檔。
|
按等級索賠
有幾個安全包可用於播放限制。有關詳細信息,請參見概述:Brightcove 播放限制文檔。
以下是每個播放限制包的可用聲明:
功能 | 理賠 | 安全等級 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}