Seoyoung Portfolio LogoSeoyoung.

서버 기반 권한 체계 전환 + 프론트엔드 테스트 도입

#frontend#React#TypeScript#Zustand#TanStack Query#Vitest#React Testing Library#GitLab CI
Seoyoung Kim · 프론트엔드 권한 레이어 설계·구현, 테스트 체계 도입 (FE 단독 담당) @ (주)사파이어스트림테크놀로지 / System SW
2026-10-01 ~ 2026-10-19

사내 업무 플랫폼 프로젝트다. 보안 정책상 화면·내부 용어·코드는 공개하지 않고 구조와 의사결정 위주로 정리했다.

배경

  • 권한 검사가 사실상 프론트엔드 화면 가드에만 의존하고 있어, 화면을 거치지 않는 요청은 막을 수 없는 구조였다.
  • 역할·지역·계정 ID 목록이 환경별 규칙 파일에 하드코딩돼 있어 권한 하나를 바꾸려면 FE 배포가 필요했다.
  • 판정 로직이 화면 67곳에 흩어져 있어, 같은 기능도 목록과 상세에서 판정이 달랐다.
  • 저장소에 FE 테스트가 없어 대규모 교체의 회귀를 수동 클릭으로만 확인할 수 있었다.

→ 권한 정의와 최종 검사는 서버가 맡고, 프론트엔드는 서버가 내려준 권한 맵을 해석해 UI만 제어하는 구조로 바꿨다.

역할

  • 프론트엔드 권한 레이어 설계·구현 단독 담당 (설계 문서 → 단계별 계획 → 구현 → 레거시 대조 리뷰)
  • 테스트 도구 도입, 테스트 작성 규칙 수립, CI 검증 단계 추가
  • 백엔드와 권한 리소스 목록·판정 의미 협의 (서버 API 구현은 백엔드 담당)

1. 권한 레이어 설계

none
권한 API └─ Adapter 응답 정규화 (서버 응답 형태는 여기서만 다룸) └─ 판정 함수 순수 함수 — 기능 단위 / 데이터 단위 └─ Store 계정별 키로 캐시 분리 └─ Hook · Guard · 메뉴 · 버튼
  • 두 종류의 판정
    • 기능 단위: 이 메뉴·페이지·기능을 쓸 수 있는가
    • 데이터 단위: 작성자 본인, 같은 조직, 본인 계정처럼 대상 데이터와의 관계 조건 중 하나라도 맞으면 허용 (백엔드와 OR 의미로 합의)
  • Fail closed: 권한을 받기 전, 조회 실패, 알 수 없는 값은 모두 거부한다. 프론트엔드에 관리자 우회 경로를 두지 않았다.
  • 계정 전환 안전성: 로그인 ID·역할·지역을 묶은 키로 캐시를 저장해, 계정이 바뀌거나 조회에 실패해도 다른 사용자의 권한이 쓰이지 않는다. 역할 변경, 세션 만료 후 재로그인 시에는 권한을 다시 조회한다.

2. 점진적 교체 (7단계)

한 번에 갈아엎지 않고 신·구 판정을 공존시키며 리소스 단위로 옮기고, 옮긴 부분의 레거시 규칙은 바로 지웠다. 어느 단계에서 멈춰도 배포 가능한 상태를 유지했다.

  1. 인프라 (API·어댑터·판정 함수·스토어) + 단위 테스트
  2. 라우트 가드·상단 메뉴
  3. 단순 액션 화면
  4. 핵심 업무 화면 (결과 입력·수정, 실행 시간 한도 등)
  5. 작성자 기반 게시판 5종 — 게시판별 가드를 공용 가드 하나로 통합
  6. 사용자 관리·채팅
  7. 하드코딩 규칙 파일 삭제

7단계까지 완료해 하드코딩 규칙 파일 3개를 삭제했다. 서버에 아직 리소스가 없는 예외 3종만 남겼고, 서버에 추가되면 바로 지울 수 있도록 제거 조건을 코드에 적어 두었다.

3. 함께 정리한 것

  • 비동기 라우트 가드: 권한 준비 전에는 로딩, 조회 실패 시 거부 안내와 자동·수동 재시도. 권한을 받기 전에는 버튼 자리를 비워 두어 "안 보임 → 보임" 깜빡임이 생기지 않게 함
  • 정책 값 서버 이관: 역할별로 하드코딩된 실행 시간 한도를 서버 값으로 교체하고, 값이 늦게 도착하면 초과 입력을 한도로 보정
  • 메뉴 일관성: 표시할 항목이 있을 때만 메뉴를 렌더링하고, 항목마다 각자의 권한을 따르도록 통일
  • 중복 제거: 게시판별 가드, 레이아웃마다 있던 채팅 노출 판정, 테이블 행 클릭 처리를 공용화

4. 레거시 대비 전수 대조

기존 판정 91건을 새 구현과 하나씩 대조해 MR에 분류해 기재했다.

분류건수의미
동일54동작 그대로
의도된 변경12확인을 거친 동작 변경
신규 가드15그동안 열려 있던 경로에 가드 추가
서버 확인 필요7백엔드에 공유
보류3서버에 리소스가 없어 레거시 유지
의도치 않은 제거0사라진 가드 없음

바뀌는 동작을 "숨은 변경"으로 두지 않고 모두 드러내 문서로 팀에 공유했고, 서버 규칙과의 차이는 백엔드에 전달했다.

5. 테스트 도입

검증 흐름

  • 커밋 시: 변경 파일과 관련된 테스트만 실행 (husky + lint-staged) — 빠르게 고쳐 주는 층
  • MR 시: 전체 lint·타입 검사와 전체 테스트를 별도 잡으로 실행하고, 통과해야 머지 — 아무도 우회하지 못하는 확인 층. 실패한 테스트는 MR 화면에서 바로 확인 (GitLab CI, JUnit 리포트)
  • Vitest를 고른 이유: Vite 설정과 경로 alias를 그대로 공유해 별도 변환 설정이 필요 없고, Jest와 같은 API라 팀이 익히기 쉬움
  • 팀 컨벤션에 테스트 작성 기준을 추가하고 도입 가이드를 작성했다.

무엇을 검증하나

  • 단위 테스트: 판정 함수의 fail closed 경계, 데이터 단위 OR 판정, 서버 응답 정규화, 계정 전환 시 캐시 분리
  • 컴포넌트 테스트 (React Testing Library): 권한 맵을 주입해 가드의 로딩·거부 흐름, 권한 재조회·재시도, 게시글 수정 가드, 권한별 메뉴·버튼 표시·숨김·비활성을 확인
  • 공용 도구: Provider를 감싼 렌더 헬퍼, 권한 맵 주입 헬퍼, jsdom 미지원 API 스텁
  • 작성 규칙: 소스 옆 배치, "조건 → 기대" 문장형 테스트명, 역할·이름 기반의 접근성 쿼리 우선

테스트가 실제로 판정을 지키는지

권한 판정 104곳을 하나씩 반대로 뒤집었을 때 모두 테스트가 실패하는 것을 확인했다 (뮤테이션 방식 점검). 판정을 빼도 통과하는 테스트는 없다.

테스트 작성 과정에서 고친 결함 (10건)

  • 응답 필드가 빠지면 수정 화면이 깨지는 버그 — 컴포넌트 테스트 실패로 먼저 발견하고 회귀 테스트 추가
  • 세션 만료·로그아웃 시 관리자 화면이 로딩에서 멈춤
  • 토큰 만료를 감지해 재로그인해도 권한을 다시 받지 않음
  • 게시글 수정 가드가 조회 실패 시 빈 화면을 남김
  • 아이콘만 있는 버튼 12종의 접근성 라벨 누락 (접근성 쿼리로 요소를 찾다가 발견)
  • 그 외 캐시 갱신 누락, 메뉴 노출 조건 오류 등 5건

결과

  • 판정 호출 67곳을 공용 권한 레이어로 교체하고, 예외 3종 외 레거시 권한 규칙 모두 제거
  • 컴포넌트 테스트로 응답 필드가 빠지면 수정 화면이 깨지던 기존 운영 버그를 발견 · 수정
  • 기존 리소스의 권한 변경은 FE 배포 없이 서버 규칙만 바꾸면 반영되는 구조

배운 점

  • 권한은 서버가 진실이고 프론트엔드 가드는 UX다. 이 원칙은 fail closed와 우회 경로 제거처럼 구조로 강제해야 지켜진다.
  • 대규모 교체는 신·구 공존 → 리소스 단위 이관 → 즉시 삭제 순서로 하면 중간 상태에서도 배포할 수 있다.
  • 바뀌는 동작을 대조표로 드러내 두면 리뷰할 때 무엇이 의도된 변경인지 바로 확인할 수 있었다.
  • 컴포넌트 테스트를 접근성 쿼리로 쓰면 접근성 결함까지 함께 드러난다.

남은 과제

  • 서버에 리소스가 추가되면 남은 예외 3종 제거