서버 기반 권한 체계 전환 + 프론트엔드 테스트 도입
#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단계)
한 번에 갈아엎지 않고 신·구 판정을 공존시키며 리소스 단위로 옮기고, 옮긴 부분의 레거시 규칙은 바로 지웠다. 어느 단계에서 멈춰도 배포 가능한 상태를 유지했다.
- 인프라 (API·어댑터·판정 함수·스토어) + 단위 테스트
- 라우트 가드·상단 메뉴
- 단순 액션 화면
- 핵심 업무 화면 (결과 입력·수정, 실행 시간 한도 등)
- 작성자 기반 게시판 5종 — 게시판별 가드를 공용 가드 하나로 통합
- 사용자 관리·채팅
- 하드코딩 규칙 파일 삭제
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종 제거