---
phase: requirements
agent: requirements-advisor
agent_version: 1
generated_at: 2026-06-23T00:41:35Z
workstream: W1-거버넌스/RBAC
concerns:
  - "권한 모델 형태(결정1)·관리자 부트스트랩(결정3)·인터셉터 보호범위(결정5)는 권장안을 제시했으나 사용자 최종 확정 필요 — AskUserQuestion 서브에이전트 불가로 orchestrator 경유 확인 요함"
  - "RBAC 도입은 신규 DB 스키마(permissions/user_permissions)를 강제함 — schema.sql + DDL 적용 절차(maintenance) 동반 변경"
  - "role 변경 시 세션 role attr(UserController:508) stale — 세션 동기화/재로그인 전략은 design 결정이나 보안 비기능 요구로 격상"
  - "기존 임시 ROLE_ADMIN(.equals) 흡수 시 comment/review 모더레이션 동작 변경 — W3-2 산물 회귀 점검 필요"
concerns_checked: true
self_verification:
  checklist_passed: true
---

# W1 — 거버넌스 / RBAC 요구사항

## 원 요청 (로드맵 §W1 인용)
> 관리자(전체 권한) / 부관리자(허용된 권한만 = 권한부여형) 모델 / 관리자 콘솔 — 부관리자 임명 + 권한 토글 / 권한 체크 인터셉터 / 권한 토글 항목 예시: 게임잼관리, 포스팅작성(=포스터 권한). 의존 없음. 공유자원 users, security/, 세션/인증.

---

## §0. RBAC 권한 생명주기 맵 (end-to-end — 구조적 gap-hunt)

순차 상태 전이가 내포된 개념(부관리자 임명→권한 토글→권한 회수/강등, role 변경→세션 반영)이 있으므로 표면 요구 너머의 단절을 적극 헌트한다. 각 단계에 현재 코드 상태 + 단절 여부를 표기.

| 단계 | 전이 | 현재 코드 상태 | 단절 |
|---|---|---|---|
| L0 | 가입 → USER 부여 | `UserController:295` 전원 `setRole("USER")` | OK |
| L1 | 최초 ADMIN 부트스트랩 | **경로 없음** — signup 전원 USER, 승격 수단 0 | **단절 — 갭 G1** |
| L2 | ADMIN → 부관리자(SUBADMIN) 임명 | 콘솔/임명 API 0 | **단절 — 갭 G2** |
| L3 | 부관리자에게 권한 토글 부여 | 권한 저장 구조 0 (role 단일 String) | **단절 — 갭 G3** |
| L4 | 요청 시 권한 게이트 통과 검사 | Interceptor 0, `addInterceptors` 0 | **단절 — 갭 G4 (QG-1 핵심)** |
| L5 | 권한 토글 **회수**(역방향) | 회수 API·세션 무효화 0 | **단절 — 갭 G5 (역방향 전이)** |
| L6 | 부관리자 → USER **강등/해임**(종료) | 강등 경로 0 | **단절 — 갭 G6 (종료 전이)** |
| L7 | role/권한 변경 후 **세션 반영** | 세션에 `role` attr 박제(`:508`), 변경 전파 0 → **stale 권한** | **단절 — 갭 G7 (2차 단절·보안)** |

> happy-path(L0~L4)만이 아니라 **회수/강등/세션 무효화**(L5~L7)를 반드시 W1 스코프에 포함한다. 권한을 줄 수만 있고 회수·전파가 없으면 거버넌스로서 미완이며 보안 결함(권한 회수 후에도 세션 유효)이 된다.

### 복수 이벤트 조건 대칭 점검 (병렬 분기)
- **권한 부여 시 / 권한 회수 시** 세션 반영이 대칭이어야 한다. 부여만 즉시 반영하고 회수는 다음 로그인까지 지연되면 보안 비대칭(회수 우회). → 결정축에 반영(아래 결정7).
- **임명(승격) 시 / 해임(강등) 시** 감사 로그 기록이 대칭이어야 한다. → NFR-보안 감사로그.

---

## 기능 요구 (FR)

### 권한 모델·저장
- **FR-1**: 사용자는 ADMIN / SUBADMIN / USER 중 하나의 기본 role을 가진다. (`users.role` 재사용, 값 집합 확장)
- **FR-2**: SUBADMIN(부관리자)은 0개 이상의 개별 권한(permission)을 부여받을 수 있다. ADMIN은 전 권한을 암묵적으로 보유한다(권한 토글 무관). USER는 권한 0.
- **FR-3**: 권한 카탈로그는 최소 `GAME_JAM_MANAGE`(게임잼관리), `POST_WRITE`(포스팅작성/포스터)를 포함한다. 모더레이션 권한(`CONTENT_MODERATE`)을 추가 후보로 둔다(결정4 연계).

### 관리자 콘솔 (L1~L6)
- **FR-4**: ADMIN은 콘솔에서 사용자를 SUBADMIN으로 **임명**(승격)할 수 있다. (G2)
- **FR-5**: ADMIN은 콘솔에서 SUBADMIN의 각 권한을 **토글(부여/회수)**할 수 있다. (G3, G5 — 부여·회수 대칭)
- **FR-6**: ADMIN은 SUBADMIN을 USER로 **강등/해임**할 수 있고, 강등 시 부여 권한은 전부 회수된다. (G6)
- **FR-7**: 콘솔은 현재 운영진(ADMIN/SUBADMIN) 목록과 각자의 권한 상태를 조회한다.
- **FR-8**: ADMIN 권한 자체는 콘솔에서 부여하지 않는다(최초 ADMIN은 부트스트랩 전용 — 결정3). 콘솔에서 다루는 임명 대상은 SUBADMIN과 그 권한에 한정한다.

### 권한 체크 인터셉터 (L4 — QG-1 선행)
- **FR-9**: `HandlerInterceptor` 구현 + `addInterceptors` 등록으로 보호 경로 진입 시 권한을 검사한다.
- **FR-10**: 보호 대상 — (a) 관리자 콘솔 경로 전체(ADMIN only), (b) 게임잼관리 액션(`GAME_JAM_MANAGE` 보유자 — W2 소비), (c) 포스팅 작성 액션(`POST_WRITE` 보유자 — W3-3 소비). 미보유 시 거부(미인증=401/로그인 리다이렉트, 인증·미인가=403).
- **FR-11**: 인터셉터는 세션 식별값(`userId`)을 기준으로 권한을 판정한다. 세션 role attr 단독 신뢰 여부는 결정7(세션 stale)에 종속.

### 부트스트랩 (L1)
- **FR-12**: 최초 ADMIN은 운영 DB seed/수동 승격으로 지정한다(결정3 권장안). 코드 자동 승격 경로는 두지 않는다.

### 기존 임시 ADMIN 흡수 (결정4)
- **FR-13**: comment/review 모더레이션 체크(`GameCommentController:201`, `GameReviewController:419`의 `ROLE_ADMIN.equals(role)`)를 W1 권한 게이트(운영자 판정)로 대체한다. ADMIN + `CONTENT_MODERATE` 보유 SUBADMIN이 통과하도록 재정의한다. (권장안 — 사용자 확정 시)

### 세션·권한 전파 (L7 — 2차 단절)
- **FR-14**: role/권한 변경(임명·토글·회수·강등) 후, 대상 사용자의 후속 요청에서 새 권한이 반영되어야 한다. (부여·회수 **대칭** — 결정7)

---

## 비기능 요구 (NFR)

- **NFR-보안(CSRF)**: 콘솔의 모든 상태변경(임명/토글/회수/강등)은 `CsrfTokens.isValid` 검증 적용. (기존 패턴 — comment/review/UserController 일관)
- **NFR-보안(SQL)**: 권한 조회·갱신 MyBatis SQL은 `#{}` 바인딩만 사용, `${}` 금지.
- **NFR-보안(인가 경계)**: 권한 판정은 서버측 인터셉터/컨트롤러에서 수행. 클라이언트(JSP) 노출은 표시용일 뿐 게이트 아님. 콘솔 진입·임명 API는 ADMIN 이외 도달 시 403.
- **NFR-보안(세션 무결성)**: 권한 변경 시 stale 세션 권한 방지(FR-14). 권한 **상승은 즉시, 회수는 즉시** 반영을 목표(비대칭 금지). 세션 고정 방어(`changeSessionId` — 로그인 시 이미 존재)는 보존.
- **NFR-보안(감사 로그)**: 임명/강등/권한 토글은 누가·누구를·언제·무엇을 변경했는지 기록(감사 추적). 임명·해임 **대칭** 기록.
- **NFR-운영(롤백/마이그레이션)**: 신규 권한 스키마는 schema.sql + 적용 DDL로 제공, 기존 데이터(전원 role='USER')와 호환(추가만, 파괴 없음). 롤아웃 순서: 스키마 적용 → 부트스트랩 ADMIN 지정 → 인터셉터 배포.
- **NFR-호환성**: 기존 `users.role` 컬럼·세션 role attr·comment/review 동작을 보존하거나 명시적으로 대체(FR-13). USER 기존 사용자에 영향 없음.
- **NFR-i18n**: 콘솔 UI 문자열은 기존 JSP 한국어 직접 출력 패턴 따름(별도 i18n 프레임워크 없음 — 해당 없음 수준).
- **NFR-접근성**: 콘솔은 관리자 전용 내부 화면 — 표준 폼/키보드 조작 보장 외 특별 요건 없음(해당 최소).
- **NFR-성능**: 권한 체크는 요청당 1회 발생 — 권한 조회 캐시/세션 캐싱 여부는 design 결정(stale 트레이드오프와 결합, 결정7). 상한 요구 없음.

---

## 6개 핵심 결정의 확정값 (권장안 + 트레이드오프)

> AskUserQuestion이 서브에이전트에서 불가하여, 각 결정에 권장안을 명시하고 사용자 확정이 필요한 항목은 [확정필요]로 표기했다. design 진입 전 orchestrator가 사용자에게 확인.

### 결정1 — 권한 모델 형태 [확정필요·권장 (a)]
- **권장 (a)**: `users.role`(ADMIN/SUBADMIN/USER) 유지 + `user_permissions` join 테이블(user_id × permission). 부관리자별 개별 토글을 직접 표현.
- 근거: 기존 `.equals(role)`(comment/review) + 세션 role attr 코드를 **호환 보존**하면서 부분집합을 추가. 변경 표면 최소.
- 트레이드오프: 판정이 role 분기 + permission 조회 2단계. (b)보다 모델이 약간 복잡.
- (b) user↔permission 직접: role 개념 제거 — 기존 ADMIN 체크/세션 전면 교체 비용. 기각.
- (c) role↔permission 매핑: 역할 단위라 "부관리자 개인별 토글" 요구와 불일치. 기각.

### 결정2 — 초기 permission 카탈로그 [권장 확정: DB 카탈로그 + 2~3권한]
- **권장**: `GAME_JAM_MANAGE`, `POST_WRITE` 출시 포함 + (결정4 채택 시) `CONTENT_MODERATE`. 카탈로그를 **DB 테이블**(`permissions`)로 관리해 운영자 추가·향후 W2-2(심사위원)/W4(배지)와 분리 흡수 가능.
- 트레이드오프: DB 카탈로그는 코드 enum 대비 타입 안전성↓, 권한 키 오타 가능 → permission 키는 코드 상수와 DB 시드를 동기화하는 계약 필요(design).
- 대안: 코드 enum(단순·타입안전, 추가 시 배포). W1 권한이 2~3개로 적어 enum도 무리 없음 — [확정필요] 여지.

### 결정3 — 관리자 부트스트랩 [확정필요·권장: DB seed/수동 승격]
- **권장**: 운영 DB에서 특정 user.role을 ADMIN으로 직접 UPDATE(seed 스크립트 또는 1회 수동 maintenance). 코드 변경·설정 노출 없음.
- 근거: 가장 안전(공격면 0), 순환문제(콘솔 임명은 ADMIN 선존 필요) 회피.
- 트레이드오프: 운영 수동 절차 1회 필요 → maintenance 문서화 동반.
- 대안: 설정 기반 자동 승격(이메일 목록) — 설정 노출·관리 부담으로 비권장.

### 결정4 — 기존 임시 ROLE_ADMIN 흡수 [확정필요·권장: 권한 게이트로 통일]
- **권장**: comment/review 모더레이션을 ADMIN + `CONTENT_MODERATE` 보유자 통과로 재정의(FR-13). 부관리자도 모더레이션 권한 토글 가능.
- 근거: 임시 산물(W3-2)을 W1 체계로 흡수해 권한 일원화. 미루면 ADMIN 하드코딩 분산 유지.
- **2차 단절 점검**: 변경 시 기존 ADMIN 통과 동작이 끊기면 안 됨 → ADMIN은 모든 권한 암묵 보유(FR-2)로 회귀 방지. W3-2 모더레이션 테스트 회귀 확인 필요(concern).
- 대안: 현행 `.equals("ADMIN")` 호환만 유지(부관리자 모더레이션 불가, 최소 변경) — W1 일원화 목적엔 미달.

### 결정5 — 인터셉터 보호 범위 (QG-1) [확정필요·권장 범위 확정]
- **권장**: W1 출시에 인터셉터 **포함 확정**(W3-3가 이를 선행 의존 — QG-1). 보호 대상:
  - 관리자 콘솔 경로 전체 → ADMIN only.
  - 게임잼관리 액션 → `GAME_JAM_MANAGE` (W2가 경로 확정 시 등록, W1은 게이트 인프라 제공).
  - 포스팅 작성 액션 → `POST_WRITE` (W3-3 소비).
- 근거: QG-1에서 "W3-3은 W1 완료 후 착수(임시 체크 안 함)" 이미 확정 → 인터셉터가 W1 산출에 반드시 포함.
- 트레이드오프: 보호 경로 매핑 방식(URL 패턴 vs 어노테이션 vs 핸들러 메타) = design 결정. 미인증(401/리다이렉트) vs 미인가(403) 응답 정책 = design 확정.

### 결정6 — 관리자 콘솔 범위 [권장 최소 범위]
- **권장 최소**: (1) 운영진 목록 조회(FR-7), (2) 부관리자 임명(FR-4), (3) 권한 토글 부여/회수(FR-5), (4) 강등/해임(FR-6). 4개 액션. 모두 CSRF 보호.
- 제외(후속): 권한 변경 이력 열람 UI, 일괄 작업, 사용자 검색 고도화.

### 결정7 (신규 — gap-hunt 산물) — 세션 권한 전파(stale) [확정필요·권장: 즉시 반영, 부여·회수 대칭]
- 배경: 세션에 `role` attr 박제(`UserController:508`). 인터셉터가 세션 role만 신뢰하면 권한 변경이 다음 로그인까지 미반영 → **회수 우회 보안 결함**(L7/G7).
- **권장**: 인터셉터는 권한 판정 시 권위 소스(DB user_permissions 또는 세션과 동기화된 캐시)를 신뢰. 변경 시 부여·회수 **대칭 즉시 반영**.
- 트레이드오프: 매 요청 DB 조회(성능) vs 세션 캐시(stale 위험). design에서 캐시 TTL/무효화 전략 결정. **단 회수의 즉시성은 비기능 보안 요구로 양보 불가.**

---

## 스코프
- **포함**: ADMIN/SUBADMIN/USER role 확장, user_permissions 저장, 권한 카탈로그(2~3개), 관리자 콘솔 4액션(임명·토글·회수·강등 — 부여/회수 대칭), 권한 체크 인터셉터(QG-1), 부트스트랩 절차, 기존 ADMIN 흡수(결정4 채택 시), 세션 권한 전파(결정7).
- **제외**: 심사위원 역할(W2-2), 리뷰어/기술자 배지(W4), 게임잼관리 액션의 실제 비즈니스(W2 — W1은 게이트만 제공), 포스팅 작성 기능 본체(W3-3 — W1은 권한만 제공), 권한 변경 이력 UI·일괄작업(후속).

---

## 가정 / 추측
- (가정) `users.role` 컬럼(varchar(30), DEFAULT 'USER')을 그대로 재사용해 값 집합만 확장 — schema.sql:35 근거. role 컬럼 자체 신규 추가 아님.
- (가정) 세션 고정 방어(`changeSessionId`)는 로그인에 이미 존재(`UserController:160`)하므로 W1은 보존만, 재구현 불필요.
- (가정) comment/review의 CSRF·canModify 패턴이 W1 콘솔의 참조 표준(동일 `CsrfTokens.isValid`).
- (추측→concern) RBAC 도입은 신규 DB 스키마를 강제함 → schema.sql + DDL 적용 + maintenance 문서 동반 변경. concerns에 이관.

## 확정 필요 (오픈 질문 — orchestrator 경유 사용자 확인)
> 서브에이전트 AskUserQuestion 불가로 권장안 채택을 전제하되, 아래는 사용자 명시 확정이 바람직한 항목. 미응답 시 권장안을 design 가정으로 진행 가능(파괴적 결정 아님).

- **Q1 (결정1)**: 권한 모델 = role + user_permissions join (권장 a) 채택 확인.
- **Q2 (결정3)**: 부트스트랩 = DB seed/수동 승격 (권장) 채택 확인.
- **Q3 (결정4)**: 기존 ADMIN 흡수 = 권한 게이트로 통일(`CONTENT_MODERATE`) 채택 확인. 미채택 시 comment/review 현행 유지.
- **Q4 (결정5)**: 인터셉터 W1 포함 + 보호범위(콘솔 ADMIN / 잼관리 / 포스팅) 확인. (QG-1상 사실상 포함 확정이나 범위 동의 필요)
- **Q5 (결정2)**: 권한 카탈로그 저장 = DB vs 코드 enum 택1.
- **Q6 (결정7)**: 권한 회수 즉시 반영(부여/회수 대칭) 요구 동의 — 보안상 강한 권고.

## Acceptance Criteria 후보 (design이 만족시킬 목표)
- AC-1: ADMIN이 임명한 SUBADMIN에게 `POST_WRITE`를 토글하면, 해당 사용자가 포스팅 작성 액션에 통과한다.
- AC-2: 위 권한을 회수하면, **다음 요청부터** 동일 액션이 403으로 거부된다(즉시·세션 잔류 없음 — 결정7).
- AC-3: SUBADMIN을 강등하면 모든 권한이 회수되고 콘솔 운영진 목록에서 SUBADMIN으로 표시되지 않는다.
- AC-4: 비-ADMIN이 관리자 콘솔 경로에 접근하면 403/리다이렉트로 차단된다(인터셉터 게이트).
- AC-5: 콘솔의 모든 상태변경 요청은 CSRF 토큰 없으면 403.
- AC-6: 부트스트랩 절차로 지정된 최초 ADMIN만 콘솔에 진입 가능(자동 승격 경로 부재).
- AC-7: (결정4 채택 시) comment/review 모더레이션이 ADMIN + `CONTENT_MODERATE` 보유자에게 통과하고, 기존 ADMIN 통과 동작은 회귀 없이 보존(W3-2 테스트 PASS 유지).
- AC-8: 임명/토글/회수/강등이 감사 로그에 대칭 기록된다.
- AC-9: 권한 관련 SQL에 `${}` 동적 치환이 없다.

## design-advisor 전달 단절 목록 (명시 인계)
- G1 부트스트랩, G2 임명, G3 권한토글부여, G4 인터셉터(QG-1), **G5 권한회수(역방향)**, **G6 강등/해임(종료)**, **G7 세션 권한 전파(2차 단절·보안)**.
- 병렬 대칭: 권한 부여/회수 세션 반영 대칭, 임명/해임 감사로그 대칭.
