docs: close nine gaps found by re-reading every open question in full

Cross-checked each question's 남은 미지수, 다음 검증 and 제약 against the plan item by item. Adds B-0 (autoconfiguration actually chosen), B-6 (encryption key rotation) and B-7 (oauth2-proxy cookie secret rotation) as new experiments, plus lock-holder death, rotation-disabled comparison, partial-logout recovery, store latency and the Q4 design checklist. Restores the Redis persistence comparison and records the correct index URL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-04 11:16:59 +09:00
co-authored by Claude Opus 5
parent f1e8c35805
commit 9c3cde457e
18 changed files with 397 additions and 58 deletions
+377 -56
View File
@@ -87,12 +87,14 @@
| **A-6** | 지연 주입 (netem) | 느려지기만 하면? | | A-1 |
| **A-7** | volatile 모드 비교 | 옛 방식(KC≤24)은 뭐가 다른가 | | A-1·A-2·A-4 |
| **A-8** | 롤링 재시작 | 배포하면 로그아웃되나 | | A-0 |
| **B-1** | BFF + Redis 저장소 | Redis vs JDBC 어느 쪽인가 → **Q3** | | 배포 필요 |
| **B-0** | 자동구성 구현체 확인 | **믿는 구현체가 실제로 쓰이나** | | 배포 필요 |
| **B-1** | BFF + Redis 저장소 | Redis vs JDBC 어느 쪽인가 → **Q3** | | B-0 |
| **B-2** | BFF 다중 인스턴스 | 인스턴스 늘리면? → **Q1** | | B-1 |
| **B-3** | Refresh 토큰 경쟁 | 동시 갱신하면? → **Q2** | | B-1 |
| **B-4** | Edge 인가 범위 | 인가를 Edge에 얼마나? → **Q4** | | B-1 |
| **B-5** | Redis 상실 | Redis가 죽으면? | | B-1 |
| **B-6** | Redis 영속화 비교 | AOF/RDB 유무 차이 | | B-5 |
| **B-5** | Redis 상실과 영속화 | 죽으면? 재시작하면 뭐가 남나 | | B-1 |
| **B-6** | 암호화 key 교체 | **key 를 바꾸면 로그인한 사람은?** → Q3 | | B-1 |
| **B-7** | OAuth2-Proxy cookie secret | **secret 을 바꾸면?** → Q1 | | 배포 필요 |
| **C-1** | 다중 앱 SSO | **SSO 붙이면 뭐가 달라지나** | | B-1 |
| **C-2** | 백채널 로그아웃 | 로그아웃이 전 앱에 퍼지나 | | C-1 |
| **D-1** | 백업·복구 리허설 | **복구해본 적 있나** | | A-2 |
@@ -531,29 +533,128 @@ kubectl -n keycloak-lab rollout restart statefulset/keycloak
---
# B층 — 애플리케이션 세션 (Redis)
# B층 — 애플리케이션 세션 (Redis) · 열린 질문 4개
**선행: 배포가 필요하다.** 현재 실험대에 BFF도 Redis도 없다.
**공개된 열린 질문 네 개는 전부 이 층에 있다.** A/C/D층에 대응하는 질문은
등록되어 있지 않다.
> 목록 경로는 `/questions` 가 아니라
> **[`/explore/questions`](https://hyeonworks.com/explore/questions)** 다.
> 2026-09-04 확인 시점에 KeyCloak Patterns 소속 열린 질문은 **4개**.
## 커버리지 대조표 — 질문의 「남은 미지수」와 「다음 검증」 전수
각 질문 페이지의 항목을 하나씩 옮겨 실험 번호를 붙였다.
**빠진 것이 있으면 여기서 드러난다.**
### Q1. 다중 인스턴스 운영
| # | 미지수 / 검증 항목 | 실험 |
|---|---|---|
| 1 | 재시작 뒤 로그인이 유지되는가 | B-2 ② |
| 2 | 인스턴스가 바뀌어도 같은 session 을 찾는가 | B-2 ① |
| 3 | 여러 session 이 authorized client 를 공유·덮어쓰는가 | B-2 ③ |
| 4 | 한쪽에서 로그아웃하면 다른 쪽도 끊기는가 | B-2 ④ |
| 5 | 저장된 refresh token 이 암호화되는가 | B-1 ② |
| 6 | **logout 에서 두 저장소를 모두 정리하는가. 한쪽만 지웠을 때 다음 요청·재로그인에서 무엇이 복원되는가** | **B-2 ⑤** ← 추가 |
| 7 | session 만료와 token 만료가 어긋나면 무엇이 먼저 실패하고 화면에 어떻게 보이는가 | B-1 ③ |
| 8 | **OAuth2-Proxy replica 들이 cookie secret 을 어떻게 공유·교체하는가. 교체 중 로그인해 있던 사람은?** | **B-7** ← 실험 신설 |
| 9 | **Spring Boot 자동구성이 실제로 고른 구현체가 무엇인가** | **B-0** ← 추가 |
| 10 | Resource Server 8081 이 host 에 열려 우회 가능 (제약) | B-2 ⑥ |
### Q2. Refresh Token 경쟁
| # | 미지수 / 검증 항목 | 실험 |
|---|---|---|
| 1 | 동시 갱신 시 각 replica 가 어떻게 동작하는가 | B-3 ①② |
| 2 | 사용자 화면에 로그인 만료로 보이는가 일시 오류로 보이는가 | B-3 ④ |
| 3 | 새 token 을 다시 읽어 재시도하면 성공하는가 | B-3 ③ |
| 4 | 한 곳에서만 갱신할지, 각자 하고 실패는 재시도로 둘지 | B-3 ⑤ 판정 |
| 5 | **lock 을 쓴다면 어디에 두고 얼마나 잡는가. 잡은 채 프로세스가 내려가면 어떻게 푸는가** | **B-3 ⑥** ← 추가 |
| 6 | 갱신 실패를 로그인 만료와 구분해 표시할 수 있는가 | B-3 ④ |
| 7 | **rotation·재사용 0회 전제를 바꿔서도 비교 (제약: 추후 검토)** | **B-3 ⑦** ← 추가 |
### Q3. BFF 저장소 결정
| # | 미지수 / 검증 항목 | 실험 |
|---|---|---|
| 1 | Redis 와 JDBC 중 무엇이 이 접근 패턴에 맞는가 | B-1 전체 |
| 2 | session 과 authorized client 를 같은 store 에 둘지 나눌지 | B-1 ⑥ |
| 3 | **암호화 key 를 어디 두고 어떻게 교체하는가. 교체 중 이전 key 로 저장된 값은 어떻게 읽는가** | **B-6** ← 실험 신설 |
| 4 | session TTL 과 refresh token 수명 중 무엇을 기준으로 맞추는가 | B-1 ③ |
| 5 | 두 store 를 logout 에서 한 번에 지우는가 | B-1 ④ · B-2 ⑤ |
| 6 | sticky session 이 durable store 의 대안인가 보완인가 | B-2 ⑦ |
| 7 | **저장소 지연이 화면 지연으로 드러난다 (제약) — 얼마나?** | **B-1 ⑦** ← 추가 |
### Q4. Edge 인가 범위
| # | 미지수 / 검증 항목 | 실험 |
|---|---|---|
| 1 | 다중 값 role 의 구분자·escaping. 값 안에 구분자가 들어오면 | B-4 ① |
| 2 | 헤더 크기 상한 초과 시 자르는가 거부하는가 | B-4 ② |
| 3 | role 변경이 proxy session 과 downstream 에 언제 반영되는가 | B-4 ③ |
| 4 | upstream 이 헤더 존재만 보는가 값과 service identity 까지 보는가 | B-4 ④ |
| 5 | **internal token 검사를 controller 에서 공통 경계로 옮기기 (제약)** | **B-4 ⑤** ← 추가 |
| 6 | **설계 판단 5문항 — claim 이 계속 느는가 / 즉시 반영이 필요한가 / 정책이 도메인을 아는가 / 헤더가 인가 근거인가 / 서비스별 정책차가 큰가** | **B-4 ⑥** ← 추가 |
---
## 선행 — 배포가 필요하다
현재 실험대에 BFF도 Redis도 없다.
```
지금 B층 실험에 필요한 것
┌──────────────┐ ┌──────────────────────────┐
│ keycloak x2 │ │ keycloak x2 │
│ postgres │ │ postgres │
│ echo (헤더용)+ │ bff x2 ← 새로 배포
│ redis ← 새로 배포
│ │ │ resource-server
└──────────────┘ └──────────────────────────┘
지금 B층에 필요한 것
┌──────────────┐ ┌──────────────────────────┐
│ keycloak x2 │ │ keycloak x2 │
│ postgres │ │ postgres │
│ echo + │ bff x2 신규
관측 스택 │ redis 신규
│ │ │ resource-server ← 신규
│ │ │ oauth2-proxy ← 신규(B-7) │
└──────────────┘ └──────────────────────────┘
```
BFF 구현은 `develop-keycloak-pattern3`에 있으므로 가져온다.
BFF 구현은 `develop-keycloak-pattern3`에 있다.
```bash
git checkout develop-keycloak-pattern3 -- bff/
```
**자원 예산 확인 필요** — 호스트 12GB 중 현재 여유 약 4GB.
Redis(~64Mi) + BFF 2개(~512Mi) 정도면 들어가지만, **A층 실험을 마친 뒤**
배포하는 편이 안전하다.
**자원 예산** — 호스트 12GB 중 여유 약 4GB. Redis(~64Mi) + BFF 2개(~512Mi) +
resource-server(~256Mi)는 들어가지만, **A층을 마친 뒤** 배포하는 편이 안전하다.
---
## B-0. 자동구성이 실제로 고른 구현체 확인
### 답하려는 질문
> Q1 확인한 사실: *"코드에 저장소를 직접 생성하는 Bean이 없기 때문에, 어떤
> 구현체가 실제로 사용되는지는 Spring Boot 자동구성 결과까지 확인해야
> 정확하게 알 수 있다."*
**추측이 아니라 실물을 본다.** A-0에서 "설정 파일이 껍데기라 동작을 재야 했다"와
같은 종류의 확인이다.
### 확인 방법
```bash
# 실제 주입된 빈의 클래스명을 찍는다
curl -s localhost:8080/actuator/beans | jq '.contexts.application.beans
| with_entries(select(.key | test("SessionRepository|AuthorizedClientService|AuthorizedClientRepository")))'
# 또는 기동 시 자동구성 리포트
--debug # ConditionEvaluationReport 에서 Positive/Negative matches
```
| 확인 | 예상 |
|---|---|
| `OAuth2AuthorizedClientService` 구현체 | `InMemoryOAuth2AuthorizedClientService` |
| `SessionRepository` | 없음 (서블릿 컨테이너 in-memory) |
| Redis 전환 후 | `RedisIndexedSessionRepository` 등으로 **바뀌는지 확인** |
**전환 전후를 둘 다 찍는다.** "Redis 붙였다"고 믿는데 자동구성이 안 걸려
in-memory 그대로인 경우가 실제로 흔하다.
---
## B-1. BFF 저장소 결정 → Q3
@@ -567,81 +668,296 @@ Redis(~64Mi) + BFF 2개(~512Mi) 정도면 들어가지만, **A층 실험을 마
└─────────┘ └──────────────┘
│ 두 가지가 들어간다
│ ① Application Session
│ 키: session ID
│ ② OAuth2AuthorizedClient
│ 키: registration + principal
▼ ★ session ID 가 없다
[ Keycloak ]
```
### 확인할 것 — **조회 키가 다르다는 것이 핵심**
**두 상태의 조회 키가 다르다는 것이 이 질문의 핵심**이며, 그래서
"Redis 하나 붙이면 끝"이 성립하지 않는다.
| 상태 | 조회 키 | 결과 |
### 확인할 것
| # | 항목 | 방법 |
|---|---|---|
| Application Session | **session ID** | 브라우저마다 별개 |
| OAuth2AuthorizedClient | **registration + principal name** | **같은 사용자의 여러 브라우저가 공유** |
| ① | 두 인스턴스에서 로그인 유지 · 재시작 복구 | B-2 와 겹침 |
| ② | **refresh token 이 평문으로 남는가** | `redis-cli --scan``GET` 으로 **직접 열어본다** |
| ③ | session TTL ≠ token 만료 — 어긋나게 두고 응답·화면 기록 | TTL 을 짧게/길게 두 번 |
| ④ | logout 뒤 두 store 에 잔여 항목이 없는가 | `--scan` 으로 전수 확인 |
| ⑤ | 저장소를 끊은 상태에서 로그인·API 호출의 오류 형태 | Redis 파드 정지 (B-5) |
| ⑥ | **같은 store 에 둘 때 vs 나눌 때** | 두 구성으로 각각 |
| ⑦ | **저장소 지연이 화면 지연으로 얼마나 드러나는가** | `tc netem` 으로 Redis 앞에 지연 주입 |
5단계 — 로그인 유지·재시작 복구 / **refresh token 이 평문으로 저장되는가** /
**session TTL ≠ token 만료** / logout 후 잔여 항목 / 저장소 끊김 시 오류 형태
**⑦이 Q3의 제약을 직접 재는 것이다** — *"모든 UI 요청이 BFF를 지나기 때문에
저장소 지연이 화면 지연으로 바로 드러나게 된다."* A-6에서 쓴 netem 을
그대로 재사용한다.
**②를 Redis 와 JDBC 양쪽에서 한다.** 어느 쪽이 더 잘 보이는지가 판단 재료다.
---
## B-2. 다중 인스턴스 운영 → Q1
### 구조
```
nginx ip_hash 주석 스티키 유무를 같은 구성에서 비교
nginx ip_hash 주석 on/off ← 스티키 유무를 같은 구성에서 비교
├──▶ bff-0 ──┐
└──▶ bff-1 ──┴──▶ Redis
✗ 우회 경로: 브라우저 ──직접──▶ resource-server:8081
(Q1 제약. NetworkPolicy 로 닫는다)
```
**확인 핵심****authorized client 덮어쓰기.** "Redis만 붙이면 해결"이라는
착각을 깨는 항목이다. 조회 키에 session ID가 없어서 생긴다.
### 확인할 것
| # | 항목 | 예상 / 보는 이유 |
|---|---|---|
| ① | 한쪽에서 로그인 → 다른 인스턴스로 요청 시 200 유지 | 공유 저장소의 기본 |
| ② | 한 인스턴스 재시작 후 같은 session cookie 로 상태 유지 | |
| ③ | **두 브라우저에서 같은 사용자로 로그인 → authorized client 덮어쓰기** | **"Redis만 붙이면 해결"을 깨는 항목.** 조회 키에 session ID 가 없어서 생긴다 |
| ④ | 한쪽 logout 후 다른 쪽 요청 | 같은 authorized client 를 보므로 **같이 끊길 것** |
| ⑤ | **HttpSession 만 지우고 authorized client 를 남겼을 때 / 그 반대일 때** | **재로그인에서 무엇이 복원되는가.** 부분 삭제는 실제 사고 모양이다 |
| ⑥ | resource-server 직접 호출 차단 | 2홉 실험의 NetworkPolicy 패턴 재사용 |
| ⑦ | **ip_hash on/off 비교** | **sticky 가 durable store 의 대안인가 보완인가** (Q3 미지수 6) |
**⑤가 새로 넣은 항목이다.** Q1은 "한쪽만 삭제했을 때 다음 요청이나
재로그인에서 어떤 상태가 복원되는가"를 명시적으로 묻는다.
---
## B-3. Refresh Token 경쟁 → Q2
### 구조
```
access token 만료 직후, 동시에 두 요청
│ │
▼ ▼
bff-0 bff-1
│ │
└────── 같은 refresh token ──────▶ Keycloak
한쪽만 성공, 다른 쪽은?
bff-0 bff-1
└──── 같은 refresh token ──────▶ Keycloak
rotation + 재사용 0회
→ 한쪽만 성공
```
**★ B-1 이후여야 재현된다.** 저장소가 process-local이면 두 replica가 같은
항목을 보지 않아 **경쟁 자체가 성립하지 않는다.**
**★ B-1 이후여야 재현된다.** 저장소가 process-local 이면 두 replica 가 같은
항목을 보지 않아 **경쟁 자체가 성립하지 않는다.** Q2가 제약에 직접 써둔 것이다.
**A-0에서 낙관적 락(`VERSION`)과 `SKIP LOCKED`를 이미 확인했다.**
Keycloak 쪽 동작을 알고 시작하므로, BFF 쪽 재시도 설계에 집중한다.
**A-0에서 Keycloak 쪽 절반은 이미 나왔다** 낙관적 락(`VERSION`)과
`SKIP LOCKED`. 남은 것은 BFF 쪽 재시도·lock 설계다.
판정 기준 — *실패가 사용자에게 노출되면 lock, 노출 안 되면 재시도.*
### 확인할 것
| # | 항목 |
|---|---|
| ① | 만료 직후 동시 요청 — 이긴 쪽 응답 |
| ② | 진 쪽 응답 (`invalid_grant`? 다른 코드?) |
| ③ | 진 쪽이 저장소에서 새 token 을 다시 읽어 **재시도하면 성공하는가** |
| ④ | **진 쪽 사용자 화면** — 로그인 만료로 보이는가 일시 오류로 보이는가. **구분해 표시할 수 있는가** |
| ⑤ | lock 있는 구성 / 없는 구성을 같은 입력으로 — 실패율과 지연 |
| ⑥ | **lock 을 잡은 채 프로세스가 죽으면 어떻게 풀리는가** — TTL? 수동? 다음 요청이 막히는 시간은? |
| ⑦ | **rotation·재사용 0회를 끈 구성과 비교** |
**판정 기준***실패가 사용자에게 노출되면 lock, 노출되지 않으면 재시도.*
**⑥은 lock 을 고를 경우의 진짜 비용이다.** Redis lock 을 잡은 파드가 OOM 으로
죽으면 TTL 만료까지 그 사용자는 갱신이 막힌다. 이걸 재보지 않고 lock 을
고르면 안 된다.
**⑦은 Q2 제약이 "추후 바꿔서 검토"라고 남긴 것**이다. 전제를 바꿨을 때
경쟁이 사라지는지, 대신 무엇을 잃는지(탈취된 token 재사용 탐지)를 본다.
---
## B-4. Edge 인가 범위 → Q4
### 구조
```
브라우저 ──▶ nginx ──▶ [auth_request] ──▶ BFF
│◀── X-Auth-Request-Roles ──┘
브라우저 ──▶ nginx ──auth_request──▶ oauth2-proxy / BFF
│ │
│◀── X-Auth-Request-User ──┘
│ X-Auth-Request-Email
│ X-Auth-Request-Roles ← 새로 추가할 것
└──▶ Resource Server ← 헤더만 믿는가?
▼ allowlist + 항상 덮어쓰기
resource-server (upstream)
└─ JWT 를 입력으로 받지 않는다
→ 헤더 값을 검증할 방법이 없다
```
**2홉 실험의 교훈이 여기서 결정적이다** — 헤더 위조가 통하면
그것은 쿠키 속성이 아니라 **신원 위조**다. NetworkPolicy 패턴을 재사용한다.
**2홉 실험의 결론이 여기서 결정적이다** — 헤더 위조가 통하면 그것은 쿠키
속성이 아니라 **신원 위조**다. NetworkPolicy 패턴을 그대로 재사용한다.
확인 — 다중 값 구분자·escaping / 헤더 크기 상한 / role 변경 반영 시점 /
upstream 이 값과 service identity 까지 보는가
### 확인할 것
## B-5 · B-6. Redis 상실과 영속화
| # | 항목 |
|---|---|
| ① | 다중 값 role 의 **구분자와 escaping**. **값 안에 그 구분자가 들어오면** |
| ② | **헤더 크기 상한** 초과 시 — proxy 가 자르는가 요청 자체가 거부되는가 |
| ③ | role 을 바꾼 뒤 **몇 번째 요청부터 반영**되는가. proxy session 과 downstream 각각 |
| ④ | upstream 이 헤더 **존재만** 보는가, **값과 service identity** 까지 보는가 |
| ⑤ | **internal token 검증을 controller 에서 공통 경계로 옮긴다** (Filter / Interceptor / Security) |
| ⑥ | **설계 판단 5문항에 답한다** |
**⑤는 코드 변경이며 Q4의 제약이 "헤더를 늘리기 전에" 하라고 못박은 것이다.**
현재 `permitAll` 아래 특정 컨트롤러에만 검증이 있어, **같은 경로에 엔드포인트를
추가하면 검증이 자동으로 안 걸린다.** 헤더를 늘리기 전에 이걸 먼저 옮긴다.
### ⑥ 설계 판단 5문항 — Q4가 정한 결론 규칙
| # | 질문 | 그렇다면 |
|---|---|---|
| 1 | 전달할 claim 이 계속 늘어나는가 | |
| 2 | role·tenant 변경이 **즉시 반영**돼야 하는가 | **BFF 구조로** |
| 3 | 정책이 애플리케이션 **도메인을 알아야** 하는가 | **BFF 구조로** |
| 4 | 헤더 값이 **인가 판단의 근거**가 되는가 | **BFF 구조로** |
| 5 | **서비스별 정책 차이**가 커지는가 | **BFF 구조로** |
> *2번부터 5번 중 하나라도 그렇다면 헤더를 늘리기보다 BFF 구조로 구성하자.*
**실험이 이 5문항에 데이터를 대는 것이 목적이다.** ①~③의 측정 결과가
1·2번의 답을 정한다.
---
## B-5. Redis 상실과 영속화
**A-2·A-3의 Redis 판본.** 같은 질문을 다른 저장소에 던진다.
### ① 상실
```
B-5 Redis 파드 정지 → 세션 전멸? 로그인 화면?
B-6 appendonly yes/no 비교 → 재시작 후 무엇이 남는가
bff-0 ──┐
├──▶ Redis ✗ 파드 정지
bff-1 ──┘
```
**A-2·A-3의 Redis 판본이다.** 같은 질문을 다른 저장소에 던진다.
| 확인 | 예상 |
|---|---|
| 로그인한 사용자의 다음 요청 | 세션 소멸 → 로그인 화면 |
| **오류 형태** | 500? 302 로그인 리다이렉트? — Q3 검증 5번 |
| 복구 후 자동 재연결 | BFF 재시작이 필요한가 |
| 브라우저 쿠키는 남아 있다 | **쿠키는 있는데 세션이 없는 상태** — 어떻게 보이나 |
### ② 영속화 — 재시작하면 무엇이 남는가
Redis 는 기본이 **메모리**다. 껐다 켜면 사라진다. 두 가지 영속화 방식이 있다.
| 방식 | 무엇 | 잃는 것 |
|---|---|---|
| **RDB** | 주기적 스냅샷 | 마지막 스냅샷 이후 전부 |
| **AOF** (`appendonly yes`) | 모든 쓰기를 로그로 | `appendfsync` 설정만큼 |
| 둘 다 없음 (기본) | 없음 | **전부** |
```bash
# 세 구성을 같은 입력으로 비교한다
redis-server --appendonly no --save "" # 영속화 없음
redis-server --save "60 1" # RDB
redis-server --appendonly yes # AOF
```
| 확인 | |
|---|---|
| 각 구성에서 **정상 재시작** 후 세션 생존율 | |
| 각 구성에서 **강제 종료(SIGKILL)** 후 생존율 | **A-3 과 같은 질문** |
| `appendfsync everysec` 의 실제 손실 폭 | Keycloak 의 `synchronous_commit OFF`**같은 모양의 트레이드오프** |
**A-3에서 PostgreSQL 로 한 것을 Redis 로 반복하는 것이다.**
저장소가 달라도 "내구성을 어디까지 포기하는가"라는 질문은 같다.
---
## B-6. 암호화 key 보관과 교체 → Q3 미지수 3
### 답하려는 질문
> *"암호화 key 를 어디에 두고 어떻게 교체하게 되는가. 교체하는 동안 이전
> key 로 저장된 값은 어떻게 읽는가."*
### 구조
```
교체 전 교체 중 교체 후
┌────────┐ ┌────────┐ ┌────────┐
│ key A │ │ key B │ 쓰기 │ key B │
└────────┘ │ key A │ 읽기(구값) └────────┘
│ └────────┘ │
▼ │ ▼
[ Redis: A로 암호화된 값 ] │ [ Redis: B로 재암호화된 값 ]
이 구간에 로그인해 있던 사람은?
```
| 확인 | |
|---|---|
| key 를 어디에 두는가 | 환경변수 / k8s Secret / 외부 KMS — **D-3 과 연결** |
| 교체 중 **이전 key 로 저장된 값을 읽을 수 있는가** | 복수 key 를 동시에 들 수 있는 구조인가 |
| 교체 중 로그인해 있던 사람 | **강제 로그아웃되는가, 무중단인가** |
| 재암호화 시점 | 읽을 때마다? 배치로 한 번에? |
**Q3은 "후보를 고른 뒤에 따로 설계한다"고 미뤄둔 항목**이지만, 실험대에서는
같이 재볼 수 있다. B-7(cookie secret 교체)과 **같은 모양의 문제**다.
---
## B-7. OAuth2-Proxy cookie secret 공유와 교체 → Q1 미지수 7
### 답하려는 질문
> *"OAuth2-Proxy 구조의 replica 들이 같은 cookie secret 을 어떻게 공유하고
> 교체하게 되는가. 교체하는 동안 로그인해 있던 사람은 어떻게 되는가."*
**계획서에 아예 없던 실험이다.** Q1을 정독하고 나서야 드러났다.
### 왜 다른 문제인가
```
BFF / Mediator 서버에 세션을 둔다 → 공유 저장소 문제
OAuth2-Proxy 쿠키에 세션을 담는다 → 서명·암호화 key 문제
(서버 저장소가 없다)
```
**OAuth2-Proxy 는 서버 저장소를 안 쓴다.** 로그인 상태를 브라우저 쿠키에
담고, 그 쿠키를 **cookie secret 으로 서명·암호화**한다. 현재 설정의 쿠키
유효 시간은 **1시간**.
| 결과 | |
|---|---|
| replica 간 공유 문제가 **없다** | 쿠키에 다 들어 있으므로 |
| 대신 **모든 replica 가 같은 secret 을 가져야 한다** | 다르면 서로의 쿠키를 못 읽는다 |
| secret 을 바꾸면 | **기존 쿠키가 전부 무효** → 전원 재로그인 |
### 구조
```
┌──────────────────┐ ┌──────────────────┐
│ oauth2-proxy-0 │ │ oauth2-proxy-1 │
│ secret = X │ │ secret = X │ ← 같아야 한다
└────────┬─────────┘ └────────┬─────────┘
│ │
└──── 브라우저 쿠키 ─────────┘
(서버에 아무것도 없다)
secret 을 Y 로 바꾸면? → X 로 서명된 쿠키를 아무도 못 읽는다
```
### 확인할 것
| # | 항목 |
|---|---|
| ① | replica 간 secret 이 **다를 때** 무슨 일이 생기는가 — 요청마다 다른 결과? |
| ② | secret 교체 시 로그인해 있던 사람 — **전원 로그아웃인가** |
| ③ | **무중단 교체가 가능한가** — 구 secret 을 읽기 전용으로 병행할 수 있는가 |
| ④ | 쿠키 TTL 1시간과 실제 세션 수명의 관계 |
| ⑤ | secret 을 k8s Secret 으로 두면 회전 시 파드 재시작이 필요한가 |
**②·③이 B-6(암호화 key 교체)과 같은 모양**이다. 둘을 나란히 하면
"key 회전"이라는 한 주제의 두 사례가 된다.
---
@@ -753,12 +1069,17 @@ nginx reload 중 **진행 중이던 요청**은 어떻게 되는가.
└─▶ A-7 (volatile 비교) ← A-1·A-2·A-4·A-8 을 다 한 뒤 전부 재실행
[ BFF + Redis 배포 ]
[ BFF + Redis + resource-server 배포 ]
├─▶ B-1 (저장소) ──┬─▶ B-2 (다중 인스턴스)
├─▶ B-3 (refresh 경쟁)
─▶ B-4 (edge 인가)
─▶ B-5 (Redis 상실) ─▶ B-6 (영속화)
├─▶ B-0 (자동구성 확인) ← 저장소를 바꾸기 전과 후 둘 다 찍는다
└─▶ B-1 (저장소 결정 · Q3) ──┬─▶ B-2 (다중 인스턴스 · Q1)
─▶ B-3 (refresh 경쟁 · Q2)
│ ├─▶ B-4 (edge 인가 · Q4)
│ ├─▶ B-5 (Redis 상실·영속화)
│ └─▶ B-6 (암호화 key 교체)
├─▶ B-7 (oauth2-proxy cookie secret) ← B-6 과 짝. "key 회전" 한 주제
└─▶ C-1 (SSO) ─▶ C-2 (백채널 로그아웃)
+10 -2
View File
@@ -1,7 +1,15 @@
# 열린 질문 커버리지 — 이 실험대로 답할 수 있는가
공개 기록(`hyeonworks.com/questions`)에 등록된 KeyCloak Patterns 열린 질문
네 개를, 이 실험대가 실제로 검증할 수 있는지 대조한 결과.
공개 기록에 등록된 KeyCloak Patterns 열린 질문 네 개를, 이 실험대가 실제로
검증할 수 있는지 대조한 결과.
> **목록 경로는 [`/explore/questions`](https://hyeonworks.com/explore/questions)**
> 다. `/questions` 는 404 이고 개별 문서만 `/questions/<slug>` 로 열린다.
>
> **2026-09-04 재확인** — Playwright 로 네 문서를 전문 재독하고
> 「남은 미지수」·「다음 검증」·「제약」을 항목 단위로 대조한 결과
> **계획에 빠진 항목 9개**를 찾아 보강했다. 항목별 실험 번호 대조표는
> [`experiment-plan.md`](experiment-plan.md) B층 머리에 있다.
**결론 — 네 개 모두 이 실험대에서 재현 가능하다. 다만 로드맵에 빠진 항목이
있고, 순서가 한 곳 뒤집혀 있다.**