학원 관리 시스템에서 학생 프로필 사진을 저장하고 서빙하는 기능을 구현했다. Firebase Functions 백엔드에서 GCS(Google Cloud Storage)를 직접 사용하는 구조다.
처음에는 {projectId}-student-profiles처럼 도메인별로 버킷을 만들려 했다.
하지만 버킷이 늘어날수록 관리가 복잡해지므로, **목적별(public/private)**로 분리하는 구조로 변경했다.
{projectId}-assets-public ← 공개 에셋 (현재 사용)
{projectId}-assets-private ← 민감 데이터 (추후 필요 시)
버킷 안에서 도메인 구분은 경로로 한다:
assets-public/
└── students/{studentId}/profile_{timestamp}.webp
└── students/{studentId}/profile_{timestamp}_thumb.webp
└── classes/{classId}/cover.webp ← 추후 확장 시
이렇게 하면 버킷 수는 최소화하면서, IAM 정책(공개/비공개)은 버킷 단위로 깔끔하게 관리된다.
버킷에 allUsers → Storage Object Viewer 권한 부여
→ URL을 아는 누구나 접근 가능
구현은 간단하지만, URL 패턴이 students/{studentId}/profile_{timestamp}.webp이므로
studentId와 timestamp를 추측하면 누구나 학생 얼굴을 볼 수 있다.
학생 사진은 민감 정보이므로 이 방식은 부적절하다.
버킷은 private (외부 접근 차단)
→ 서버가 인증된 요청에 대해서만 시간 제한 URL을 발급
→ URL이 만료되면 접근 불가
일반 URL: <https://storage.googleapis.com/bucket/image.webp>
Signed URL: <https://storage.googleapis.com/bucket/image.webp>
?X-Goog-Signature=abc123...
&X-Goog-Expires=3600
Signed URL은 서버의 서비스 계정 키로 서명되어 있어서, GCS가 서명을 검증한 뒤에만 응답한다. 만료 시간이 지나면 같은 URL로 접근해도 403이 반환된다.
흐름:
클라이언트 → 백엔드 "학생 조회 API"
→ Firestore에서 GCS 경로 읽음 (students/abc/profile_123.webp)
→ 서비스 계정 권한으로 Signed URL 생성 (유효기간 1시간)
→ Signed URL을 응답에 포함하여 반환
클라이언트 → Signed URL로 GCS에 직접 요청 → 이미지 수신