5.4 KiB
5.4 KiB
단계별 상세
1. 사용자 인증 / 공유 링크 접근
요청의 진입점으로, 두 가지 접근 경로를 처리한다.
- 인증 사용자:
Authorization헤더의 JWT Bearer 토큰을 파싱 및 검증 - 공유 링크: URL 쿼리 파라미터의
share_token을 파싱
두 경로 모두 이후 단계에서 동일한 인터페이스로 처리될 수 있도록 내부적으로 정규화한다.
2. 파일 권한 / 공유 상태 검증
DB를 조회하여 요청자가 해당 파일에 접근할 수 있는지 확인한다.
- 인증 사용자: 파일의 소유자 또는 공유 대상 여부 확인
- 공유 링크: 링크의 활성화 여부, 만료 여부, 비밀번호 보호 여부 확인
- 권한이 없으면 즉시
403 Forbidden반환 (이후 단계로 진행하지 않음)
Risk
권한이 없는 경우 403, 파일이 없는 경우 404를 분리 반환한다. 이 차이를 이용하면 공격자가 파일 ID를 열거하여 존재하는 파일 목록을 추론할 수 있다.
3. 다운로드 토큰 발급
실제 스트리밍 요청에 사용할 단명(short-lived) 토큰을 생성한다.
- TTL은 약 60초 권장 (재사용·탈취 위험 최소화)
- 토큰에는 파일 ID, 요청자 식별자, 만료 시각을 포함
- HMAC 서명 또는 JWT로 위변조 방지
# Redis 기반 원자적 무효화
result = REDIS.SET(f"dl_token:{token}", "used", NX=True, EX=60)
# NX: key가 없을 때만 SET → 원자적 보장
# result가 None이면 이미 사용된 토큰 → 401
API 서버와 스트리밍 서버를 분리 운영할 때 특히 유용하다.
클라이언트는 이 토큰을 받아 스트리밍 엔드포인트에 직접 요청한다.
4. 다운로드 토큰으로 파일 스트림 요청
클라이언트가 발급받은 토큰으로 스트리밍 엔드포인트에 요청한다.
GET /files/stream?token={download_token}
Range: bytes=0-1048575 (선택, Range 요청 시)
- 토큰이 만료되었거나 유효하지 않으면
401 Unauthorized반환 - 토큰은 1회 사용 후 무효화하는 것을 권장 (재생 공격 방어)
Risk
- "토큰은 1회 사용 후 무효화하는 것을 권장"이라고만 기술되어 있으나, 동시에 같은 토큰으로 2개 이상의 요청이 들어오는 경우의 원자적 무효화 전략이 없다.
- 토큰을 1회 사용 후 무효화하면, 대용량 파일의 Range 기반 이어받기(resume)가 불가능해진다. 네트워크 끊김 후 클라이언트가
Range: bytes=10485760-으로 재요청하면, 이미 무효화된 토큰이므로401이 반환된다.
5. storage_key → 로컬 FS 경로 resolve
storage_key를 실제 파일 시스템 경로로 변환한다.
- 키-경로 맵(DB 또는 설정 파일)을 조회하여 절대 경로 획득
- Path traversal 방어 필수:
realpath()등으로 경로를 정규화하고, 허용된 루트 디렉터리(/data/uploads/등) 내에 있는지 반드시 검증 - 파일이 존재하지 않으면
404 Not Found반환
6. 파일 스트림 응답 (Range 지원 권장)
파일을 청크 단위로 스트리밍하여 응답한다.
응답 헤더:
| 헤더 | 값 |
|---|---|
Content-Type |
파일 MIME 타입 |
Content-Disposition |
attachment; filename="파일명" |
Accept-Ranges |
bytes |
Content-Length |
파일 크기 (전체 또는 청크) |
Range 지원 시 동작:
Range헤더가 있으면206 Partial Content로 응답Range헤더가 없으면200 OK로 전체 파일 응답- Range를 지원하면 대용량 파일 resume, 미디어 스트리밍, 멀티파트 다운로드 모두 대응 가능
Risk
- 응답 헤더에
Content-Type: 파일 MIME 타입이라고만 되어 있고, 이 값을 어디서 가져오는지 명시되어 있지 않다. 업로드 시 클라이언트가 보낸 값을 DB에 저장하고 그대로 사용하는 경우, 공격자가text/html이나image/svg+xml로 위장한 악성 파일을 업로드할 수 있다. - 로컬 FS에서 직접 스트리밍하므로 API/스트리밍 서버의 egress 대역폭이 병목이 된다. 동시 다운로드 수 제한, 사용자별 대역폭 제한, 전체 서버 대역폭 보호 정책이 없다.
7. 로그 기록 / 카운트 증가
다운로드 완료 후 감사(Audit) 로그를 기록하고, 공유 링크의 경우 카운트를 증가시킨다.
- 로그 항목: 파일 ID, 요청자, IP, User-Agent, 다운로드 시각, 파일 크기
- 카운트 증가: 공유 링크에만 적용 (
download_count++) - 타이밍 주의: 스트림이 완전히 전송된 이후에 카운트를 올린다. 요청 시점에 올리면 중단된 다운로드도 집계된다.
에러 응답 정리
| 상황 | HTTP 상태 코드 |
|---|---|
| 인증 실패 / 토큰 만료 | 401 Unauthorized |
| 권한 없음 | 403 Forbidden |
| 파일 없음 | 404 Not Found |
| Range 범위 초과 | 416 Range Not Satisfiable |
| 정상 전체 응답 | 200 OK |
| 정상 부분 응답 (Range) | 206 Partial Content |
보안 체크리스트
- JWT / share_token 서명 검증
- 파일 접근 권한 DB 조회 (캐시 사용 시 TTL 주의)
- 다운로드 토큰 단명 발급 (TTL ≤ 60s)
- 토큰 1회 사용 후 무효화
- Path traversal 방어 (
realpath+ 루트 경로 검증) Content-Disposition: attachment헤더 명시- 다운로드 완료 후 감사 로그 기록