Files
cloud-sharp-docs/설계/flows/다운로드 흐름.md
2026-03-16 17:57:51 +09:00

5.4 KiB

!file_download_flow.svg

단계별 상세

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 헤더 명시
  • 다운로드 완료 후 감사 로그 기록