Java (Spring Boot) 구현 예제
JWT 인코더 구현
Kollus의 응답 규격에 맞춰 헤더와 Payload를 생성하고 HS256 알고리즘으로 서명하는 컴포넌트입니다.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Custom JWT Encoder for Kollus
*/
public class KollusJwtEncoder {
private final ObjectMapper objectMapper = new ObjectMapper();
public String encode(Map<String, Object> payload, String key) throws Exception {
// 1. Construct the header
Map<String, String> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
String headerJson = objectMapper.writeValueAsString(header);
String payloadJson = objectMapper.writeValueAsString(payload);
// 2. Base64Url encode (without padding)
String encodedHeader = Base64.getUrlEncoder().withoutPadding()
.encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
String encodedPayload = Base64.getUrlEncoder().withoutPadding()
.encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
String unsignedToken = encodedHeader + "." + encodedPayload;
// 3. Generate HS256 signature
Mac hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmac.init(secretKey);
byte[] signatureBytes = hmac.doFinal(unsignedToken.getBytes(StandardCharsets.UTF_8));
String signature = Base64.getUrlEncoder().withoutPadding()
.encodeToString(signatureBytes);
return unsignedToken + "." + signature;
}
}
DRM 다운로드 콜백 컨트롤러 구현
Kollus 서버로부터의 요청(items)을 수신하여 유형별 비즈니스 로직을 수행하는 예제입니다.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Instant;
import java.util.*;
@SpringBootApplication
@RestController
public class KollusDrmCallbackApplication {
private static final String KOLLUS_KEY = "{CUSTOM_KEY}";
private static final String JWT_KEY = "{SECURITY_KEY}";
private final ObjectMapper objectMapper = new ObjectMapper();
private final KollusJwtEncoder jwtEncoder = new KollusJwtEncoder();
/**
* Endpoint to receive DRM callbacks
*/
@PostMapping("/drm/callback")
public ResponseEntity<String> drmCallback(@RequestParam("items") String items) {
try {
// Parse the 'items' parameter
List<Map<String, Object>> itemsList = objectMapper.readValue(items, List.class);
List<Map<String, Object>> resultData = new ArrayList<>();
// Process each item in the list
for (Map<String, Object> item : itemsList) {
Integer kind = (Integer) item.get("kind");
String mediaContentKey = (String) item.get("media_content_key");
String clientUserId = (String) item.get("client_user_id");
// Logic branching based on callback type (kind)
Map<String, Object> result = switch (kind) {
case 1 -> processKind1(mediaContentKey, clientUserId);
case 2 -> processKind2(mediaContentKey, clientUserId);
case 3 -> processKind3(mediaContentKey, clientUserId, item);
default -> createErrorResponse(kind, mediaContentKey, "Invalid kind");
};
resultData.add(result);
}
// Generate the JWT response
Map<String, Object> payload = new HashMap<>();
payload.put("data", resultData);
String jwtToken = jwtEncoder.encode(payload, JWT_KEY);
// Configure response headers (Required: X-Kollus-UserKey)
HttpHeaders headers = new HttpHeaders();
headers.set("X-Kollus-UserKey", KOLLUS_KEY);
headers.setContentType(MediaType.TEXT_PLAIN);
return ResponseEntity.ok().headers(headers).body(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.internalServerError().body("Error processing callback");
}
}
/**
* kind1 (Download approval)
*/
private Map<String, Object> processKind1(String mediaContentKey, String clientUserId) {
// TODO: Verify user and content permissions in the database
Map<String, Object> response = new HashMap<>();
response.put("kind", 1);
response.put("media_content_key", mediaContentKey);
response.put("result", 1);
response.put("expiration_date", Instant.now().getEpochSecond() + 86400); // 1 day
response.put("expiration_count", 3);
response.put("expiration_playtime", 1800); // 30 minutes
response.put("vmcheck", 1);
response.put("check_abuse", 0);
return response;
}
/**
* kind2 (Download completion notification)
*/
private Map<String, Object> processKind2(String mediaContentKey, String clientUserId) {
// TODO: Record download completion status in the database
Map<String, Object> response = new HashMap<>();
response.put("kind", 2);
response.put("media_content_key", mediaContentKey);
response.put("result", 1);
response.put("content_delete", 0);
response.put("check_expiration_date", 0);
return response;
}
/**
* kind3 (Offline playback authorization)
*/
private Map<String, Object> processKind3(String mediaContentKey, String clientUserId,
Map<String, Object> item) {
// TODO: Verify playback permissions and expiration status in the database
Map<String, Object> response = new HashMap<>();
response.put("kind", 3);
response.put("media_content_key", mediaContentKey);
response.put("start_at", item.get("start_at"));
response.put("result", 1);
response.put("content_expired", 0);
response.put("content_delete", 0);
response.put("content_expire_reset", 0);
response.put("check_abuse", 0);
response.put("check_expiration_date", 0);
if (item.containsKey("session_key")) {
response.put("session_key", item.get("session_key"));
}
return response;
}
/**
* Create error response
*/
private Map<String, Object> createErrorResponse(Integer kind, String mediaContentKey, String message) {
Map<String, Object> response = new HashMap<>();
response.put("kind", kind);
response.put("media_content_key", mediaContentKey);
response.put("result", 0);
response.put("message", message);
return response;
}
public static void main(String[] args) {
SpringApplication.run(KollusDrmCallbackApplication.class, args);
}
}
예제 코드 저장소 링크
다양한 언어의 연동 예제 코드는 아래 GitHub 저장소에서 확인할 수 있습니다.