Java (Spring Boot) Implementation Example
Notice
This document is a machine-translated draft and is currently undergoing review. Some content may be inaccurate or differ from the original Korean version. For the most precise information, refer to the Korean documentation.
JWT encoder implementation
This is a component that generates headers and Payload according to Kollus's response specifications and signs them with the HS256 algorithm.
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 download callback controller implementation
This is an example that receives requests (items) from the Kollus server and performs business logic by type.
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);
}
}
Example code repository link
Integration example code in various languages is available in the GitHub repository below.