はじめに
動画ライブラリへのアクセスをさらに保護したり、コンテンツにユーザー単位の制限を適用したりするために、Brightcove Playback API の呼び出しに JSON Web Token (JWT) を渡すことができます。
JWT に初めて触れる方は、以下を確認してください:
ワークフロー
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ハッシュアルゴリズムを使用したRSAアルゴリズムで署名されます(JWT仕様では"RS256"と記載されています)。他のJWTアルゴリズムはサポートされません。
JWTの標準的なクレームのサブセットと、Brightcoveが定義するプライベートクレームの一部を使用します。秘密鍵で署名された JSON Web Token を作成します。
JWTトークンを生成するためのライブラリは一般的に利用可能です。詳細については、JSON Web Tokensのサイトをご覧ください。
時間フィールドを扱う際には、Epoch & 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 を使用した例
以下は、サードパーティ- ライブラリを使用せずにトークンを生成するための(cliツールとしての)リファレンス Go 実装の例になります:
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}
静的URL配信のクレーム
以下のクレームは Brightcove の静的 URL 配信で使用できます。
クレーム | タイプ | 必須 | 説明 |
---|---|---|---|
accid |
文字列 | 再生されるコンテンツを所有するアカウントID | |
iat |
整数 | このトークンが発行された時刻(エポック秒) | |
exp |
整数 |
このトークンが無効になる時刻(エポック秒)。iat から最大30日以内である必要があります。
|
|
drules |
文字列配列 | 適用する配信ルールアクションIDのリスト。詳細については、配信ルールの実装ドキュメントを参照してください。config_id クエリパラメータが設定されている場合でも、このクレームによってオーバーライドされるため、このパラメーターは無視されます。
|
|
conid |
文字列 | 存在する場合、このトークンは特定の Video Cloud 動画ID のみを認証します。これは、DRM/HLSeストリームまたは非DRMアセットのいずれかを指定できます。 有効な動画IDである必要があります。参照IDはサポートされていません。 |
|
pro |
文字列 | 1つのビデオに複数のプロテクション タイプが存在する場合に指定するプロテクション タイプ。 値:
|
|
vod |
オブジェクト | Video-On-Demandの特定の設定オプションを含みます。 | |
vod.ssai |
文字列 | サーバーサイド広告挿入(SSAI)設定ID。このクレームは HLS または DASH VMAP を取得するために必要です。 | |
aud |
文字列 | はい* | JWTが意図されている受信者(オーディエンス)。 * 現在、このクレームはオプションですが、将来的に強制される予定です。 |
以下は、使用可能な JSON Web Token(JWT)のクレームの例です:
{
// アカウントID:このアカウントにのみ有効なJWT
"accid":"4590388311111",
// 発行時刻:JWTが作成されたタイムスタンプ
"iat":1575484132,
// 有効期限:JWTが期限切れになるタイムスタンプ
"exp":1577989732,
// 配信ルール:適用される配信ルールIDのリスト
"drules": ["0758da1f-e913-4f30-a587-181db8b1e4eb"],
// コンテンツID:JWTが有効な動画ID
"conid":"5805807122222",
// プロテクション:1つのビデオに複数の保護タイプが存在する場合の指定
"pro":"aes128",
// VODの特定の設定オプション
"vod":{
// 適用するSSAI構成
"ssai":"efcc566-b44b-5a77-a0e2-d33333333333"
}
}
再生制限のためのクレーム
以下のクレームは、Brightcove 再生制限と共に使用することができます。再生制限の一環として、以下を実装することができます:
機能 | クレーム | タイプ | 機能に必須 | DRMのみ | 説明 |
---|---|---|---|---|---|
一般 | accid |
文字列 | はい | 再生されるコンテンツの所有者のアカウントID | |
aud |
文字列の配列 |
JWTの対象受信者(オーディエンス)。 このクレームはオプションです。 aud クレームがない JWTトークンも Playback API に送信して動作します。aud クレームが存在する場合、値として playback.api.brightcove.com を含む必要があります。
|
|||
exp |
整数 | はい |
トークンの有効期限(エポックからの秒数)。発行時間(iat )から30日以内である必要があります。
|
||
nbf |
整数 | トークンの有効開始時間(エポックからの秒数)。指定がない場合は即時有効。 | |||
iat |
整数 | はい | トークンの発行時間(エポックからの秒数) | ||
ip |
文字列 | 地理的制限を評価するためのクライアントIPを上書きできます。有効な IPv4(短縮形なし)または IPv6 形式である必要があります。 例:Brightcove Playback APIの前にプロキシがある場合、 ip クレームをエンドユーザーのIPアドレスに設定することで、プロキシのIPではなくエンドユーザーのIPを使用して地理的制限を評価できます。
|
|||
再生権利 | prid |
文字列 | この動画のカタログに設定されているIDを上書きするための playback_rights_id 。
このフィールドは検証されません。 |
||
tags |
文字列の配列 | 存在する場合、このトークンは、リストされたタグ値を持つ動画にのみ有効です。これらの動画のみが再生が許可されます。 | |||
vids |
文字列の配列 | 存在する場合、トークンは一連の動画IDに対してのみライセンス取得が許可されます。
|
|||
ライセンスキーの保護 | ua |
文字列 | 存在する場合、このトークンは、User-Agent からのリクエストにのみ有効です。
このフィールドは特定のフォーマットに従う必要はありません。 ライセンスキーの保護 が有効である必要があります。 |
||
conid |
文字列 | 存在する場合、このトークンは、特定の Video Cloud 動画IDに対してのみライセンス取得が許可されます。
有効な動画IDである必要があります。 ライセンスキーの保護 が有効である必要があります。 |
|||
maxip |
整数 | はい | 存在する場合、このトークンは、この数の異なるIPアドレスのみで使用可能です。
セッション トラッキングには必須です。HLSe(AES-128)のみ。 ライセンスキーの保護 が有効である必要があります。 |
||
maxu |
整数 | はい |
存在する場合、このトークンは、この数のライセンスリクエストに対してのみ有効です。
ライセンスキーの保護 が有効である必要があります。 |
||
同時ストリーム | 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 の値が下げられても引き続き動作します。
例: 値が 3 に設定されている場合、ユーザーはデバイスA、B、Cで再生可能です(すべて許可されます)。デバイスDでの再生は拒否されます。
値が 1 に変更された場合、Playback Rights API を使用してデバイスを管理することによってデバイスが手動で無効にされない限り、ユーザーは3つのデバイスA、B、Cすべてで再生することができます。
デバイス登録の場合、必須 |
|
配信ルール | drules |
文字列 | 適用する配信ルールのアクションID。詳細については、配信ルールの実装ドキュメントをご覧ください。
|
階層別クレーム
再生制限のために利用可能な複数のセキュリティパッケージが用意されています。詳細については、概要: Brightcove 再生制限ドキュメントを参照してください。
以下は、各再生制限パッケージで利用可能なクレームです:
機能 | クレーム | セキュリティ層1 | セキュリティ層2 | セキュリティ層3 |
---|---|---|---|---|
一般 | accid | はい | はい | はい |
aud | はい | はい | はい | |
iat | はい | はい | はい | |
exp | はい | はい | はい | |
nbf | はい | はい | はい | |
再生権限 [1] | prid | はい | はい | はい |
tags | はい | はい | はい | |
vids | はい | はい | はい | |
ライセンスキー保護 | ua | いいえ | はい | はい |
conid | いいえ | はい | はい | |
maxip | いいえ | はい | はい | |
maxu | いいえ | はい | はい | |
同時ストリーム | uid | いいえ | いいえ | はい |
climit | いいえ | いいえ | はい | |
cbeh | いいえ | いいえ | はい | |
sid | いいえ | いいえ | はい | |
汎用同時ストリーム | uid | いいえ | いいえ | はい |
climit | いいえ | いいえ | はい | |
sid | いいえ | いいえ | はい | |
デバイス登録 | uid | いいえ | いいえ | はい |
dlimit | いいえ | いいえ | はい |