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 저장소에서 확인할 수 있습니다.