Files
keycloak-pattern/docs/open-questions-coverage.md
T
DongHyeonkaandClaude Opus 5 9c3cde457e 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>
2026-09-04 11:16:59 +09:00

12 KiB

열린 질문 커버리지 — 이 실험대로 답할 수 있는가

공개 기록에 등록된 KeyCloak Patterns 열린 질문 네 개를, 이 실험대가 실제로 검증할 수 있는지 대조한 결과.

목록 경로는 /explore/questions 다. /questions 는 404 이고 개별 문서만 /questions/<slug> 로 열린다.

2026-09-04 재확인 — Playwright 로 네 문서를 전문 재독하고 「남은 미지수」·「다음 검증」·「제약」을 항목 단위로 대조한 결과 계획에 빠진 항목 9개를 찾아 보강했다. 항목별 실험 번호 대조표는 experiment-plan.md B층 머리에 있다.

결론 — 네 개 모두 이 실험대에서 재현 가능하다. 다만 로드맵에 빠진 항목이 있고, 순서가 한 곳 뒤집혀 있다.

# 질문 게시 로드맵 커버
Q1 서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가 2026.08.29 부분
Q2 Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가 2026.08.26 부분
Q3 BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가 2026.08.30 부분
Q4 Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가 2026.08.31 없음

발견한 구조적 문제

1. 순서가 뒤집혀 있다

Q2가 명시한다.

이 경쟁은 저장소를 공유한 뒤에야 재현되기 때문에 저장소 결정을 하고 나서 해당 문제를 이어서 풀어보자.

Q3(저장소 결정) → Q2(경쟁 재현) 순서다. 그런데 로드맵은 refresh-token-concurrencyredis-app-session-store보다 에 두었다.

Q2를 먼저 시도하면 재현 자체가 불가능하다. 저장소가 process-local이면 두 replica가 같은 refresh token 항목을 보지 않기 때문이다.

→ 로드맵 순서를 교정한다.

2. Session과 Authorized Client는 조회 키가 다르다

Q3의 핵심이며 로드맵에 이 구분이 없었다.

상태 조회 키 저장 위치(현재)
Application Session session ID 서블릿 컨테이너 in-memory
OAuth2AuthorizedClient client registration 이름 + principal name 자동구성 in-memory

session ID가 조회 키에 없다. 그래서 같은 사용자가 두 브라우저에서 로그인하면 동일한 authorized client 항목을 공유한다.

Q1의 제약이 이를 그대로 지적한다.

여러 인스턴스가 같은 세션을 사용할 수 있도록 Session Store를 공유 저장소로 변경하는 것만으로는 충분하지 않다.

→ 실험을 "Redis 도입" 하나로 뭉뚱그리면 안 된다. 두 저장소를 각각 설계하고 각각 검증해야 한다.

3. 이미 해결한 문제가 질문에도 있다

Q1의 제약:

Resource Server의 8081이 host에도 열려 있어서 모든 client가 BFF만 거치도록 network에서 강제된 상태가 아니다.

이는 2홉 헤더 실험에서 마주친 프록시 우회 경로와 같은 문제이며, NetworkPolicy로 닫는 방법을 이미 확립했다 (two-hop-proxy-header-contract.md 11절).

→ Q1에 답할 때 그 패턴을 그대로 재사용한다.


Q1. 다중 인스턴스 운영

질문이 요구하는 검증 5단계

# 검증 실험대 가능 로드맵
1 한쪽에서 로그인 후 다른 인스턴스로 요청 시 200 유지 가능 없음
2 한 인스턴스 재시작 후 같은 session cookie로 상태 유지 가능 없음
3 같은 사용자 두 브라우저 → authorized client 덮어쓰는가 가능 없음
4 한쪽 logout 후 다른 쪽 요청 가능 부분 (백채널 로그아웃)
5 session 만료 ≠ token 만료 각 경우의 응답과 화면 가능 없음

실험대 준비 상태 — BFF를 2 replica로 띄우면 전부 재현된다. 호스트 nginx의 ip_hash 주석을 켜고 끄면 스티키 유무 비교까지 같은 구성에서 된다.

추가로 필요한 것

  • BFF 이미지 (아직 bff/ 디렉터리에 소스 없음)
  • 로그아웃 전파를 관찰할 두 번째 앱 (app2.hyeonworks.com 이름은 확보)

3번이 특히 중요하다. "Redis만 붙이면 해결"이라는 착각을 깨는 항목이고, 조회 키가 다르다는 사실의 실증이다.


Q2. Refresh Token Rotation 경쟁

질문이 요구하는 검증 5단계

# 검증 실험대 가능 로드맵
1 replica 두 대에서 access token 만료 직후 동시 요청 가능 있음
2 이긴 쪽/지는 쪽 응답 각각 기록 가능 부분
3 지는 쪽이 저장된 새 token으로 재시도해 성공하는가 가능 없음
4 지는 쪽 사용자 화면에 무엇이 보이는가 가능 없음
5 lock 유무를 같은 입력으로 비교 (실패율·지연) 가능 없음

5번이 결론을 내는 기준이다.

실패가 사용자에게 노출되면 lock을 고르고, 노출되지 않으면 재시도로 둔다.

로드맵에 없던 항목인데, 이것이 없으면 질문에 답할 수 없다.

제약을 지켜야 한다

  • rotation + 재사용 0회는 전제로 고정한다. 바꾸지 않고 답한다
  • 이미 발급된 access token은 만료 전까지 통하므로 재현은 access token 만료 직후에 맞춰 실행한다. 그렇지 않으면 실패가 화면에 보이지 않는다

선행 조건 — Q3의 저장소 공유가 먼저다.


Q3. BFF 저장소 결정

질문이 요구하는 검증 5단계

# 검증 실험대 가능 로드맵
1 인스턴스 두 대에서 로그인 유지와 재시작 복구 가능 부분
2 저장소를 열어 refresh token이 평문인가 가능 없음
3 session TTL ≠ token 만료 그 순간의 응답과 화면 가능 없음
4 logout 뒤 두 store에 잔여 항목이 없는가 가능 부분
5 저장소를 끊은 상태에서 로그인·API 호출 오류 가능 있음

로드맵에 없던 큰 항목 — 후보 비교

질문은 "Redis로 간다"가 아니라 "Redis와 JDBC 중 무엇이 이 접근 패턴에 맞는가" 를 묻는다.

요청마다 읽는 값과 가끔 읽는 값이 섞여 있다.

이 실험대에는 PostgreSQL이 이미 있으므로 JDBC 후보를 같은 조건에서 비교할 수 있다. Redis만 붙이면 질문의 절반만 답하는 셈이다.

2번(평문 확인)의 실행 방법

kubectl -n <ns> exec -it deploy/redis -- redis-cli --scan --pattern 'spring:session:*'
kubectl -n <ns> exec -it deploy/redis -- redis-cli GET <key>

저장소를 직접 열어 refresh token이 그대로 읽히는지 본다. 읽힌다면 암호화 설계가 필요하고, 그 key 교체 절차는 별도 과제다.


Q4. Edge 인가 범위 — 로드맵에 전혀 없다

이 축을 A층(Keycloak)·B층(앱 세션) 중심으로 잡으면서 AP4의 인가 범위 질문을 빠뜨렸다.

질문이 요구하는 검증

검증 실험대 가능
role을 헤더에 담고 다중 값 구분자·escaping 확인 가능
헤더 크기 상한 초과 시 proxy가 자르는가 요청이 거부되는가 가능
role 변경 후 몇 번째 요청부터 반영되는가 가능
upstream이 헤더 존재만 보는가 값과 service identity까지 보는가 가능

이 실험대에서 특히 잘 맞는 이유

nginx의 헤더 처리 특성을 이미 실측했다. 질문이 지적한

Nginx는 client가 보낸 동명 헤더를 merge하지 않고 덮어쓴다.

는 2홉 헤더 실험에서 proxy_set_header X-Forwarded-For $remote_addr로 확인한 그 동작이다. X-Auth-Request-*도 같은 규칙을 따르는지를 같은 방법으로 검증할 수 있다.

그리고 질문의 제약

internal token 검사가 controller 한 곳에만 있다. 헤더를 늘리기 전에 이 검사를 공통 경계로 옮겨야 된다.

는 코드 변경이므로 backend/에서 진행한다.


교정된 실험 순서

기존 로드맵의 순서를 질문의 의존 관계에 맞춰 조정한다.

✅  환경 구축
✅  2홉 프록시 헤더 계약
──────────────────────────────────────────────────────────
1.  Keycloak 멀티노드 클러스터 형성          (선행 인프라)
2.  persistent vs volatile 세션              (A층)
3.  BFF 저장소 결정          → Q3           ★ Q2 의 선행 조건
4.  다중 인스턴스 운영       → Q1
5.  Refresh Token 경쟁       → Q2           ★ 3 이후여야 재현됨
6.  Edge 인가 범위           → Q4           ← 새로 추가
7.  장애 주입과 복구                         (전 항목 공통)

바뀐 점

  • refresh-token-concurrencyredis-app-session-store 뒤로 이동
  • 저장소 결정이 Redis 도입이 아니라 Redis vs JDBC 비교로 확장
  • Edge 인가 범위(Q4) 신규 추가

브랜치 매핑

실험 브랜치 상태
멀티노드 클러스터 feature/keycloak-multinode-cluster-jdbc-ping 존재
persistent vs volatile feature/keycloak-persistent-vs-volatile-sessions 존재
BFF 저장소 (Q3) feature/keycloak-redis-app-session-store 존재 — 범위 확장 필요
다중 인스턴스 (Q1) 없음
refresh 경쟁 (Q2) feature/keycloak-refresh-token-concurrency 존재
Edge 인가 (Q4) 없음
장애 주입 feature/keycloak-failure-injection-recovery 존재

두 개를 새로 만들어야 한다.

git checkout develop-keycloak-session-store
git checkout -b feature/keycloak-multi-instance-session-operation
git checkout -b feature/keycloak-edge-authorization-scope

공통 선행 조건 — BFF 구현은 이미 있다

세 질문(Q1·Q2·Q3)이 모두 BFF를 2 replica로 띄우는 것을 전제한다. develop-keycloak-session-storebff/에는 빌드 산출물만 있지만, develop-keycloak-pattern3에 구현이 완성되어 있다.

bff/Dockerfile
bff/pom.xml
bff/src/main/java/com/example/keycloakpattern/bff/
  ├ BffApplication.java
  ├ BffController.java
  ├ CsrfController.java
  ├ SecurityConfig.java
  └ SpaCsrfTokenRequestHandler.java
bff/src/main/resources/application.yml
bff/src/main/resources/static/{index.html,app.js}
bff/src/test/java/.../BffControllerTest.java

→ 새로 구현할 필요가 없다. AP3 브랜치에서 이 실험대로 가져온다.

git checkout develop-keycloak-session-store
git checkout develop-keycloak-pattern3 -- bff/

가져온 뒤 확인할 것 — 질문들이 지목한 부분이 코드에 그대로 있는지.

확인 어디를 볼 것인가
Session 저장소가 in-memory 자동구성인가 SecurityConfig.java, application.yml에 Spring Session 설정 부재
OAuth2AuthorizedClientService가 in-memory인가 Bean 정의 부재 → 자동구성 결과 확인 필요
authorized client 조회에 session ID가 없는가 Spring Security 기본 계약

Q3가 "어떤 구현체가 실제로 쓰이는지는 자동구성 결과까지 확인해야 정확히 알 수 있다"고 남긴 미지수를, 기동 후 Bean을 실제로 조회해서 확정할 수 있다.

kubectl -n <ns> exec deploy/bff -- \
  curl -s localhost:8082/actuator/beans | grep -i authorizedClientService

참고

문서 관계
session-store-lab-roadmap.md 이 문서가 그 순서를 교정한다
two-hop-proxy-header-contract.md Q1의 우회 경로 제약, Q4의 헤더 덮어쓰기 근거
session-lab-operations.md 실행 도구와 명령
four-pattern-tradeoff-matrix.md Q4가 되돌아가는 선택지(BFF)의 비교표