DRM Callback
概要
KollusモバイルダウンロードDRMを利用してダウンロードしたコンテンツの「再生回数」、「再生有効期限」、「再生時間」を設定してコンテンツの再生を制御する機能について説明する文書です。サービス提供形態によって重複制限の設定として使用されることもあります。
Expire option
設定項目のdata-typeや値が範囲から外れた場合、エンドユーザーのコンテンツの利用に問題が発生する可能性があります。また、誤った設定値による使用回数の過剰などを回収する方法はありませんので設定に注意してください。
Expire count : 再生回数の制限
- data-type : integer
- range : 0 ~ 1000
- 0 : unlimited (無制限)
Expire date : 有効期限の制限
data-type : integer, unixtime stamp
コンテンツの有効期限(再生可能期限)
終了日(日時)
例) 2014. 3. 3. 5時 45分 30秒 GMT → 13938255310 : unlimited (無制限)
最大値 : 2029年 12月 31日 23時 59分 59秒 (1893455999)
Expire playtime : 再生時間(倍速再生の場合は倍速が適用された時間で計算)
- data-type : integer
- range : 0, 60 ~ 604800 (単位: 秒, min : 60秒, max:1週間)
- 0 : unlimited (無制限)
DRM callback
DRM ポリシーを適用するためには、チャンネルにDRM Callback URLを設定する必要があります。
チャンネルにDRM Callback URLを設定すると、コンテンツをダウンロードする時点にDRM Callback URLを呼び出して、リターン値をDRMポリシーとして使用します。ダウンロードされるDRMポリシー情報はJWT EncodeされてHttp Bodyにリターンしなければなりません。
注意:
- DRM Callback URLがレスポンスしないとダウンロードできません。
- アルゴリズムはHS256のみ対応しており、Httpヘッダに指定された(X-KOLLUS-USERKEY)ヘッダに“ユーザーキー”を共に転送しなければなりません。
Callback flow
- 配信チャンネルに DRM CallbackURLを設定します。
- ex> http://www.foo.com/auth.php を顧客のDRM認証サーバーとする
- JSONデータを生成してJWTでエンコードする
- Kollus mobile playerがhttp://www.foo.com/auth.php に以下の情報をPOST転送します。
- session_key : Playerが生成したリクエスト確認用のセッションキー
- kind3のcontent_expire_resetの場合、リクエストしたsession_keyを確認します。
- Callback v2が適用されるv1.6以降から使用可能です。
- kind : 1 - 3
- client_user_id : ユーザID(サービス会員情報)
- Media token 生成の際に含まれたID
- player_id : ユーザのデバイスID
- device_name : ユーザのデバイス名
- Android, iOS別に転送される端末名が異なります。
- iOS:事前にApple社が定義した文字列で転送されます。
- Android:デバイス名、モデル名が転送されます。※該当する情報がない場合NULLで転送
- media_content_key : ダウンロードするコンテンツキー
- チャンネルに登録されたコンテンツのメディアコンテンツキー(ユニーク)
- 同じコンテンツを複数のチャンネルに登録する場合、それぞれのmedia_content_keyは全て異なります。
- session_key : Playerが生成したリクエスト確認用のセッションキー
- 顧客のDRM認証サーバーは転送された上記の情報に基づいて以下のjson フォーマットのdataをJWT payloadに追加してEncoding します。ヘッダーに指定された“ユーザキー(X-KOLLUS-USERKEY)"を一緒に転送します。
(顧客が認証データをPlayerに転送する全てのデータは必ずinteger型で転送しなければなりません。)
device_name 追加説明
Andoid
Androidアプリケーションの開発に使用される Build.DEVICE, Build.MODELは /(スラッシュ)で区分した文字列を作成して使用してください。
- Build.DEVICE+”/”+Build.MODEL
- デバイスによって該当情報はNULLで表示される可能性があります。
iOS
iOSから提供するdevice-nameを使用します。
| Device Type | Product Name |
|---|---|
| iPhone1,1 | iPhone |
| iPhone1,2 | iPhone 3G |
| iPhone2,1 | iPhone 3GS |
| iPhone3,1 | iPhone 4 (GSM) |
| iPhone3,3 | iPhone 4 (CDMA) |
| iPhone4,1 | iPhone 4S |
| iPhone5,1 | iPhone 5 (A1428) |
| iPhone5,2 | iPhone 5 (A1429) |
| iPhone5,3 | iPhone 5c (A1456/A1532) |
| iPhone5,4 | iPhone 5c (A1507/A1516/A1529) |
| iPhone6,1 | iPhone 5s (A1433/A1453) |
| iPhone6,2 | iPhone 5s (A1457/A1518/A1530) |
| iPhone7,1 | iPhone 6 Plus |
| iPhone7,2 | iPhone 6 |
| iPhone8,1 | iPhone 6s |
| iPhone8,2 | iPhone 6s Plus |
| iPhone8,4 | iPhone SE |
| iPhone9,1 | iPhone 7 (A1660/A1779/A1780) |
| iPhone9,2 | iPhone 7 Plus (A1661/A1785/A1786) |
| iPhone9,3 | iPhone 7 (A1778) |
| iPhone9,4 | iPhone 7 Plus (A1784) |
| iPhone10,1 | iPhone 8 (A1863/A1906) |
| iPhone10,2 | iPhone 8 Plus (A1864/A1898) |
| iPhone10,3 | iPhone X (A1865/A1902) |
| iPhone10,4 | iPhone 8 (A1905) |
| iPhone10,5 | iPhone 8 Plus (A1897) |
| iPhone10,6 | iPhone X (A1901) |
| iPad1,1 | iPad |
| iPad2,1 | iPad 2 (Wi-Fi) |
| iPad2,2 | iPad 2 (GSM) |
| iPad2,3 | iPad 2 (CDMA) |
| iPad2,4 | iPad 2 (Wi-Fi, revised) |
| iPad2,5 | iPad mini (Wi-Fi) |
| iPad2,6 | iPad mini (A1454) |
| iPad2,7 | iPad mini (A1455) |
| iPad3,1 | iPad (3rd gen, Wi-Fi) |
| iPad3,2 | iPad (3rd gen, Wi-Fi+LTE Verizon) |
| iPad3,3 | iPad (3rd gen, Wi-Fi+LTE AT&T) |
| iPad3,4 | iPad (4th gen, Wi-Fi) |
| iPad3,5 | iPad (4th gen, A1459) |
| iPad3,6 | iPad (4th gen, A1460) |
| iPad4,1 | iPad Air (Wi-Fi) |
| iPad4,2 | iPad Air (Wi-Fi+LTE) |
| iPad4,3 | iPad Air (Rev) |
| iPad4,4 | iPad mini 2 (Wi-Fi) |
| iPad4,5 | iPad mini 2 (Wi-Fi+LTE) |
| iPad4,6 | iPad mini 2 (Rev) |
| iPad4,7 | iPad mini 3 (Wi-Fi) |
| iPad4,8 | iPad mini 3 (A1600) |
| iPad4,9 | iPad mini 3 (A1601) |
| iPad5,1 | iPad mini 4 (Wi-Fi) |
| iPad5,2 | iPad mini 4 (Wi-Fi+LTE) |
| iPad5,3 | iPad Air 2 (Wi-Fi) |
| iPad5,4 | iPad Air 2 (Wi-Fi+LTE) |
| iPad6,3 | iPad Pro (9.7 inch) (Wi-Fi) |
| iPad6,4 | iPad Pro (9.7 inch) (Wi-Fi+LTE) |
| iPad6,7 | iPad Pro (12.9 inch, Wi-Fi) |
| iPad6,8 | iPad Pro (12.9 inch, Wi-Fi+LTE) |
| iPad6,11 | iPad 9.7-Inch 5th Gen (Wi-Fi Only) |
| iPad6,12 | iPad 9.7-Inch 5th Gen (Wi-Fi/Cellular) |
| iPad7,1 | iPad Pro (12.9 inch, A1670) |
| iPad7,2 | iPad Pro (12.9 inch, A18219) |
| iPad7,3 | iPad Pro (10.5 inch, A1701) |
| iPad7,4 | iPad Pro (10.5 inch, A1709) |
| iPad7,5 | iPad (6th gen, A1893) |
| iPad7,6 | iPad (6th gen, A1954) |
| iPod1,1 | iPod touch |
| iPod2,1 | iPod touch (2nd gen) |
| iPod3,1 | iPod touch (3rd gen) |
| iPod4,1 | iPod touch (4th gen) |
| iPod5,1 | iPod touch (5th gen) |
| iPod7,1 | iPod touch (6th gen) |
DRM Callback 新規バージョン v2バージョン1.4まで提供していた「kind1, kind2, kind3」の個別で呼び出しを行われたことを、一回の呼出で統合しました。ユーザー側の呼出が増えることで対応が難しいとの多数の顧客からの意見に対応しました。新しいバージョンを"v2"で、以前バージョンを"v1"で記載します。
Reuest
| 区分 | Description |
|---|---|
| POST | Http POSTでリクエスト (parameterではない) |
| items | JsonArrayで構成されたstring |
items項目はkind1, kind2, kind3の全てのデータが含まれた呼出にすることができます。
items (JsonArray)
kind1, kind2
| 区分 | Description |
|---|---|
| kind | 1,2 |
| client_user_id | ユーザーID, media_token 生成に使用されたclient_user_idと同一です。 |
| player_id | ユーザーのデバイスID |
| hardware_id | デバイスのhardware ID(PC, 内容がある場合) |
| device_name | ユーザーデバイスのモデル名 |
| media_content_key | コンテンツのメディアコンテンツキー |
| uservalues | JSON format (VideoGatewayの呼出に使用されたuservalue0~9) |
| localtime | デバイスの時刻 (UTC) |
kind3
| 区分 | Description |
|---|---|
| kind | 3 |
| session_key | content_expire_resetリクエストの際に同じsession_keyを確認します。 |
| client_user_id | ユーザーID, media_token 生成に使用されたclient_user_idと同一です。 |
| player_id | ユーザのデバイスID |
| hardware_id | デバイスのhardware ID(PC, 内容がある場合) |
| device_name | ユーザーデバイスのモデル名 |
| media_content_key | コンテンツのメディアコンテンツキー |
| start_at | unixtimestamp (localtime) - 転送リクエスト時刻 |
| uservalues | JSON format (VideoGatewayの呼出に使用されたuservalue0~9) |
| content_expired | コンテンツの有効期限を確認 flag ( 1: 再生不可, 0: 再生可能) |
| check_expired | 更新チェックの有効期限を確認 flag ( 1: 再生不可, 0: 再生可能) |
| reset_req | 一括更新なのかを確認 ( 0 (default), 1: 一括更新) |
| expiration_date | 有効期限終了日(unixtimestamp) |
| localtime | デバイスの時刻 (UTC) |
Items Sample
[
{
"kind": 1,
"media_content_key" : "XXX-MEDIA_CONTENTKEY-XXX",
"client_user_id": "XXXXXXX",
"player_id": "xxxxxxxxxxxxxxxx",
"device_name": "XXXXX",
"uservalues": {
"uservalue0": "value0"
}
},
{
"kind": 2,
"media_content_key" : "XXX-MEDIA_CONTENTKEY-XXX",
"client_user_id": "XXXXXXX",
"player_id": "xxxxxxxxxxxxxxxx",
"device_name": "XXXXX",
"uservalues": {
"uservalue0": "value0"
}
},
{
"kind": 3,
"session_key" : "XXX-SESSION_KEY-XXX", ← content_expire_reset リクエストする際に確認します。
"media_content_key" : "XXX-MEDIA_CONTENTKEY-XXX",
"client_user_id": "XXXXXXX",
"player_id": "xxxxxxxxxxxxxxxx",
"device_name": "XXXXX",
"uservalues": {
"uservalue1": "value1"
}
}
]
Response
Arrayは“data”フィールドで受け取る
kind1
| 区分 | 必須 | Description |
|---|---|---|
| (int) kind | O | 1 |
| (string) media_content_key | O | コンテンツのメディアコンテンツキー |
| (int) expiration_date | 有効期限のunixtime stamp 最大値 : 2029年 12月 31日 23時 59分 59秒 (1893455999) | |
| (int) expiration_count | 再生回数制限 例) 10 ← 10回まで再生可能 | |
| (int) expiration_playtime | 再生時間制限 例) 3600 ← 3600秒(1時間)まで再生可能 | |
| (int) expiration_playtime_type | 1の場合、play状態を起動時間として制限 | |
| (int) result | O | 0 (エラー), 1 (正常) 0の場合ダウンロードできません。 0の場合リトライしません。 |
| (string) message | 0 (エラー)の場合、message内容を追加するとエラーが起きた際に内容が表示されます。 | |
| (int) expiration_refresh_popup | 有効期限終了後、ポリシー更新表示の有無 0の場合表示しない (基本値) 1の場合有効期限終了後にユーザの確認を求めるメッセージを表示します。 | |
| (int) vmcheck | virtual machine 有無確認 (PC) 0: 確認しない, 1: 確認する (基本値) | |
| (int) check_abuse | DRM kind3 を常に確認 (0: 確認しない(基本値), 1: 確認する) | |
| (int) offline_bookmark.download | オフライン状態でブックマークダウンロード使用有無 | |
| (int) offline_bookmark.readonly | オフライン状態でブックマーク追加/削除の使用有無 (0: 使用する(基本値), 1: 使用しない) |
kind2
| 区分 | 必須 | Description |
|---|---|---|
| (int) kind | O | 2 |
| (string) media_content_key | O | コンテンツのメディアコンテンツキー |
| (int) content_delete | 0 (削除しない)、1 (ダウンロードしたファイルを削除) ダウンロードしたコンテンツを削除するオプションです。 | |
| (string) message | 0 (エラー)の場合、またはcontent_deleteが1(ダウンロードしたファイルを削除)の場合にmessageを追加すると状況によるメッセージが表示されます。(内容編集可能) | |
| (int) check_expiration_date | 使用しない(0), 更新チェックの有効期限終了日のunixtime stamp | |
| (int) result | O | 0 (エラー)、1 (正常) 0の場合、追加作業はありません。 0の場合、追加オプションは無視されます。 0の場合、再度リクエストされません。 |
kind3
| 区分 | 必須 | Description |
|---|---|---|
| (int) kind | O | 3 |
| (string) session_key | content_expire_reset リクエストした際にsession_keyを確認します。 | |
| (string) media_content_key | O | コンテンツのメディアコンテンツキー |
| (int) start_at | O | Requestに含まれたstart_at |
| (int) content_expired | 0 (再生可能), 1 (再生不可) ダウンロードしたコンテンツを強制的に有効期限切れ状態にします。 content_expire_resetオプションで復旧することができます。 * expiredコンテンツは 0(再生可能)でレスポンスしても再生できません。 * 1の場合、content_expire_reset オプションを無視します。 | |
| (int) content_delete | 0 (削除しない), 1 (削除する) ダウンロードしたコンテンツを削除するオプション content_expired オプションを無視してcontent_deleteが存在する場合削除する。 | |
| (int) content_expire_reset | 0 (何もしない), 1 (expiredされたコンテンツ権限をリセット) expiredされたコンテンツの権限をリセットするオプション | |
| (int) expiration_date | 有効期限終了日のunixtime stamp 最大値: 2029年 12月 31日 23時 59分 59秒 (1893455999) * content_expire_reset オプションが1の場合、再設定されます。 | |
| (int) expiration_count | 再生回数制限 例) 10 ← 10回再生可能 * content_expire_reset オプションが1の場合、再設定されます。 | |
| (int) expiration_playtime | 再生時間制限 例) 3600 ← 3600秒(1時間)まで再生可能 * content_expire_reset オプションが1の場合、再設定されます。 | |
| (int) result | O | 0 (エラー)、1 (正常) 0の場合、追加作業はありません。 0の場合、追加オプションは無視されます。 0の場合、再度リクエストされません。 |
| (string) message | 0 (비정상), content_expiredが 1(再生不可)・content_deleteが 1(コンテンツ削除)の場合にmessageを追加すると状況によるメッセージが表示されます。 | |
| (int) check_abuse | DRM kind3 常に確認 (0: 確認しない(基本値), 1: 確認する) | |
| (int) check_expiration_date | 更新チェックしない(0), 更新チェックの有効期限終了日のunixtime stamp * content_expire_reset オプションが 1の場合、再設定されます。 |
Response Example
{
“data” : [
{
"kind": 1,
"media_content_key": "XXX-MEDIA_CONTENT_KEY-XXX",
"expiration_date": 1402444800,
"expiration_count": 10,
"expiration_playtime": 60,
"result": 1
},
{
"kind": 2,
"media_content_key": "XXX-MEDIA_CONTENT_KEY-XXX",
"content_delete": 1,
"result": 1
},
{
"kind": 3,
"session_key" : "XXX-SESSION_KEY-XXX",
"media_content_key": "XXX-MEDIA_CONTENT_KEY-XXX",
"start_at": 140000000,
"content_expired": 1,
"content_delete": 1,
"content_expire_reset": 1,
"expiration_date": 1402444800,
"expiration_count": 10,
"expiration_playtime": 3600,
"result": 1
}
]
}
JWT 対応
DRMポリシーをJWTでレスポンスすることができます。JWT ウェブサイト(https://jwt.io) から公認されたライブラリーを使用して生成されたTokenに変更してレスポンスする機能が追加されました。
PlayerにJWT Tokenで返還する場合には指定されたヘッダにユーザーキーを含めて転送しなければなりません。
HTTP/1.1 200 OK Date: Fri, 14 Oct 2016 04:12:46 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive X-Kollus-UserKey: 0993d76eb424a72f2005b874ac49405d44a6c Content-Encoding: gzip
指定した(X-Kollus-UserKey) ヘッダに転送されたユーザーキーの情報が一致する場合のみ再生されます。
JWT Secret, JWT ユーザーキー
ユーザーキー(2)をheaderに追加することと共に、既存のJWT scpecにあるsecret keyにはセキュリティキー(1)を入力しなければなりません。Kollus CMSで以下のように関連情報を確認することができます。
설정 > 서비스 계정 설정
JWT payload
各 callbackの responseを JWT playloadに入れて転送します。
Sample
http://jwt.io から提供している様々な言語で具現されたライブラリーとサンプルを使用してください。JWT スペックと同じですが、レスポンスされるhttpヘッダに X-Kollus-UserKeyでユーザーキーを一緒に転送しなければなりません。
(サンプル) php
define('KOLLUS_KEY', '**ユーザーキー**'); // X-Kollus-UserKey
define('JWT_KEY', '**セキュリティキー**'); // JWT VERIFY SIGNATURE
function encode_jwt($payload, $key, $alg = 'HS256') {
$header = array('alg' => $alg, 'typ' => 'JWT' );
$unsignedToken = base64_encode(json_encode($header)).'.'.
base64_encode(json_encode($payload));
$signature = hash_hmac('sha256', $unsignedToken, $key, TRUE);
return $unsignedToken.'.'.base64_encode($signature);
}
$payload = array(
'data' => array(
array(
'kind' => 1,
'result' =>1,
'expiration_date' => time()+3600,
'expiration_playtime' => 30,
'vmcheck' => 1,
),
array(
'kind' => 2,
'result' =>1,
'expiration_date' => time()+3600,
'expiration_playtime' => 30,
'vmcheck' => 1,
),
array(
'kind' => 3,
'result' =>1,
'expiration_date' => time()+3600,
'expiration_playtime' => 30,
'vmcheck' => 1,
),
),
);
$result = encode_jwt($payload, JWT_KEY);
//header('Content-Type: application/jwt');
header('X-Kollus-UserKey: '.KOLLUS_KEY);
echo $result;
Code sample (v1.4 基準)
以下のように顧客のDBが構成されたことを想定したサンプルコードになります。
PHP sample
<?php
/**
* PHP Version : 5.4 above
*/
function get_jwt($payload, $key, $alg = 'HS256')
{
$header = array('alg' => $alg, 'typ' => 'JWT' );
$unsignedToken = base64_encode(json_encode($header)).'.'.base64_encode(json_encode($payload));
$signature = hash_hmac('sha256', $unsignedToken, $key, TRUE);
return $unsignedToken.'.'.base64_encode($signature);
}
function print_kollus_jwt($data)
{
define('KOLLUS_KEY', '**ユーザーキー**'); // X-Kollus-UserKey
define('JWT_KEY', '**セキュリティキー**'); // JWT VERIFY SIGNATURE
$payload = array( ‘data’ => $data );
$result = encode_jwt($payload, JWT_KEY);
//header('Content-Type: application/jwt');
header('X-Kollus-UserKey: '.KOLLUS_KEY);
echo $result;
}
/////////////////////////////////////////////////
include "./config.php";
// 注意 : kindが1の場合、自動でexpiration_dateが生成されるサンプルページとなります。
// 再生回数制限値
$_default_expiration_count = 3;
$_expired_duration = 60 * 60 * 24; // 1 day
// DB Connection
$_db_conn = mysqli_connect($_hostname, $_username, $_password);
if (!$_db_conn) {
die('Could not connect: ' . mysql_error());
}
$_db_selected = mysqli_select_db($_database, $_db_conn);
if (!$_db_selected) {
die ('Can\'t use database : ' . mysqli_error());
}
$_kind = isset($_POST['kind']) ? ((int) $_POST['kind']) : NULL;
$_media_content_key = isset($_POST['media_content_key']) ? $_POST['media_content_key'] : NULL;
$_client_user_id = isset($_POST['client_user_id']) ? $_POST['client_user_id'] : NULL;
$channel = NULL;
$_query = sprintf("SELECT * FROM `channels` WHERE `media_content_key` = '%s'",
mysqli_real_escape_string($_media_content_key, $_db_conn));
$_result = mysqli_query($_query, $_db_conn);
if ($_result) {
$channel = mysqli_fetch_array($_result, MYSQL_ASSOC);
if ($channel === FALSE) $channel = NULL;
} else {
die('Invalid query: ' . mysqli_error());
}
$user = NULL;
$_query = sprintf("SELECT * FROM `users` WHERE `client_user_id` = '%s'",
mysqli_real_escape_string($_client_user_id, $_db_conn));
$_result = mysqli_query($_query, $_db_conn);
if ($_result) {
$user = mysqli_fetch_array($_result, MYSQL_ASSOC);
if ($user === FALSE) $user = NULL;
} else {
die('Invalid query: ' . mysqli_error());
}
$channel_user = NULL;
if (!is_null($_media_content_key) && !is_null($_client_user_id)) {
$_query = sprintf("SELECT * FROM `channel_users` WHERE `user_id` = '%s', `channel_id` = '%s'", $user['id'], $channel['id']);
$_result = mysqli_query($_query, $_db_conn);
if ($_result) {
$channel_user = mysqli_fetch_array($_result, MYSQL_ASSOC);
if ($channel_user === FALSE) $channel_user = NULL;
} else {
die('Invalid query: ' . mysqli_error());
}
}
$_jwt_result = array('result' => 0);
switch($_kind) {
case 1:
if (is_null($channel_user)){
$_expiration_date = time() + $_expired_duration;
$_expiration_count = $_default_expiration_count;
$_query = sprintf("INSERT INTO `channel_users`(`user_id`, `channel_id`, `expiration_date`
,`expiration_count` , `created_at`, `updated_at`) VALUES('%s', '%s', '%s', '%s', UNIX_TIMESTAMP(),
UNIX_TIMESTAMP())", $user['id'], $channel['id'], $_expiration_date, $_expiration_count);
$_result = mysqli_query($_query, $_db_conn);
if (!$_result) {
die('Invalid query: ' . mysqli_error());
}
} else {
$_expiration_date = $channel_user['expiration_date'];
$_expiration_count = $channel_user['$expiration_count'];
}
$_jwt_result['expiration_date'] = (int) $_expiration_date;
$_jwt_result['expiration_count'] = (int) $_expiration_count;
break;
case 2:
if (!is_null($channel_user)){
$_download_times = ++((int) $channel_user['download_times']);
$_query = sprintf("UPDATE `channel_users` SET `download_times` = '%s', `updated_at` =
UNIX_TIMESTAMP() WHERE id = %s", $_download_times, $channel_user['id']);
$_result = mysqli_query($_query, $_db_conn);
if (!$_result) {
die('Invalid query: ' . mysql_error());
}
$_jwt_result['result'] = 1;
}
break;
case 3:
if (!is_null($channel_user) && $channel_user['is_expired']) {
$_jwt_result['content_expired'] = 1;
}
break;
}
// DB Close
mysqli_close($_db_conn);
// 結果の data arrayをJWTに変換して出力
print_kollus_jwt($_jwt_result);
サンプルコード
Copyright © CATENOID, lnc. All Rights Reserved.
E-mail. jp_sales@catenoid.net | Tel. 03-4405-8462