Skip to main content

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

플레이 콜백 컨트롤러 구현

Kollus 서버로부터의 POST 요청을 수신하여 플레이 콜백 유형별로 비즈니스 로직을 처리하는 예제입니다.

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 (kind 1: 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);
}
}

예제 코드 저장소 링크

다양한 언어의 연동 예제 코드는 아래 GitHub 저장소에서 확인할 수 있습니다.