[BlerOn] 동영상 재생 Presigned URL
- BlerOn 학습창 동영상 Presigned URL 정리
BlerOn 학습창 동영상 Presigned URL 정리
BlerOn 학습창에서 동영상을 재생할 때, 클라이언트는 DB에 저장된 원본 URL을 직접 사용하지 않는다. Backend API가 응답을 생성하는 시점에 LG CDN(CloudN) Enguard 규격에 맞는 Presigned URL을 만들어 내려주고, Frontend HLS Player가 이 URL로 CDN에 스트리밍을 요청한다.
이 글은 업로드 → DB 저장 → API 응답 → Frontend 재생까지의 전체 흐름과, CdnTokenGenerator의 URL 서명 알고리즘·만료 시간 계산·보안 한계를 한 곳에 정리한다. MultiDRM 미적용 배경과 향후 도입 시 변경 포인트도 함께 다룬다.
0. 개요 한눈에 보기
먼저 Presigned URL이 어떻게 만들어지고 전달되는지부터 짚고 간다.
┌─────────────┐ API 요청 ┌─────────────┐ Presigned URL ┌─────────────┐
│ 학습창 FE │ ───────────────▶ │ BLER Backend │ ──────────────────▶ │ HLS Player │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ LG CDN │
│ (토큰 검증) │
└─────────────┘
| 파라미터 | 설명 |
|---|---|
e |
만료 시각 (Unix timestamp, UTC) |
h |
secret-key + URL + e 로 생성한 MD5 해시 |
playtype=preview |
미리보기 전용 (Preview API만) |
vodstart / vodend |
미리보기 재생 구간 (초) |
| 단계 | 저장/전달 데이터 | 토큰 포함 |
|---|---|---|
| CDN 업로드 후 | {CDN_STREAMING_URL}/{bucket}/{yyyy}/{MM}/{uuid}/playlist.m3u8 |
❌ |
DB (unit_video.video_path) |
위와 동일 | ❌ |
API 응답 (unitVideo.videoUrl) |
...?e={timestamp}&h={hash} |
✅ |
| HLS Player 요청 | API에서 받은 URL 그대로 사용 | ✅ |
DB에는 토큰 없는 원본 URL만 저장하고, API 응답 시점에만 Presigned URL을 부여한다.
1. 보안 정책
1-1. MultiDRM 미적용
| 항목 | 내용 |
|---|---|
| 검토 항목 | Widevine, FairPlay, PlayReady 등 MultiDRM |
| 미적용 사유 | 개발 리소스 부족, 라이선스·운영 비용 이슈 |
| 적용 대안 | LG CDN Enguard Module + Hash Presigned URL |
MultiDRM은 영상 파일 자체를 암호화해 키 교환으로 보호한다.
현재는 URL 서명 + 만료 시간으로 무단 접근을 최소한 방어한다.
1-2. 현재 보안 전략 요약
[적용 O] URL Hash 서명 (secret-key + MD5)
[적용 O] 만료 시간 (영상 길이 기반 동적 계산)
[적용 O] Preview 구간 제한 (LG CDN vodend)
[적용 O] DB에는 토큰 없는 원본 URL만 저장
[적용 O] 수강·학습기간 검증 (정식 학습)
[미적용] MultiDRM / 콘텐츠 암호화
2. 전체 흐름
2-1. End-to-End 시퀀스
sequenceDiagram
autonumber
participant Admin as 관리자
participant BE as BLER Backend
participant LG as LG CDN
participant DB as MariaDB
participant FE as 학습창
participant Player as HLS Player
Admin->>LG: MP4 업로드
LG->>LG: HLS 변환 (playlist.m3u8 + .ts)
LG->>BE: 업로드 콜백
BE->>DB: 원본 URL 저장 (토큰 없음)
FE->>BE: GET /user/learning/.../unit/{unitId}
BE->>DB: 유닛·동영상 조회
BE->>BE: CdnTokenGenerator — Presigned URL 생성
BE->>FE: videoUrl (e=, h= 포함)
FE->>Player: signed URL 전달
Player->>LG: playlist.m3u8 / .ts 요청
LG->>LG: e, h 검증
LG-->>Player: 스트리밍 응답
3. 업로드 & DB 저장
3-1. LG CDN 업로드
| 항목 | 설명 |
|---|---|
| 업로드 방식 | LG CDN REST API (multipart 업로드) |
| 구현 클래스 | LGCdnStorageClient |
| 변환 결과 | playlist.m3u8 + .ts 세그먼트 (HLS) |
3-2. CDN 콜백 처리
POST /api/v1/cdn/callbacks
처리 내용 (CdnFileAppServiceImpl.registerUploadedFile)
- 스트리밍 URL 조립 —
{streaming-url}/{subPath}/{uuid}/playlist.m3u8 - Presigned URL 생성 (FFmpeg duration 추출용)
- FFmpeg로 재생 시간(duration) 추출
cdn_file테이블에 메타 저장
3-3. 유닛-동영상 매핑
유닛 등록 시 UnitVideoAppService가 CDN 파일과 연결한다.
CdnFileDomain.fileConvertPath → unit_video.video_path
.mp4 경로 → /playlist.m3u8 로 변환 (changeFilePath)
DB 저장 예시 (토큰 없음)
{CDN_STREAMING_URL}/{bucket}/{yyyy}/{MM}/{uuid}/playlist.m3u8
4. 학습창 API & URL 생성
4-1. 관련 API
| API | 용도 | Presigned 적용 |
|---|---|---|
GET /user/learning/class/{classId}/round/{round}/section/{sectionId}/unit/{unitId} |
정식 학습 | ✅ Enguard |
GET /user/learning/class/{classId}/round/{round}/preview |
미리보기 | ✅ LG Preview |
4-2. 정식 학습 호출 체인
LearningUnitUserController.getUnit()
│
▼
LearningUnitAppQueryServiceImpl.getLearnUnit()
├── confirmEnrollmentStatus() ← 수강·학습기간 검증
└── LearningUnitAppComponent.getUnitByDivision(VIDEO)
└── UnitVideoRepositoryImpl.findByUnitId()
│
▼
LearningUnitResponse.from()
└── LearningVideoResponse
└── LearnUnitVideo.from() ★ Presigned URL 생성
4-3. 응답 DTO — LearnUnitVideo.from()
| 필드 | 생성 방식 | 만료 기준 |
|---|---|---|
videoUrl |
CloudN Enguard | (영상길이 + 300초) × 2 + expire-time |
introUrl |
Enguard (Preview 모드) | preview-expire-time |
outroUrl |
Enguard (Preview 모드) | preview-expire-time |
300초 버퍼 (
VIDEO_DURATION_BUFFER)
재생 중 토큰이 만료되어 끊기는 것을 방지하기 위한 여유 시간이다.
4-4. 미리보기 호출 체인
LearningUnitUserController.getPreviewUnits()
│
▼
LearningUnitAppQueryServiceImpl.getPreviewUnits()
└── unitVideoRepository.findByUnitIdIn() ← 원본 URL
│
▼
LearningPreviewCurriculumResponse.from()
└── CdnTokenGenerator.generateCdnUrl(
url, duration, isPreview=true, previewDuration)
Preview 전용 파라미터
| 파라미터 | 값 | 설명 |
|---|---|---|
playtype |
preview |
미리보기 모드 |
vodstart |
0 |
시작 지점 (초) |
vodend |
previewDuration + 30 |
종료 지점 + 버퍼 |
chunkduration |
10 |
HLS chunk 단위 (초) |
5. CdnTokenGenerator 상세
경로: application/service/utils/cdn/CdnTokenGenerator.java
5-1. 설정값 (config/cdn/cdn-{profile}.yml)
app:
cdn:
secret-key: ${CDN_SECRET_KEY} # 해시 생성 비밀키 (환경변수 주입)
expire-time: 600 # 정식 재생 추가 버퍼 (초)
preview-expire-time: 3600 # Preview / Intro / Outro (초)
ftp:
streaming-url: ${CDN_STREAMING_URL}
vod:
intro: ${VOD_INTRO_URL}
outro: ${VOD_OUTRO_URL}
실제 URL·비밀키 값은 환경별 설정 파일 또는 환경변수로 관리한다.
본 글에는 민감정보를 포함하지 않는다.
5-2. 알고리즘 A — CloudN Enguard (정식 학습)
① expire = UTC현재시각 + (videoDuration × 2) + expire-time
② hashKeyUrl = 원본 URL에서 /playlist.m3u8 제거
③ raw = secret-key + hashKeyUrl + "?e=" + expire
④ h = MD5(raw)
⑤ 결과 URL = 원본URL + "?e={expire}&h={h}"
/playlist.m3u8 제거 이유
HLS 재생 중 playlist ↔ segment 경로가 바뀌어도,
CDN이 디렉터리 단위로 토큰을 검증할 수 있도록 하기 위함이다.
5-3. 알고리즘 B — LG CDN Preview (미리보기)
hashInput = secret-key + hashKeyUrl
+ "?e=" + expire
+ "&playtype=preview"
+ "&chunkduration=10"
+ "&vodstart=0"
+ "&vodend=" + (previewDuration + 30)
h = MD5(hashInput)
5-4. URL 예시 (구조만 표기)
정식 학습
{CDN_STREAMING_URL}/{bucket}/{yyyy}/{MM}/{uuid}/playlist.m3u8
?e={unix_timestamp}
&h={md5_hash}
미리보기 (previewDuration = 60초 기준)
{CDN_STREAMING_URL}/{bucket}/{yyyy}/{MM}/{uuid}/playlist.m3u8
?e={unix_timestamp}
&playtype=preview
&chunkduration=10
&vodstart=0
&vodend=90
&h={md5_hash}
6. Frontend 재생
① 학습창 → GET .../unit/{unitId}
② 응답에서 unitVideo.videoUrl 수신 (Presigned URL)
③ HLS Player (hls.js / Video.js 등)에 URL 설정
④ Player → CDN playlist.m3u8 요청
⑤ CDN → e, h 검증
├── 유효 → .ts 세그먼트 스트리밍
└── 무효 → 403 거부 (만료 or 위조)
Intro / Outro 도 동일한 Presigned URL 방식으로 재생한다.
7. 만료 시간 계산
7-1. 정식 학습 (videoUrl)
expireSeconds = (max(duration + 300, 0) × 2) + expire-time
expireTimestamp = UTC현재시각 + expireSeconds
계산 예시 — 1시간(3600초) 영상, expire-time = 600초
| 항목 | 값 |
|---|---|
| duration + buffer | 3600 + 300 = 3900초 |
| × 2 | 7800초 |
| + expire-time | 7800 + 600 = 8400초 |
| 유효 시간 | 약 2시간 20분 |
7-2. Preview / Intro / Outro
expireSeconds = preview-expire-time (기본 3600초)
또는 previewDuration + preview-expire-time
8. 보안 수준 & 한계
8-1. 적용된 보호
| 수단 | 상태 | 효과 |
|---|---|---|
| URL Hash 서명 (Enguard) | ✅ | secret-key 없이 URL 위조 불가 |
URL 만료 (e) |
✅ | 시간 경과 후 접근 차단 |
| Preview 구간 제한 | ✅ | 미리보기 길이 CDN 레벨 제한 |
| DB 원본 URL 비노출 | ✅ | API 응답 시점에만 토큰 부여 |
| 수강·기간 검증 | ✅ | 정식 학습 API 접근 제어 |
8-2. 보안 한계 (인지 필요)
| 한계 | 설명 |
|---|---|
| URL 캡처 | DevTools로 Presigned URL 캡처 → 만료 전 재생 가능 |
| 화면 녹화 | URL 서명만으로는 녹화·캡처 방지 불가 |
| DRM 미적용 | 파일 다운로드·유출에 대한 강력한 방어 없음 |
현재 수준: "링크 공유·무단 접근 최소 방어"
MultiDRM 대비 콘텐츠 유출 방어는 약함
9. 코드 위치
| 역할 | 파일 경로 |
|---|---|
| Presigned URL 생성 | application/service/utils/cdn/CdnTokenGenerator.java |
| MD5 유틸 | application/service/utils/cdn/MD5Util.java |
| 학습 응답 DTO | presentation/dto/learning/unit/learningunit/LearnUnitVideo.java |
| Preview 응답 DTO | presentation/dto/learning/unit/response/LearningPreviewCurriculumResponse.java |
| 유닛 Video Repository | infrastructure/persistence/repository/unit/video/UnitVideoRepositoryImpl.java |
| CDN 업로드 클라이언트 | infrastructure/client/LGCdnStorageClient.java |
| CDN 설정 | resources/config/cdn/cdn-{profile}.yml |
| 학습창 API | presentation/controller/learning/unit/LearningUnitUserController.java |
| 버퍼 상수 | application/service/utils/Constant.java (VIDEO_DURATION_BUFFER = 300) |
10. 향후 MultiDRM 도입 시
현재 구조는 DB 원본 URL 저장 + 응답 시점 서명으로 분리되어 있어,
Presigned URL 레이어 교체·확장이 비교적 수월하다.
| 변경 포인트 | 내용 |
|---|---|
| CDN 업로드 | DRM 패키징 파이프라인 추가 |
| Backend | CdnTokenGenerator → DRM 라이선스 서버 연동 |
| Frontend | EME(Encrypted Media Extensions) 지원 Player 필요 |
| API 응답 | videoUrl → manifest URL + license URL 분리 검토 |
11. 용어 정리
| 용어 | 설명 |
|---|---|
| HLS | HTTP Live Streaming. playlist.m3u8 + .ts 세그먼트로 스트리밍 |
| Enguard Module | CloudN CDN URL 서명·검증 모듈 |
| Presigned URL | 만료 시간 + 해시가 붙은, 일시적으로만 유효한 URL |
| MultiDRM | Widevine·FairPlay 등 복수 DRM을 지원하는 콘텐츠 보호 솔루션 |
12. 마치며
BlerOn 학습창 동영상 재생은 DB 원본 URL 저장 → API 응답 시 Presigned URL 생성 → HLS Player CDN 요청 구조로 동작한다. MultiDRM 대신 LG CDN Enguard Hash 서명과 만료 시간으로 무단 접근을 최소한 방어하고, Preview API에서는 vodstart/vodend로 재생 구간까지 제한한다.
핵심은 다음 세 가지다.
- DB에는 토큰 없는 원본 URL만 저장 — API 응답 시점에만 서명
CdnTokenGenerator— Enguard(정식) / Preview(미리보기) 알고리즘 분기- 만료 시간 —
(영상길이 + 300초) × 2 + expire-time으로 재생 중 끊김 방지
향후 MultiDRM 도입 시에도 DB 저장·API 응답 레이어 분리 구조 덕분에 CdnTokenGenerator 교체와 DRM 라이선스 연동으로 확장할 수 있다.