ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Portfolio #2] Firebase 연동과 트러블 슈팅 (CRUD & 보안)
    Project/Portfolio Website 2025. 12. 21. 18:00

     전역 후 개발 공부를 위해 기획했던 포트폴리오 웹사이트를 리액트로 구축하고, 디자인하기로 하였다. 프로젝트의 완성 단계인 지금 회고록을 작성하고자 한다.

     

     총 세 편으로 프로젝트 회고록을 작성할 것이며 순서는 다음과 같다.

     

     1편에서 React와 Tailwind CSS로 그럴싸한 외관을 완성했다. 하지만 데이터를 저장할 곳이 없어 그저 "껍데기"에 불과하다는 생각이 들었다.

    이번 편에서는 별도의 백엔드 서버를 구축하는 대신, Serverless 서비스인 Firebase를 활용해 Feedback Message 기능을 구현한 과정과, 그 속에서 겪은 보안 규칙(Security Rules) 권한 오류를 해결한 경험을 정리한다.

     

    1. 왜 Firebase인가?

     이 프로젝트는 개인 포트폴리오 사이트이므로, 방명록 하나를 위해 EC2 인스턴스를 띄우거나 복잡한 DB 서버를 구축하는 것은 아직 주니어에겐 힘든 과정일뿐더러 오버엔지니어링이라고 판단했다.

    • NoSQL 기반의 유연함: 정형화된 스키마 없이 JSON 형태로 데이터를 저장할 수 있어 프론트엔드 개발자에게 친숙하다.
    • 실시간성: 데이터가 업데이트되면 즉각 반영되는 속도가 빠르다.
    • 비용 효율성: 이 프로젝트에서는 무료 티어(Spark Plan)로도 충분하다고 판단했다.

     

    2. 초기 설정과 환경 변수 관리

     가장 먼저 firebase.ts를 생성하여 초기화했다. 이때 API Key와 같은 민감한 정보는 GitHub에 그대로 올라가면 안 되기 때문에, Vite의 환경 변수 기능(import.meta.env)을 사용해 분리했다.

    (.env 파일은 .gitignore에 등록하여 저장소에 올라가지 않도록 설정했다.)
     
    // firebase.ts
    const firebaseConfig = {
      apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
      // ... 기타 설정
      projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
    };
    
    const app = initializeApp(firebaseConfig);
    export const db = getFirestore(app);

     

     

    3. 기능 구현, CRUD와 비밀번호 로직

     방명록과 유사한 정보를 제공하는 Feedback Message는 Create(작성), Read(조회), Update(수정), Delete(삭제)의 기본 기능을 갖춰야 한다. 여기에 '익명성'을 보장하기 위해 게시글 별 비밀번호관리자 모드를 도입했다.

    데이터 조회(Read)와 관리자 모드

     Main이 되는 Feedback.tsx에서는 Firestore에서 데이터를 가져와 상태(list)에 저장한다. 이때 createdAt을 기준으로 내림차순 정렬하여 최신 글이 먼저 보이도록 했다.

    또한, 내 포트폴리오인 만큼 나만 비밀글을 볼 수 있어야 했다. 별도의 로그인 구현이나 DB에서 직접 보는 번거로움 대신, 간단한 프롬프트 입력 방식을 통해 관리자 모드를 구현했다.

    // Feedback.tsx (관리자 모드 토글)
    const toggleAdminMode = () => {
      if (isAdmin) {
        setIsAdmin(false);
      } else {
        const input = prompt("관리자 비밀번호:");
        if (input === ADMIN_PASSWORD) { // 환경변수로 저장된 비밀번호 확인
          setIsAdmin(true);
        }
      }
    };
    

     

    비밀글과 삭제/수정 권한(Update/Delete)

    사용자가 글을 남길 때 '비공개 메시지(Secret)' 토글 기능을 구현했다. FeedbackItem.tsx에서는 isSecret 상태와 isAdmin 상태를 조합하여 내용을 숨기거나 보여준다.

    // FeedbackItem.tsx (렌더링 로직)
    const canShow = !item.isSecret || isAdmin;
    // ...
    {canShow ? item.text : "비공개 메시지입니다."}

     

    삭제나 수정 시에는 관리자가 아니라면, 글 작성 시 입력했던 비밀번호를 다시 입력받아 검증하는 로직을 추가하여 보안성을 높였다.

     

    4. 트러블 슈팅: 보안 규칙(Security Rules)과 권한 오류

     기능 구현 후 배포를 앞두고 가장 헤맨 부분이다. 로컬 테스트 모드에서는 잘 작동하던 기능이, 규칙을 조금만 건드리면 Missing or insufficient permissions 오류를 나타냈다.

    문제점 발견

    처음에는 단순하게 누구나 쓰고 읽을 수 있게 설정했으나, 이는 보안상 치명적이었다. 반대로 규칙을 강화하니 클라이언트에서 데이터를 아예 불러오지 못하는 문제가 발생했다.

    특히 비공개 메시지 처리가 까다로웠다. 프론트엔드에서 canShow 변수로 내용을 가리더라도, 브라우저 개발자 도구(Network 탭)를 열어보면 Firestore가 반환한 JSON 데이터에 비밀글 내용이 그대로 노출되는 문제가 있었다.

    해결 과정

    이를 해결하기 위해 Firestore의 Security Rules를 학습하여 적용했다.

    • 쓰기(create): 누구나 가능해야 한다.
    • 읽기(read): 기본적으로 누구나 읽을 수 있다.
    • 이상적인 방법 : resource.data.isSecret == false인 경우만 허용. 하지만 이렇게 하면 클라이언트의 쿼리(getDocs)와 보안 규칙이 충돌하여 에러가 발생했다.
    현실적인 타협

     완벽한 서버 필터링을 하려면 쿼리 자체를 "비밀글이 아닌 것만 가져오기"로 바꿔야 하는데, 그러면 "비밀글이 있다는 사실(자물쇠 아이콘)"조차 보여줄 수 없었다.

    따라서 이번 프로젝트에서는 **"UI상의 비밀글 처리"**에 집중하고, DB 레벨의 보안 규칙은 관리자 외의 무단 '삭제/수정'을 막는 데 초점을 맞추었다.

    // firestore.rules (예시)
    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /feedback/{document} {
          // 누구나 생성 가능
          allow create: if true;
          // 누구나 조회 가능 (내용 숨김은 클라이언트에서 처리)
          allow read: if true;
          // 수정/삭제는 로직 내에서 비밀번호 검증을 거치므로 일단 허용 
          // (더 안전하게 하려면 Cloud Functions가 필요함)
          allow update, delete: if true; 
        }
      }
    }
    

     

     실제 상용 서비스였다면, 비밀글 내용은 별도의 Sub-collection으로 분리하거나 Cloud Functions를 통해 관리자 권한을 가진 요청에만 원문을 내려주는 방식을 사용해야 한다. 이번 프로젝트는 학습과 포트폴리오 목적이므로 클라이언트 사이드 마스킹 방식을 채택했다.

     

     

    5. 결과물 및 개선점

    동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

     

     이제 방문자들이 자유롭게 피드백을 남길 수 있고, 나는 관리자 모드로 비공개 메시지를 확인하고 관리할 수 있게 되었다. animate-fade-in-up 효과 덕분에 글이 부드럽게 로딩되는 UX도 적용했다.

     

    하지만 코드를 작성하다 보니 Feedback.tsx 컴포넌트가 너무 비대해졌다.

    • 데이터 fetching 로직
    • 상태 관리 (list, isAdmin)
    • 관리자 인증 로직
    • UI 렌더링

     이 모든 것이 한 파일에 섞여 있어 유지보수가 어려워 보였다. 다음 편에서는 Zustand를 도입하여 복잡한 상태 관리 로직을 깔끔하게 분리하고 리팩토링하는 과정을 다루겠다.

제목 없는 코딩 블로그