Files
DongHyeonkaandClaude Opus 5 ab59130196 chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다.
사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다.

대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 —
final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인
final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다.
삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다.

그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개,
writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물,
scripts/check-ssot-facts.py 와 그 시험이 들어 있다.

이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 11:02:02 +09:00

94 lines
7.1 KiB
Markdown

---
id: 036563a1-3a43-4931-a017-0e693b591e89
kind: REFERENCE
slug: runtime-verification-safe-order
title: 패턴 검증을 실제로 돌릴 때의 안전한 순서
topic: oauth-oidc-auth-boundary
topicName: OAuth/OIDC 인증 경계
project: KeyCloak Patterns
status: 게시 전
studio: "https://hyeonworks.com/studio/documents/036563a1-3a43-4931-a017-0e693b591e89/edit"
sourceRevision: keycloak-patterns-lab@2026-08
source:
- final/document.md#결정이-지켜지는지-확인하는-방법-실제-runtime-검증을-수행할-때의-안전한-순서
---
# 패턴 검증을 실제로 돌릴 때의 안전한 순서
네 패턴의 경계가 실제로 성립하는지 보려면 스택을 띄우고 브라우저로 로그인까지 해 봐야 하는데, 패턴별 검증 절차는 스택을 다시 만들기 전에 볼륨을 초기화한다. 보존해야 할 realm이나 데이터베이스가 같은 Compose 프로젝트에 있으면 검증을 돌리는 사이에 그 데이터를 잃는다.
## 관계
- **Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유**
이 절차로 확인하는 경계 가운데 하나다. 위조 헤더를 보내 보는 것이 5번 규칙의 부정 입력이다.
- **OAuth/OIDC 인증 패턴 선택 기준**
어느 구조를 고를지는 그 기준으로 정하고, 고른 구조가 실제로 그렇게 동작하는지는 이 절차로 확인한다.
## 목적
이 절차는 두 가지를 막는다. 보존해야 할 realm과 데이터베이스를 검증 도중에 지우는 일과, 앞 단계가 어긋났는데도 나머지를 계속 돌려 마지막에 전체 통과를 남기는 일이다.
## 규칙
### 1. 대상이 일회용 환경인지 확정한 뒤에 시작한다
보존해야 할 Keycloak realm과 사용자, PostgreSQL 데이터가 같은 Compose 프로젝트에 있으면 안 된다. 볼륨의 소유와 용도를 확정하지 못했다면 검증을 미룬다. 지금 쓰는 볼륨이 계속 필요하다면 별도 프로젝트로 복제하거나, 백업이나 스냅숏을 만들어 둔 뒤에 진행한다.
### 2. 비밀값은 환경변수로 주입하고 출력에 남기지 않는다
secret과 테스트 사용자 비밀번호는 환경변수로 주입하고 출력 로그에 값을 찍지 않는다. 커맨드라인 인자나 브라우저 출력, 버전 관리되는 파일에 값이 들어갔다면 검증을 멈춘다.
### 3. 한 번에 한 패턴만 올린다
한 번에 한 패턴만 대상으로 고르고, 여러 패턴 스택을 같은 포트에 동시에 올리지 않는다. 검증은 고른 경계의 입력과 출력에 직접 연결되어야 하므로, 관측한 상태 코드와 쿠키가 어느 패턴에서 나온 값인지 분명해야 한다.
### 4. 정적 검사를 먼저 돌리고 실패하면 다음으로 가지 않는다
정적 realm 검증과 단위 테스트를 먼저 실행하고, 여기서 클라이언트 종류나 redirect URI, audience mapper, 컨트롤러 계약이 어긋나면 브라우저 E2E로 진행하지 않는다.
두 검사가 통과하면 일회용 볼륨이 맞는지 다시 확인한 뒤에 패턴 스택을 빌드한다. 빌드한 뒤에는 health check가 안정될 때까지 로그인 테스트를 시작하지 않는다.
### 5. 부정 입력까지 관측한 뒤에 경계가 유지된다고 판단한다
성공 기준을 「로그인이 성공한다」로 두면 네 패턴 모두에서 너무 넓다. 리프레시 토큰이 브라우저에 노출된 채로도 화면은 열리고, 위조한 identity 헤더가 통과하는 구성에서도 정상 사용자는 자기 이름을 그대로 본다. 그래서 브라우저 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인데 사용자 이름이 원래 사용자: 통과로 기록한다
- 위조 헤더 응답의 사용자 이름이 위조한 값: 중단하고 그 요청의 입력과 출력을 보존한다
- 검증 종료 후: 스택을 내리고 백업한 볼륨을 복구한 뒤 로그인을 다시 확인한다