- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중 17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로 tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8 - 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다 - 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference) - 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다. 삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7.0 KiB
id, kind, slug, title, topic, topicName, project, status, studio, sourceRevision, source
| id | kind | slug | title | topic | topicName | project | status | studio | sourceRevision | source | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| REFERENCE | runtime-verification-safe-order | 패턴 검증을 실제로 돌릴 때의 안전한 순서 | oauth-oidc-auth-boundary | OAuth/OIDC 인증 경계 | KeyCloak Patterns | 게시 전 | keycloak-patterns-lab@2026-08 |
|
패턴 검증을 실제로 돌릴 때의 안전한 순서
네 패턴의 경계가 실제로 성립하는지 보려면 스택을 띄우고 브라우저로 로그인까지 해 봐야 하는데, 패턴별 검증 절차는 스택을 다시 만들기 전에 볼륨을 초기화한다. 보존해야 할 realm이나 데이터베이스가 같은 Compose 프로젝트에 있으면 검증을 돌리는 사이에 그 데이터를 잃는다.
대상 환경이 일회용인지 확정한 다음에 첫 검사를 돌리고, 마지막에는 테스트용 스택을 내리고 원래 환경을 복구해 다시 확인하는 것으로 닫는다. 중간에 하나라도 어긋나면 나머지를 계속 돌려 전체 통과를 만들지 않는다.
관계
- Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유 이 절차로 확인하는 경계 가운데 하나이고, 위조 헤더를 보내 보는 것이 5번 규칙의 부정 입력에 해당한다.
- OAuth/OIDC 인증 패턴 선택 기준 어느 구조를 고를지 정하는 기준과, 고른 구조가 코드에서 실제로 성립하는지 확인하는 절차는 같이 쓴다.
목적
이 절차가 막으려는 것은 두 가지다. 하나는 지워서는 안 될 데이터를 지우는 일이다. 검증 절차에 볼륨 초기화가 들어 있으므로, 실행 순서를 정하기 전에 대상이 지워도 되는 환경인지부터 확정해야 한다.
다른 하나는 앞 단계가 어긋났는데도 나머지를 계속 돌려 마지막에 전체 통과를 남기는 일이다.
규칙
1. 대상이 일회용 환경인지 확정한 뒤에 시작한다
보존해야 할 Keycloak realm과 사용자, PostgreSQL 데이터가 같은 Compose 프로젝트에 있으면 안 된다. 볼륨의 소유와 용도를 확정하지 못했다면 검증을 미룬다.
지금 쓰는 볼륨이 계속 필요하다면 별도 프로젝트로 복제하거나 백업이나 스냅숏을 만들어 둔 뒤에 진행한다.
2. 비밀값은 환경으로 주입하고 출력에 남기지 않는다
secret과 테스트 사용자 비밀번호는 환경변수로 주입하고 출력 로그에 값을 찍지 않는다. 커맨드라인 인자나 브라우저 출력, 버전 관리되는 파일에 값이 들어갔다면 그때 검증을 멈춘다.
3. 한 번에 한 패턴만 올린다
한 번에 한 패턴만 대상으로 고르고, 여러 패턴 스택을 같은 포트에 동시에 올리지 않는다. 포트가 겹치면 어느 스택이 응답한 것인지 구분되지 않아서 관측값이 어디에서 나온 것인지 알 수 없다.
4. 정적 검사를 먼저 돌리고 실패하면 다음으로 가지 않는다
정적 realm 검증과 단위 테스트를 먼저 실행하고, 여기서 client 종류나 redirect URI, audience mapper, controller 계약이 어긋나면 브라우저 E2E로 진행하지 않는다.
두 검사가 통과하면 일회용 볼륨이 맞는지 다시 확인한 뒤에 패턴 스택을 빌드한다. 빌드한 뒤에도 순서는 같아서, health check가 안정되지 않으면 로그인 테스트를 시작하지 않는다.
5. 부정 입력까지 관측한 뒤에 경계가 유지된다고 판단한다
브라우저 E2E에서는 엔드포인트별 상태 코드와 쿠키 속성, 브라우저가 실제로 보낸 네트워크 요청, 응답 payload의 키를 함께 확인한다. 리프레시 토큰이 브라우저에 노출된 채로도 화면은 열리고, 브라우저에 토큰이 없다고 알려 주는 응답의 숫자는 서버가 적어 넣은 값이라 네트워크와 저장소를 따로 봐야 확인된다.
그다음에 패턴마다 다른 부정 입력을 넣는다. 위조 헤더를 보냈을 때 프록시가 값을 덮어쓰는지, 잘못된 issuer와 audience를 가진 토큰이 거부되는지, CSRF(Cross-Site Request Forgery, 교차 사이트 요청 위조) 헤더가 없는 요청이 막히는지를 각각 관측하고,
위조 헤더를 넣은 요청이 200으로 돌아오는 것은 로그인 세션 자체가 유효하기 때문이다. 여기서 볼 값은 상태 코드가 아니라 응답에 남은 사용자 이름이고, 그 이름이 위조한 값이 아니라 원래 사용자면 덮어쓰기가 작동한 것이다.
여기 넣는 부정 입력에 서명이 깨진 토큰과 만료된 토큰은 들어 있지 않다. 단위 테스트에서 합성 JWT를 주입해 컨트롤러가 200을 주는 것을 본 것도 실제 서명 검증과 issuer 검증을 통과했다는 증거는 아니다.
6. 중단 조건에 걸리면 멈추고 그 지점을 보존한다
다음 넷 중 하나라도 성립하면 나머지 단계를 실행하지 않는다.
- 대상 볼륨의 소유와 용도를 확정할 수 없음
- redirect URI나 host가 테스트 픽스처가 아니라 실제 환경을 가리킴
- secret이 커맨드라인이나 브라우저 출력, 버전 관리 파일에 노출됨
- health check와 기대한 401·403, 헤더 덮어쓰기 중 하나라도 불일치
멈춘 뒤에 가장 먼저 하는 일은 실패한 홉의 실제 입력과 출력을 보존하는 것이다. 로그만 남기고 스택을 내리면 그 상태를 다시 만들 수 없다. 보존이 끝나면 설정과 네트워크, 애플리케이션 가운데 어느 경계가 깨졌는지 나눠서 진단한다.
7. 끝나면 원래 환경으로 되돌리고 다시 확인한다
검증이 끝나면 테스트용 스택을 내린다. 백업이 필요했던 환경이라면 원래 프로젝트와 볼륨을 복구한 뒤 health와 로그인이 되는지 다시 확인한다.
적용 조건
- 검증 절차에 볼륨 초기화나 스키마 재생성처럼 되돌릴 수 없는 단계가 들어 있을 때
- 실제 브라우저와 컨테이너를 띄워야 확인되는 경계를 다룰 때
- 여러 구성을 같은 호스트에서 번갈아 검증할 때
예외
- 일회용 환경이 아니면 이 절차를 그대로 실행하지 않는다. 별도 프로젝트로 복제한 뒤 그쪽에서 돌린다.
- 정적 검사만으로 판정이 끝나는 항목은 스택을 띄우지 않는다. 4번 규칙의 앞 절반만 적용한다.
예시
- 볼륨 소유자를 확정하지 못한 경우: 시작하지 않고 별도 프로젝트로 복제한다
- 정적 realm 검증에서 redirect URI 불일치: 브라우저 E2E로 진행하지 않고 설정을 고친다
- 위조 헤더 응답이 200인데 사용자 이름이 원래 사용자: 통과로 기록한다
- 위조 헤더 응답의 사용자 이름이 위조한 값: 중단하고 그 요청의 입력과 출력을 보존한다
- 검증 종료 후: 스택을 내리고 백업한 볼륨을 복구한 뒤 로그인을 다시 확인한다