サポートに連絡する| システムステータス
ページコンテンツ

    JSON Web トークン (JWT)

    の作成このトピックでは、ブライトコーブプレイバックで使用できる JSON Web トークン (JWT) の作成方法を学習します。制限事項。

    はじめに

    ビデオライブラリにアクセスするときに保護のレベルを追加したり、コンテンツにユーザーレベルの制限を適用したりするには、 JSON Web Token(JWT) Brightcove PlaybackAPIを呼び出します。トークンを作成するには、次の手順を実行します。

    1. 公開鍵と秘密キーのペアを生成する
    2. Brightcoveに公開鍵を登録する
    3. JSON ウェブトークンの作成
    4. テスト再生

    公開鍵と秘密鍵のペアを生成

    公開者(パブリッシャー)は公開鍵と秘密鍵のペアを生成し、その公開鍵をブライトコーブに提供します。トークンに署名するには、秘密キーを使用します。プライベートキーはブライトコーブと共有されません。

    公開鍵と秘密鍵のペアを生成する方法は多数あります。以下にいくつかの例を挙げます。

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

    公開鍵を登録する

    秘密鍵を所有しており、その秘密鍵を使用して署名付きトークンを生成します。公開鍵をブライトコーブと共有し、トークンを確認します。キー API を使用すると、パブリックキーをブライトコーブに登録できます。

    API の詳細については、「認証 API の使用」ドキュメントを参照してください。

    JSON ウェブトークンの作成

    パブリッシャーは JSON ウェブトークン (JWT) を作成します。トークンは、SHA-256ハッシュアルゴリズム(JWT仕様では「」として識別されるRSAアルゴリズムで署名されています。 RS256 ")他のJWTアルゴリズムはサポートされません。

    標準的な JSON Web トークンのクレームのサブセットと、ブライトコーブによって定義されたいくつかのプライベートクレームが使用されます。を作成しますJSONWebトークン秘密鍵で署名しました。

    静的 URL 配信のクレーム

    Brightcoveの静的 URL 配信では、次のクレームを使用できます。

    フィールド タイプ 必須 説明
    accid ストリング 再生中のコンテンツを所有するアカウント ID
    drules 文字列 [] 適用する配信ルールのアクション ID のリスト。詳細については、「配信ルールの実装」を参照してください。config_id クエリパラメータも設定されている場合、この要求がオーバーライドされるため、無視されます。
    exp 整数 このトークンが有効でなくなる時間(エポックからの秒単位)。から 30 日以内にする必要があります。iat
    iat 整数 このトークンが発行された時間(エポックからの秒単位)
    conid ストリング 存在する場合、このトークンは特定の Video Cloud ビデオ ID のライセンス取得のみを許可します。

    有効な動画IDである必要があります。参照IDはサポートされていないことに注意してください。
    pro ストリング 1 つのビデオで複数のビデオを使用できる場合の保護タイプを指定します。

    値:
    • 「」(コンテンツをクリアするためのデフォルト)
    • 「aes128"
    • 「Widevine」
    • 「playready」
    • フェアプレイ
    vod オブジェクト ビデオオンデマンドの特定の設定オプションが含まれています。
    vod.ssai ストリング サーバーサイド広告挿入 (SSAI) の設定ID。この要求は、HLS または DASH VMAP のいずれかを取得するために必要です

    以下に、使用できる JSON Web トークン (JWT) の主張の例を示します。

    {
    // account id: JWT is only valid for this accounts
    "accid":"4590388311111",
    // drules: list of delivery rule IDs to be applied
    "drules": ["0758da1f-e913-4f30-a587-181db8b1e4eb"]
    // expires: timestamp when JWT expires
    "exp":1577989732,
    // issued at: timestamp when the JWT was created
    "iat":1575484132,
    // 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"
    }
    }

    再生制限に関する申し立て

    ブライトコーブの再生制限では、以下のクレームを使用できます

    機能 フィールド タイプ 機能には必須 DRMのみ 説明
    将軍 accid ストリング はい 再生中のコンテンツを所有するアカウント ID
    exp 整数 はい このトークンが有効でなくなる時間(エポックからの秒単位)。から 30 日以内にする必要があります。iat
    iat 整数 はい このトークンが発行された時間(エポックからの秒単位)
    ua ストリング 存在する場合、このトークンは、このユーザーエージェントからの要求に対してのみ有効です。

    このフィールドは、特定の形式に従う必要はありません。
    ライセンスキー保護を有効にする必要があります。
    conid ストリング 存在する場合、このトークンは特定の Video Cloud ビデオ ID のライセンス取得のみを許可します。

    有効な動画IDである必要があります
    ライセンスキー保護を有効にする必要があります。
    再生権限 prid ストリング A playback_rights_id。このビデオのカタログに設定されているIDを上書きするために使用されます

    このフィールドは検証されていません
    tags Array <Strings> 存在する場合、このトークンは、リストされたタグ値を持つ動画に対してのみ有効です。これらのビデオのみが再生が許可されます。
    vids Array <Strings> 存在する場合、このトークンは、一連のビデオ ID のライセンス取得のみを許可します。
    同時ストリーム uid ストリング はい はい エンドビューアのユーザー ID。このフィールドは、ストリーム同時実行を強制するために、複数のセッションを相関させるために使用されます。

    デバイス登録に必要
    climit 整数 はい はい このフィールドを指定すると、ライセンス更新要求とともにストリーム同時実行チェックが有効になります。この値は、許可される同時ウォッチャーの数を示します。

    セッションの同時実行に必要
    cbeh ストリング はい BLOCK_NEW値をに設定すると、ストリームの最大数に達したときに、同じユーザーからの新しい要求をブロックする同時ストリーム制限が有効になります。

    ストリームの最大数に達したときにのみ、BLOCK_NEW_USER新しいユーザーからの新しいリクエストをブロックするには、値をに設定します。

    デフォルトでは、ストリームの最大数に達すると、最も古いストリームがブロックされます。
    cexp ストリング はい セッションの同時実行の有効期限-デフォルトはコンテンツ期間の 2 倍です。

    これは、セッションが有効である期間を定義します。その後、エンドビューアは、再生を続行するために新しいセッションを開始する必要があります。例: 2hまたは42m
    sid ストリング はい 現在のストリームのセッション ID を指定すると、セッションの定義方法を制御できます。デフォルトでは、セッションはユーザーエージェント(ブラウザ)+ IPアドレス+ビデオIDとして定義されます。

    たとえば、IPアドレス+ビデオIDへのセッションの定義を緩めることができます
    maxu 整数 はい 存在する場合、このトークンはこの数のライセンス要求に対してのみ有効です。

    • HLSE の場合、プレーヤーはビデオの再生時に複数のリクエストを行います。通常、レンディションごとに 1 つずつリクエストされます。は、maxuこれらの追加要求を考慮するのに十分な高さに設定する必要があります。
    セッショントラッキングに必要。HLSe(AES-128)のみ
    ライセンスキー保護を有効にする必要があります。
    デバイスの制限 uid ストリング はい はい エンドビューアのユーザー ID。このフィールドは、ストリーム同時実行を強制するために、複数のセッションを相関させるために使用されます。

    デバイス登録に必要
    dlimit 整数 はい はい このフィールドを含めると、指定されたユーザに関連付けることができるデバイスの数を制御します(uid)。値はでなければなりません > 0

    以前に許可されたデバイスは、dlimit後の要求で値がドロップされた場合でも、引き続き動作します。

    例:値がに設定されている場合3、ユーザーはデバイス A、B、C でプレイできます (すべて許可されます)。デバイス D で再生しようとすると拒否されます。

    値がに変更された場合でも1、Playback Rights API でデバイスを管理してデバイスを手動で取り消さない限り、ユーザーは 3 つのデバイス A、B、C すべてで再生できます。

    デバイス登録に必要
    maxip 整数 はい 存在する場合、このトークンは、この番号の異なるIPアドレスでのみ使用できます。

    セッショントラッキングに必要。HLSe(AES-128)のみ
    ライセンスキー保護を有効にする必要があります。

    トークンを生成する

    JWTトークンを生成するためのライブラリは一般的に利用可能です。詳細については、 JSON Webトークンのサイトを参照してください。

    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 second.
    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"
    }
    

    テスト再生

    必須ではありませんが、プレーヤーを設定する前にビデオ再生をテストすることをお勧めします。

    再生をリクエスト:

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

    ページの最終更新日05 Oct 2021