Skip to main content

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 using 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;
}
}

playback callback controller implementation

This is an example that receives POST requests from the Kollus server and processes business logic for each playback callback 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 java.time.Instant;
import java.util.*;

@SpringBootApplication
@RestController
public class KollusPlayCallbackApplication {

private static final String KOLLUS_KEY = "{CUSTOM_KEY}";
private static final String JWT_KEY = "{SECURITY_KEY}";

private final KollusJwtEncoder jwtEncoder = new KollusJwtEncoder();

/**
* Endpoint to receive play callbacks
*/
@PostMapping("/play/callback")
public ResponseEntity<String> playCallback(
@RequestParam("kind") Integer kind,
@RequestParam("client_user_id") String clientUserId,
@RequestParam("player_id") String playerId,
@RequestParam(value = "device_name", required = false) String deviceName,
@RequestParam("media_content_key") String mediaContentKey,
@RequestParam(value = "hardware_id", required = false) String hardwareId,
@RequestParam(value = "uservalues", required = false) String uservalues,
@RequestParam(value = "localtime", required = false) Long localtime) {

try {
System.out.println("Play Callback - kind: " + kind +
", user: " + clientUserId +
", media: " + mediaContentKey);

Map<String, Object> responseData;

// Logic branching by callback type (kind1: Expiration settings, kind 3: Final approval)
if (kind == 1) {
responseData = processKind1(clientUserId, mediaContentKey);
} else if (kind == 3) {
responseData = processKind3(clientUserId, playerId, mediaContentKey);
} else {
responseData = createErrorResponse("Invalid kind");
}

// Sign the response data
String jwtToken = jwtEncoder.encode(responseData, 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 (Playback expiration settings)
*/
private Map<String, Object> processKind1(String clientUserId, String mediaContentKey) {
// TODO: Verify user and content permissions in the database

Map<String, Object> data = new HashMap<>();
data.put("result", 1);
data.put("expiration_date", Instant.now().getEpochSecond() + 86400); // 1 day
data.put("vmcheck", 1);
data.put("disable_tvout", 0);
data.put("expiration_playtime", 1800); // 30 minutes
data.put("cpcheck", 1);

Map<String, Object> response = new HashMap<>();
response.put("data", data);
response.put("exp", Instant.now().getEpochSecond() + 3600); // JWT valid for 1 hour

return response;
}

/**
* kind3 (Final playback approval)
*/
private Map<String, Object> processKind3(String clientUserId, String playerId,
String mediaContentKey) {
// TODO: Verify playback permissions and expiration status in the database

Map<String, Object> data = new HashMap<>();
data.put("result", 1);
data.put("content_expired", 0); // 0: Playable, 1: Playback blocked

Map<String, Object> response = new HashMap<>();
response.put("data", data);
response.put("exp", Instant.now().getEpochSecond() + 3600);

return response;
}

/**
* Create error response
*/
private Map<String, Object> createErrorResponse(String message) {
Map<String, Object> data = new HashMap<>();
data.put("result", 0);
data.put("message", message);

Map<String, Object> response = new HashMap<>();
response.put("data", data);

return response;
}

public static void main(String[] args) {
SpringApplication.run(KollusPlayCallbackApplication.class, args);
}
}

Integration example code in various languages is available in the GitHub repository below.