refactor: 문서 개선 중

This commit is contained in:
donghyeon-ka
2026-09-21 14:30:55 +09:00
parent c93cdea150
commit 805a18f486
1497 changed files with 525837 additions and 59152 deletions
@@ -0,0 +1,501 @@
[
{
"file": "case-ap2-split-custody.md",
"id": "488ce49b-afa4-42a5-a2ce-de2e0653cd82",
"kind": "CASE",
"title": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"relations": [
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "SPA에서는 브라우저가 code 교환과 token 보관을 직접 수행한다. 이 Case에서는 code 교환과 refresh token 보관을 mediator가 수행하도록 구성했다."
},
{
"kind": "관계",
"target": "Public Client와 Confidential Client 구분 기준",
"reason": "confidential client를 쓰면서도 access token이 브라우저 응답에 실린다. 종류와 token 노출이 별개라는 근거다."
},
{
"kind": "관계",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "access token 원문이 응답 본문과 지역 변수와 헤더를 지난다. 상태별 이름을 나눠야 하는 이유다."
},
{
"kind": "관계",
"target": "OAuth/OIDC 인증 패턴 선택 기준",
"reason": "mediator가 refresh token을 관리하면서도 브라우저가 Resource Server를 직접 호출하는 구성을 비교할 때 사용하는 Case다."
},
{
"kind": "관계",
"target": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
"reason": "refresh token rotation과 재사용 0회를 쓰는 구성이다. replica 경쟁 질문의 전제다."
}
]
},
{
"file": "case-ap3-bff-session-csrf.md",
"id": "d85bd6af-7599-4ef7-9407-6609927d5b5c",
"kind": "CASE",
"title": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"relations": [
{
"kind": "관계",
"target": "BFF 인증 구조 설계 기준",
"reason": "이 기준이 요구하는 항목 중 무엇이 구현됐고 무엇이 구현되지 않았는지"
},
{
"kind": "관계",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "session cookie와 CSRF token, server-side token을 각각 다뤄야 하는 이유"
},
{
"kind": "관계",
"target": "OAuth/OIDC 인증 패턴 선택 기준",
"reason": "BFF 구조에서 필요한 CSRF 검증과 server-side 상태 저장 기준을 함께 다룬다"
},
{
"kind": "관계",
"target": "BFF가 OAuth Token을 관리하는 조건",
"reason": "이 결정의 구조를 실제로 실행해 본 문서"
},
{
"kind": "관계",
"target": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
"reason": "두 상태가 모두 process-local memory에 있다는 점이 질문의 시작이다"
},
{
"kind": "관계",
"target": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
"reason": "session과 authorized client의 2가지 흐름"
}
]
},
{
"file": "case-ap4-identity-header-trust.md",
"id": "a0e1cc05-92b3-4dac-bce1-513ab8cd862b",
"kind": "CASE",
"title": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"relations": [
{
"kind": "관계",
"target": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
"reason": "identity header를 신뢰하기 위한 조건을 Nginx, oauth2-proxy, backend 설정과 요청 결과로 확인했다."
},
{
"kind": "관계",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "Forward-Auth에서는 proxy session cookie와 identity header를 JWT와 구분해 다룬다."
},
{
"kind": "관계",
"target": "OAuth/OIDC 인증 패턴 선택 기준",
"reason": "OAuth 처리는 edge에서 끝내고 upstream은 검증된 identity header를 사용하도록 구성한 Case다."
},
{
"kind": "관계",
"target": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
"reason": "edge가 user와 email만 전달한다는 사실이 이 질문의 출발점이다."
}
]
},
{
"file": "case-browser-credential-boundary.md",
"id": "bf675775-4f3e-4744-8014-f0efff51422a",
"kind": "CASE",
"title": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"relations": [
{
"kind": "관계",
"target": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
"reason": "브라우저가 authorization endpoint와 token endpoint를 직접 호출하는 흐름을 코드와 network 요청으로 확인했다."
},
{
"kind": "관계",
"target": "Public Client와 Confidential Client 구분 기준",
"reason": "SPA는 client secret을 안전하게 보관할 수 없어 public client로 등록했고, Authorization Code Flow에는 PKCE를 적용했다."
},
{
"kind": "관계",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "JavaScript memory의 OAuth token과 Keycloak 도메인의 SSO cookie가 서로 다른 상태라는 점을 확인했다."
},
{
"kind": "관계",
"target": "인증 구조를 보안 성숙도 단계로 취급하지 않는다",
"reason": "이 Case의 SPA 구성을 다른 패턴보다 낮은 단계로 해석하지 않도록 별도의 결정 기록에서 기준을 정했다."
}
]
},
{
"file": "decision-bff-owns-token.md",
"id": "19b55c39-c583-4161-9775-df954280a568",
"kind": "PROJECT_DECISION",
"title": "BFF가 OAuth Token을 관리하는 조건",
"relations": [
{
"kind": "근거",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "이 결정이 가리키는 구조를 실제로 실행해 본 기록이다."
},
{
"kind": "근거",
"target": "BFF 인증 구조 설계 기준",
"reason": "이 결정이 PROPOSED인 동안의 실제 적용 기준이다."
},
{
"kind": "근거",
"target": "OAuth/OIDC 인증 패턴 선택 기준",
"reason": "이 결정을 적용할 조건과 피해야 할 조건이 여기 있다."
},
{
"kind": "근거",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "access token이 브라우저로 나가 이 요구를 만족하지 못한 경우다."
}
]
},
{
"file": "decision-federation-not-a-pattern.md",
"id": "8c1ebea7-204e-445c-9812-0421d9eb0e9c",
"kind": "PROJECT_DECISION",
"title": "외부 IdP Federation을 별도의 인증 구조로 세지 않는다",
"relations": [
{
"kind": "근거",
"target": "외부 IdP Federation과 Application 인증 경계",
"reason": "이 결정을 규칙으로 편 기준이다."
},
{
"kind": "근거",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "브로커가 발급한 code를 받는 애플리케이션 경계다."
},
{
"kind": "근거",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "upstream IdP 상태와 애플리케이션 상태를 같은 이름으로 부르지 않는다."
}
]
},
{
"file": "decision-not-maturity-ladder.md",
"id": "5f4b6000-cb78-400c-bf6e-a25632a4bb40",
"kind": "PROJECT_DECISION",
"title": "인증 구조를 보안 성숙도 단계로 취급하지 않는다",
"relations": [
{
"kind": "근거",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "브라우저가 code 교환, token 보관, API 호출을 직접 수행한다."
},
{
"kind": "근거",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "mediator가 code 교환과 refresh token 보관을 담당하고 브라우저가 access token으로 API를 직접 호출한다."
},
{
"kind": "근거",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "BFF가 token과 session을 server-side에서 관리하고 Resource Server를 호출한다."
},
{
"kind": "근거",
"target": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"reason": "oauth2-proxy가 인증을 처리하고 upstream에는 identity header를 전달한다."
},
{
"kind": "근거",
"target": "OAuth/OIDC 인증 패턴 선택 기준",
"reason": "이 결정을 적용하는 선택 기준이다."
}
]
},
{
"file": "question-bff-state-store.md",
"id": "18a5cde2-dd1e-4bff-9f1c-997577ae438f",
"kind": "QUESTION",
"title": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
"relations": [
{
"kind": "관계",
"target": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
"reason": "이 질문에서 저장소 부분만 떼어 낸 것이다."
},
{
"kind": "관계",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "session과 authorized client의 열쇠가 다르다는 사실의 출처다."
},
{
"kind": "관계",
"target": "BFF 인증 구조 설계 기준",
"reason": "이 기준의 저장소 항목이 이 질문의 답을 기다린다."
},
{
"kind": "관계",
"target": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
"reason": "저장소를 공유한 뒤에야 replica 경쟁이 재현된다."
}
]
},
{
"file": "question-edge-authorization-scope.md",
"id": "7ff40767-a00b-4db2-98f6-0cdfce8c8936",
"kind": "QUESTION",
"title": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
"relations": [
{
"kind": "관계",
"target": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"reason": "edge가 user와 email만 전달한다는 사실의 출처다."
},
{
"kind": "관계",
"target": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
"reason": "헤더 allowlist와 검증 조건이 이 기준에 있다."
},
{
"kind": "관계",
"target": "BFF 인증 구조 설계 기준",
"reason": "되돌리는 선택지의 기준이 이 문서다."
}
]
},
{
"file": "question-multi-instance-session.md",
"id": "c72656b5-842d-45d9-b5f6-82b66b09d0b9",
"kind": "QUESTION",
"title": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
"relations": [
{
"kind": "관계",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "두 상태가 모두 process-local memory에 있다는 사실의 출처다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "같은 저장소 구성을 쓰는 다른 패턴이다."
},
{
"kind": "관계",
"target": "BFF 인증 구조 설계 기준",
"reason": "이 질문의 답이 이 기준의 빈 항목을 채운다."
},
{
"kind": "관계",
"target": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
"reason": "저장소 후보 비교로 독립시킨 질문이다."
}
]
},
{
"file": "question-refresh-rotation-replica.md",
"id": "9ae4ec71-a32e-49a7-88c2-f7368541c28d",
"kind": "QUESTION",
"title": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
"relations": [
{
"kind": "관계",
"target": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
"reason": "저장소 결정이 이 질문보다 앞선다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "rotation과 재사용 0회를 쓰는 구성의 출처다."
},
{
"kind": "관계",
"target": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
"reason": "다중 인스턴스 운영이 이 경쟁의 전제다."
},
{
"kind": "관계",
"target": "BFF 인증 구조 설계 기준",
"reason": "갱신 실패를 화면 오류로 바꾸는 규칙이 이 기준의 항목이다."
}
]
},
{
"file": "reference-authorization-code-endpoints.md",
"id": "39fdf472-82c4-43ed-abec-73de672f08ae",
"kind": "REFERENCE",
"title": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
"relations": [
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "브라우저가 code를 직접 교환하는 흐름에서 endpoint별 이동을 관측했다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "confidential client가 token endpoint에서 client 인증을 수행하는 흐름을 보여 준다."
},
{
"kind": "관계",
"target": "Public Client와 Confidential Client 구분 기준",
"reason": "public/confidential client 구분에 따라 token endpoint의 client 인증 방식이 달라지고, Authorization Code Flow에서는 PKCE 적용 여부도 함께 결정한다."
}
]
},
{
"file": "reference-bff-auth-design.md",
"id": "97eddd97-1096-426a-a2c6-a6c5bf1cd09f",
"kind": "REFERENCE",
"title": "BFF 인증 구조 설계 기준",
"relations": [
{
"kind": "관계",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "이 기준의 항목 중 실제로 구현된 것과 비어 있는 것을 센 기록이다."
},
{
"kind": "관계",
"target": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
"reason": "저장소 항목이 아직 답이 없는 질문으로 남아 있다."
},
{
"kind": "관계",
"target": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
"reason": "어느 저장소에 둘지가 이 기준의 미결 항목이다."
},
{
"kind": "관계",
"target": "BFF가 OAuth Token을 관리하는 조건",
"reason": "이 결정이 PROPOSED인 동안 실제 적용 기준은 이 문서다."
}
]
},
{
"file": "reference-forward-auth-header-trust.md",
"id": "004dd0a2-5fb3-4f25-80c9-576f709de331",
"kind": "REFERENCE",
"title": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
"relations": [
{
"kind": "관계",
"target": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"reason": "이 기준의 다섯 조건을 실제 설정에서 확인한 기록이다."
},
{
"kind": "관계",
"target": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
"reason": "헤더를 어디까지 늘릴지가 이 기준의 미결 항목이다."
},
{
"kind": "관계",
"target": "OAuth Token과 Application Session을 구분하는 기준",
"reason": "identity 헤더를 JWT나 session과 같은 이름으로 부르지 않는다."
}
]
},
{
"file": "reference-idp-federation-boundary.md",
"id": "1a00a640-8987-4075-a9e4-7ec023cdffbb",
"kind": "REFERENCE",
"title": "외부 IdP Federation과 Application 인증 경계",
"relations": [
{
"kind": "관계",
"target": "외부 IdP Federation을 별도의 인증 구조로 세지 않는다",
"reason": "이 기준을 프로젝트 결정으로 굳힌 기록이다."
},
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "브로커가 만든 authorization code를 애플리케이션이 받는 흐름이다."
},
{
"kind": "관계",
"target": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
"reason": "외부 IdP가 있어도 애플리케이션 쪽 endpoint 이동은 그대로다."
}
]
},
{
"file": "reference-pattern-selection.md",
"id": "3f886154-1b85-407b-bda4-57d28370e745",
"kind": "REFERENCE",
"title": "OAuth/OIDC 인증 패턴 선택 기준",
"relations": [
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "브라우저가 code 교환과 token 보관, API 호출을 모두 맡는다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "mediator가 refresh token을 관리하고 브라우저가 access token으로 API를 직접 호출하는 구성을 확인했다."
},
{
"kind": "관계",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "BFF가 code 교환, token 보관, Resource Server 호출을 모두 처리하는 구성을 확인했다."
},
{
"kind": "관계",
"target": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"reason": "인증이 edge로 가면 보호 자원이 검증하는 것이 JWT에서 헤더로 바뀐다."
},
{
"kind": "관계",
"target": "인증 구조를 보안 성숙도 단계로 취급하지 않는다",
"reason": "이 기준의 첫 항목을 프로젝트 결정으로 굳힌 기록이다."
}
]
},
{
"file": "reference-public-confidential-client.md",
"id": "ede6b9ce-eeed-40c8-9175-9e8116029395",
"kind": "REFERENCE",
"title": "Public Client와 Confidential Client 구분 기준",
"relations": [
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "SPA를 public client로 등록한 이유를 실제 구성에서 확인할 수 있다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "confidential client를 사용해도 access token 전달 방식은 별도로 설계된다는 예다."
},
{
"kind": "관계",
"target": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
"reason": "client 종류에 따라 token endpoint의 client 인증 방식이 달라진다."
}
]
},
{
"file": "reference-token-vs-session.md",
"id": "66c18e42-116c-459f-86bd-b7e4bf394866",
"kind": "REFERENCE",
"title": "OAuth Token과 Application Session을 구분하는 기준",
"relations": [
{
"kind": "관계",
"target": "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계",
"reason": "JavaScript memory의 OAuth token과 Keycloak SSO session을 구분한 Case다."
},
{
"kind": "관계",
"target": "Mediator가 Refresh Token을 관리하고 Access Token을 Browser에 전달하는 구조",
"reason": "같은 요청 안에서 session cookie와 access token이 함께 움직인다."
},
{
"kind": "관계",
"target": "BFF에서 OAuth Token을 관리할 때 Session과 CSRF를 처리한 과정",
"reason": "BFF에서는 session cookie, JavaScript가 읽는 CSRF token, server-side OAuth token을 각각 다른 용도로 사용한다."
},
{
"kind": "관계",
"target": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
"reason": "Forward-Auth에서는 upstream이 JWT를 직접 검증하지 않고 proxy session을 기반으로 edge가 만든 identity header를 사용한다."
}
]
}
]