본문 바로가기
프로젝트/사내 클라우드 스토리지

읽기 전용 공유, 버튼 하나가 아니었던 날

by iwbap 2026. 10. 16.
728x90
반응형

공유 버튼 뒤에 초대 상태 머신·수락 전 차단·읽기 전용 권한 울타리·HMAC 서명 푸시 아웃박스가 겹쳐 있는 과정을 담은 대표 이미지
공유 버튼 하나의 무게

사내 클라우드 스토리지에 읽기 전용 공유 버튼을 붙이면 작업이 끝날 것으로 생각했다. 실제로는 초대 상태와 서버 권한을 함께 설계하고, 알림 재전송과 운영 검증까지 확인해야 했다. 2026년 10월 2일의 구현·배포 기록을 바탕으로 그 과정을 정리했다.

공유 버튼만 만들면 파일을 열 수 있을 줄 알았다

처음 목표는 사내 클라우드 스토리지의 파일과 폴더를 다른 계정에 읽기 전용으로 공유하는 것이었다. 소유자가 공유 대상을 고르고 상대 계정을 검색한 뒤 초대하는 흐름을 구상했다.

하지만 초대를 보냈다고 곧바로 접근을 허용할 수는 없었다. 수신자가 수락하기 전에는 원본 파일뿐 아니라 미리보기와 다운로드 경로도 막아야 했다.

초대를 수락한 뒤에도 파일을 읽는 권한과 수정하는 권한은 구분해야 했다. 수신자에게는 읽기 권한만 제공하고, 원본의 소유권과 저장 용량 계산은 그대로 유지하기로 했다.

  • 수락 전: 원본 파일, 썸네일, 미리보기, 영상 스트림, 다운로드 접근 차단
  • 수락 후: 파일 읽기, 미리보기, 다운로드만 허용
  • 변경 작업: 이름 변경, 삭제, 업로드, 이동, 서버 복사, 재공유 차단

폴더 공유는 현재 포함된 하위 항목에 적용되도록 했다. 공유 폴더 밖으로 이동한 파일은 해당 폴더를 통해 얻은 공유 권한을 잃도록 구성했다.

단일 파일 공유는 원본 ID에 묶인다. 소유자가 취소하거나 원본을 지우면 새 접근은 거절된다. 이미 받아 간 사본의 DRM까지는 아니다.

소유자 초대부터 수신자 pending·accepted·declined까지의 읽기 전용 공유 흐름과 수락 전 접근 차단을 나타낸 문제 도식
문제: 초대 흐름과 수락 전 차단

초대 목록만 붙이면 충분하다는 가정이 틀렸다

처음에는 WebView UI에 공유 창과 사이드바를 붙이면 충분할 것으로 생각했다. 계정 검색은 사내 인증 서비스에 맡기고, 게이트웨이는 공유 여부만 확인하면 된다고 봤다.

실제로는 같은 파일을 중복 초대하는 상황부터 처리 기준이 필요했다. 같은 항목과 동일한 수신자 집합의 유효한 초대가 있으면 기존 초대를 반환하도록 했다.

수락과 거절 요청도 네트워크 문제로 여러 번 전달될 수 있었다. 같은 응답을 다시 보내더라도 결과가 달라지지 않도록 멱등성을 확보했다.

공유 목록은 최신 500건으로 제한해 가져오도록 구성했다. 초대 상태는 수신자가 응답하기 전인 pending과 수락한 accepted, 거절한 declined로 구분했다.

알림 전달도 단순한 API 호출 하나로 끝내지 않기로 했다. 전송 실패에 대비해 SQLite 공유 원장에 아웃박스를 두고, 서명된 이벤트를 다시 전달할 수 있게 구성했다.

이벤트에는 HMAC 서명을 적용해 사내 인증 서비스의 알림 릴레이로 전달하도록 했다. 공유 상태를 기록하는 처리와 알림 전송을 분리한 것이 중요한 변경이었다.

진짜 문제는 수락 전 접근을 막는 권한 검사였다

공유 기능에서 가장 중요하게 다룬 부분은 초대와 실제 접근 권한의 경계였다. pending 상태는 초대 기록일 뿐, 파일을 읽을 수 있는 권한이 아니었다.

원본 파일 조회만 막고 다른 읽기 경로를 열어 두어서는 안 됐다. 게이트웨이에서는 수락 전 다음 접근을 모두 차단하도록 구성했다.

  • 원본 파일 조회
  • 썸네일 조회
  • 파일 미리보기
  • 영상 스트림 접근
  • 파일 다운로드

수신자가 수락해 accepted가 된 뒤에만 읽기 경로를 열었다. 파일 조회 과정에는 읽기 전용 분기를 추가해 공유 수신자의 접근을 구분하도록 했다.

쓰기 API에는 공유 권한을 부여하지 않는 구조를 유지했다. 파일 변경과 관련된 작업은 기존 소유자 검사를 거치도록 해 읽기 권한만으로 변경할 수 없게 했다.

읽기 권한을 제공한다고 해서 원본 파일을 복제하지도 않았다. 저장 용량은 원본 소유자의 할당량에만 반영되도록 기존 소유 관계를 유지했다.

계정 검색은 한 번에 최대 20명, 초대는 최대 50명까지. UI만 예쁘게 해도, 게이트웨이가 수락 전·후를 다르게 막지 않으면 공유 버튼은 구멍이 된다.

pending에서 원본·썸네일·미리보기·다운로드를 막고 accepted 뒤에만 읽기·미리보기·다운로드를 여는 읽기 전용 권한 울타리 도식
원인: pending 차단과 읽기 전용 울타리

공유 원장과 서명 릴레이를 구현하고 테스트했다

구현은 저장 구조부터 화면과 검증까지 네 부분으로 나누어 정리했다. 2026년 10월 2일 기능 커밋의 변경량은 939줄 추가와 19줄 삭제였다.

  1. 공유 원장과 수신자 상태 관리

    SQLite에 공유 원장·수신자·아웃박스 테이블을 추가했다. 기존 파일·폴더·청크를 변환하거나 삭제하지 않는 추가형 스키마를 사용했다.

  2. HMAC 서명과 푸시 아웃박스

    공유 이벤트를 아웃박스에 기록하고 사내 인증 서비스의 알림 릴레이로 전달하도록 했다. 공유 상태와 알림 이벤트는 같은 트랜잭션에 저장하고, 릴레이 전달이 실패하면 120초 뒤 다시 시도하도록 했다. 이벤트에는 HMAC 서명을 붙였다.

  3. WebView 공유 화면

    WebView UI에 공유 흐름과 사이드바를 추가했다. Windows WebView 클릭 테스트에서는 수락·차단·다운로드 등의 동작을 검증했다.

  4. 게이트웨이 자동 테스트

    공유와 푸시 관련 사례를 포함해 게이트웨이 Python 테스트 147개를 실행했다. 읽기 전용 권한과 초대 상태에 관한 검증을 자동 테스트 범위에 포함했다.

접근 제어의 핵심을 간단히 나타내면 아래와 같은 흐름이었다. 실제 구현 코드를 옮긴 것은 아니며, 공유 수신자의 권한 경계를 설명하기 위한 의사코드다.

# 개념 설명용 의사코드: 공유 수신자 접근 제어
if share.state != "accepted":
    deny("original")
    deny("thumbnail")
    deny("preview")
    deny("video_stream")
    deny("download")
else:
    allow("read", "preview", "download")
    require_owner("rename", "delete", "upload")
    require_owner("move", "server_copy", "reshare")

이 의사코드는 수락 상태와 읽기 전용 권한의 관계만 보여 준다. 실제 접근 판단에는 원본의 존재 여부와 소유자 검사 등 별도의 조건도 반영했다.

같은 날 사내 인증 서비스를 운영 환경에 배포하고 게이트웨이를 재시작했다. 릴레이 비밀 설정을 적용한 뒤 HMAC 서명 이벤트가 HTTPS 릴레이까지 전달되는지 확인했다.

이 검증에는 존재하지 않는 수신자 ID를 사용했다. 실제 사용자에게 초대를 보내지 않고 릴레이 구간의 서명 이벤트 전달을 확인한 것이었다.

운영 FCM 자격 증명의 OAuth 인증과 네이티브 세션 등록·삭제 검증은 통과했다. 다만 해당 결과를 실제 두 계정 사이의 파일 공유가 성공했다는 뜻으로 해석하지는 않았다.

운영 데이터베이스에는 새로운 공유 관련 테이블만 추가했다. 배포 전 스냅샷과 운영 DB를 대조해 기존 행에 변경이 없었음을 확인했다.

운영용 릴레이 설정은 systemd의 EnvironmentFile 오버레이로 분리했다. 기존 파일과 폴더, 청크를 변환하거나 삭제하지 않았다는 점도 함께 점검했다.

두 계정 실기기 공유 검증은 새 Android 앱을 설치한 뒤 확인할 과제로 남겼다. 백업은 SQLite backup API와 MySQL 트랜잭션 덤프로 잡았고, Firebase의 Android에서 FCM 메시지 수신 문서는 배포 기록에 공식 참고 자료로 남겨 두었다.

공유 원장·HMAC 서명 아웃박스·WebView 클릭 테스트·운영 스냅샷 검증으로 읽기 전용 공유를 붙인 전후 비교 도식
해결: 원장·아웃박스·운영 검증

읽기 전용 공유를 붙이며 배운 것

공유 기능은 화면에서 초대 버튼을 추가하는 일만으로 끝나지 않았다. 초대 상태 머신과 서버의 읽기 전용 권한 검사가 맞물려야 접근 범위를 통제할 수 있었다.

  • 초대 상태와 접근 권한은 별개다. 수락 전에는 모든 읽기 경로를 차단하고, 수락 이후에도 변경 권한은 주지 않아야 했다.
  • 알림 전달은 실패 가능성을 고려해야 했다. 공유 원장과 재전송 가능한 HMAC 서명 아웃박스를 분리해 처리했다.
  • 운영 검증은 확인한 범위까지만 기록해야 했다. 데이터베이스와 릴레이 검증을 통과했어도 두 계정 실기기 공유는 별도 확인 과제로 남겼다.

이번 작업은 공유 UI를 추가하기보다 권한과 상태의 계약을 다시 정하는 과정에 가까웠다. 다음에는 남겨 둔 두 계정 Android 실기기 공유 흐름을 확인해야 했다.

자주 묻는 질문

Q. 초대를 수락하기 전에 썸네일만 보여 줄 수는 없나? 이번 구현에서는 썸네일도 접근 차단 대상에 포함했다. 수신자가 초대를 수락한 뒤에만 해당 읽기 경로를 열도록 했다.

Q. 공유 폴더에서 파일을 밖으로 옮기면 어떻게 되나? 공유 폴더 밖으로 이동한 파일은 해당 폴더를 통한 공유 권한을 잃는다. 별도로 설정한 단일 파일 공유는 원본 ID에 연결되는 방식으로 구분했다.

Q. 이미 다운로드한 파일도 공유 취소로 삭제할 수 있나? 소유자가 공유를 취소하거나 원본을 삭제하면 이후의 새로운 접근을 거절한다. 이미 다운로드한 사본을 원격으로 삭제하는 DRM 기능은 이번 구현 범위가 아니었다.

참고 문서: Firebase Cloud Messaging — Android 앱에서 메시지 수신. Android 앱의 FCM 메시지 수신 동작을 확인할 때 참고할 수 있는 공식 문서다.

728x90
반응형