개요
Kollus 모바일 다운로드 DRM을 이용해 다운로드한 컨텐츠의 ‘재생 재한 횟수’, ‘컨텐츠 만료 기간’, ‘컨텐츠 재생시간'을 설정하여 컨텐츠의 재생을 제어하는 기능을 설명하는 문서입니다. 서비스 모델에 따라 중복 제한으로 설정되어 사용될 수 있습니다.
Expire option
설정 항목의 data-type이나 값이 범위를 벗어나는 경우 컨텐츠 이용에 문제가 발생할 수 있으며, 잘못된 설정 값에 대한 사용 제한을 회수하는 방법은 존재하지 않습니다.
Expire count : 재생 제한 횟수
- data-type : integer
- range : 0 ~ 1000
- 0 : unlimited (무제한)
Expire date : 컨텐츠 만료 시간
data-type : integer, unixtime stamp
컨텐츠 재생이 만료될 시간
컨텐츠 재생이 만료될 시간
ex) 2014. 3. 3. 5시 45분 30초 GMT → 1393825531
0 : unlimited (무제한)
최대값 : 2029년 12월 31일 23시 59분 59초 (1893455999)
Expire playtime : 재생시간 제한 (배속의 경우 배속이 적용된 시간 적용)
- data-type : integer
- range : 0, 60 ~ 604800 (단위:초, 최소: 60초, 최대:1주일)
- 0 : unlimited (무제한)
DRM callback
DRM 정책을 적용하기 위해서는 채널에 DRM 콜백 URL(DRM callback url)을 설정해야 합니다.
채널에 DRM 콜백 URL을 설정하면 컨텐츠를 다운로드하는 시점에 채널에 설정된 DRM 콜백 URL을 호출하여 반환된 값을 DRM 정책으로 사용하게 됩니다. 이때 다운로드되는 DRM 정책 정보는 JWT Encode 되어 Http Body에 반환 되어야 합니다.
주의:
- DRM 콜백 URL이 응답하지 않으면 다운로드가 되지 않습니다.
- 알고리즘은 HS256만 지원을 하며 Http 헤더에 지정된 헤더(X-KOLLUS-USERKEY)의 값으로 “사용자키"를 함께 전송해야 합니다.
Callback flow
채널에 DRM 콜백 URL을 설정합니다.
- ex> http://www.foo.com/auth.php 가 고객사 인증 DRM 서버인 경우
JSON 데이터를 생성 후 JWT로 인코딩 한다.
Kollus mobile player가 http://www.foo.com/auth.php 에 다음의 정보를 POST 전송 합니다.
- session_key : 플레이어에서 생성한 요청 확인용 세션키
- kind3의 content_expire_reset 경우 요청한 session_key를 확인합니다.
- Callback v2가 적용되는 v1.6에서 적용됩니다.
- kind : 1 - 3
- client_user_id : 고객사 회원 아이디
- Media token 생성시 포함된 아이디입니다.
- player_id : 고객사 회원이 가지고 있는 단말 아이디
- device_name : 고객사 회원이 가지고 있는 단말명
- 안드로이드, 아이폰에 따라 단말명이 다르게 전달됩니다.
- 아이폰의 경우 사전에 Apple에서 정의한 문자열로 전달되며 안드로이드의 경우 디바이스명, 모델명이 함께 전달됩니다. 안드로이드의 경우 해당 정보가 없을 경우 NULL을 전달 합니다.
- media_content_key : 현재 다운로드하려는 컨텐츠 key 입니다.
- 채널에 등록된 컨텐츠에 부여된 고유 키입니다.
- 업로드된 컨텐츠를 여러채널에 등록하면 media_content_key는 모두 다르게 생성됩니다.
- session_key : 플레이어에서 생성한 요청 확인용 세션키
고객사 DRM 인증서버는 전달받은 위 정보를 바탕으로 다음의 json 포멧의 data를 JWT의 payload 에 추가하여 Encoding 하고 헤더에 지정된 “시용자키(X-KOLLUS-USERKEY)"를 함께 전송합니다.
(고객사가 인증 정보를 플레이어로 전달할 때 모든 데이터의 자료형은 반드시 기술된대로 integer 형이여야만 합니다.)
device_name 추가 설명
Andoid
안드로이드 어플 개발시 사용되는 Build.DEVICE, Build.MODEL을 /(슬래쉬)로 구분한 문자열로 만들어 사용합니다.
- Build.DEVICE+”/”+Build.MODEL
- 단말기의 특성에 따라 해당 정보는 NULL로 표시될 수 있습니다.
iOS
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 | 고객사 사용자가 가지고 있는 단말의 아이디 |
hardware_id | 단말의 hardware 아이디(PC, 입력값이 있으면) |
device_name | 고객사 사용자가 가지고 있는 단말의 모델명 |
media_content_key | Kollus 컨텐츠 unique key |
uservalues | JSON format (VideoGateway 호출시 사용된 uservalue0~9) |
localtime | 단말의 UTC Time |
kind3
구분 | Description |
---|---|
kind | 3 |
session_key | content_expire_reset 요청시 동일한 session_key를 확인합니다. |
client_user_id | 고객사 사용자 ID, media_token 생성시 사용된 client_user_id와 동일합니다. |
player_id | 고객사 사용자가 가지고 있는 단말의 아이디 |
hardware_id | 단말의 hardware 아이디(PC, 입력값이 있으면) |
device_name | 고객사 사용자가 가지고 있는 단말의 모델명 |
media_content_key | Kollus 컨텐츠 unique 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 Time |
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 | Kollus 컨텐츠 Unique Key |
(int) expiration_date | 만료될 시간의 unixtime stamp 최대값 : 2029년 12월 31일 23시 59분 59초 (1893455999) | |
(int) expiration_count | 재생 제한 횟수, 예) 10 ← 10번 재생 가능 | |
(int) expiration_playtime | 재생 시간 제한, 예) 60 ← 60초 재생 가능 | |
(int) expiration_playtime_type | 1이면 play 상태일 때 구동 시간으로 제한 | |
(int) result | O | 0 (비정상), 1 (정상) 0 인 경우 다운로드 되지 않습니다. 0 인 경우 재 요청되지 않습니다. |
(string) message | 0 (비정상)의 경우 message를 추가하면 상황에 따른 메시지가 표시됩니다. | |
(int) expiration_refresh_popup | 만료후 갱신 여부 팝업 표시 0 인 경우 표시하지 않음 (기본값) 1 인 경우 DRM 만료후 갱신이 필요할때 사용자 확인을 받는 팝업을 표시합니다. | |
(int) vmcheck | virtual machine 체크 여부 판단, PC용 0 : check 안 함, 1 : check 함(기본값) | |
(int) check_abuse | DRM kind3 항상 물어보기(0 : 안함(기본값), 1: 체크) | |
(int) offline_bookmark.download | 오프라인일 때 기존 북마크 다운로드 사용 여부 컨텐츠 다운로드 시점의 데이터만 다운로드되며, 이후 서버와 싱크처리 되지 않습니다. 0 : 안함(기본값) 1 : 인덱스만 다운로드 | |
(int) offline_bookmark.readonly | 오프라인일 때 추가/삭제 사용 여부(0 : 사용(기본값), 1: 안함) |
kind2
구분 | 필수 | Description |
---|---|---|
(int) kind | O | 2 |
(string) media_content_key | O | Kollus 컨텐츠 Unique Key |
(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 | Kollus 컨텐츠 Unique Key |
(int) start_at | O | Request에 포함된 start_at |
(int) content_expired | 0 (재생가능), 1 (재생 제한) 다운로드 컨텐츠를 강제로 expire 시킵니다. 필요시 content_expire_reset 옵션으로 다시 복구 할 수 있습니다. * expired 컨텐츠는 0(재생가능)으로 응답해도 재생되지 않습니다. * 1인 경우 content_expire_reset 무시됩니다. | |
(int) content_delete | 0 (삭제하지 않음), 1 (다운로드 받은 파일 삭제) 다운로드된 컨텐츠를 요청에 의해 삭제하는 옵션입니다. content_expired 옵션과 상관 관계없이 해당 옵션이 존재하는 경우 삭제합니다. | |
(int) content_expire_reset | 0 (추가 액션 없음), 1 (expired된 콘텐츠 권한 Reset) expired된 콘텐츠 권한을 Reset 하는 옵션입니다. | |
(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 ← 1시간(3600초) 재생 가능 * 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으로 변경하여 반환하도록 추가 되었습니다.
플레이어에 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 보안 키 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 보안 키 $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);