feat: 문서 구조 변경 및 tech-visual 스킬 추가
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 280" role="img"
|
||||
aria-labelledby="t d">
|
||||
<title id="t">AP1 credential 보관 경계</title>
|
||||
<desc id="d">브라우저 실행 영역 하나가 code 교환, token 보관, 요청 서명 세 가지를 모두 담고 있고, 그 영역 전체가 실행 중 XSS가 닿는 범위다. Keycloak과 Resource Server는 그 밖에 있으며 Resource Server는 서명·issuer·audience를 검증한다.</desc>
|
||||
|
||||
<style>
|
||||
.lbl { font: 13px system-ui, -apple-system, "Segoe UI", sans-serif; fill: #17181b; }
|
||||
.sub { font: 11px system-ui, -apple-system, sans-serif; fill: #5b6068; }
|
||||
.zone { font: 600 11px system-ui, -apple-system, sans-serif; letter-spacing: .06em; }
|
||||
.box { fill: #fff; stroke: #b9bdc4; stroke-width: 1; }
|
||||
.arw { stroke: #6b7079; stroke-width: 1.4; fill: none; marker-end: url(#h); }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="h" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#6b7079"/>
|
||||
</marker>
|
||||
<pattern id="x" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#c2695c" stroke-width="1" opacity=".28"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- XSS reach: hatch + border, so it reads without colour -->
|
||||
<rect x="20" y="52" width="392" height="208" rx="7" fill="url(#x)" stroke="#c2695c" stroke-width="1.5"/>
|
||||
<text x="20" y="42" class="zone" fill="#a8483c">실행 중 XSS가 닿는 범위</text>
|
||||
|
||||
<rect x="34" y="66" width="364" height="180" rx="5" class="box"/>
|
||||
<text x="48" y="88" class="zone" fill="#5b6068">브라우저</text>
|
||||
|
||||
<rect x="50" y="100" width="332" height="40" rx="4" class="box"/>
|
||||
<text x="64" y="119" class="lbl">code 교환</text>
|
||||
<text x="64" y="134" class="sub">code_verifier</text>
|
||||
|
||||
<rect x="50" y="150" width="332" height="40" rx="4" class="box"/>
|
||||
<text x="64" y="169" class="lbl">token 보관</text>
|
||||
<text x="64" y="184" class="sub">access · refresh · ID — JavaScript memory</text>
|
||||
|
||||
<rect x="50" y="200" width="332" height="34" rx="4" class="box"/>
|
||||
<text x="64" y="222" class="lbl">요청 서명</text>
|
||||
<text x="150" y="222" class="sub">Authorization: Bearer</text>
|
||||
|
||||
<rect x="468" y="52" width="272" height="62" rx="5" class="box"/>
|
||||
<text x="484" y="74" class="zone" fill="#5b6068">KEYCLOAK</text>
|
||||
<text x="484" y="98" class="lbl">Authorization Code + PKCE</text>
|
||||
|
||||
<rect x="468" y="152" width="272" height="108" rx="5" class="box"/>
|
||||
<text x="484" y="174" class="zone" fill="#5b6068">RESOURCE SERVER</text>
|
||||
<text x="484" y="198" class="lbl">검증</text>
|
||||
<text x="530" y="198" class="sub">서명 · issuer · audience</text>
|
||||
<text x="484" y="222" class="lbl">STATELESS</text>
|
||||
<text x="484" y="243" class="sub">지울 session이 없다</text>
|
||||
|
||||
<path class="arw" d="M412 90 H464"/>
|
||||
<text x="418" y="82" class="sub">code</text>
|
||||
|
||||
<path class="arw" d="M412 216 H464"/>
|
||||
<text x="418" y="208" class="sub">Bearer</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 820 280" role="img"
|
||||
aria-labelledby="t d">
|
||||
<title id="t">AP2 split custody 경계</title>
|
||||
<desc id="d">Spring mediator가 authorized client에 access token과 refresh token을 함께 보관하지만, access token만 브라우저 실행 영역으로 돌아온다. 브라우저는 그 값으로 Authorization 헤더를 만들어 Resource Server를 직접 호출하며 이 경로는 mediator를 지나지 않는다. 브라우저 실행 영역 전체가 실행 중 XSS가 닿는 범위다.</desc>
|
||||
|
||||
<style>
|
||||
.lbl { font: 13px system-ui, -apple-system, "Segoe UI", sans-serif; fill: #17181b; }
|
||||
.sub { font: 11px system-ui, -apple-system, sans-serif; fill: #5b6068; }
|
||||
.zone { font: 600 11px system-ui, -apple-system, sans-serif; letter-spacing: .06em; }
|
||||
.box { fill: #fff; stroke: #b9bdc4; stroke-width: 1; }
|
||||
.arw { stroke: #6b7079; stroke-width: 1.4; fill: none; marker-end: url(#h); }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="h" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#6b7079"/>
|
||||
</marker>
|
||||
<pattern id="x" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#c2695c" stroke-width="1" opacity=".28"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- XSS reach: hatch + border, so it reads without colour -->
|
||||
<rect x="16" y="52" width="296" height="170" rx="7" fill="url(#x)" stroke="#c2695c" stroke-width="1.5"/>
|
||||
<text x="16" y="42" class="zone" fill="#a8483c">실행 중 XSS가 닿는 범위</text>
|
||||
|
||||
<rect x="28" y="66" width="272" height="142" rx="5" class="box"/>
|
||||
<text x="42" y="88" class="zone" fill="#5b6068">브라우저</text>
|
||||
|
||||
<rect x="42" y="100" width="244" height="44" rx="4" class="box"/>
|
||||
<text x="56" y="120" class="lbl">AP2_SESSION</text>
|
||||
<text x="56" y="136" class="sub">HttpOnly · SameSite=Lax</text>
|
||||
|
||||
<rect x="42" y="152" width="244" height="44" rx="4" class="box"/>
|
||||
<text x="56" y="172" class="lbl">access token</text>
|
||||
<text x="56" y="188" class="sub">JavaScript 지역 변수</text>
|
||||
|
||||
<rect x="392" y="52" width="224" height="156" rx="5" class="box"/>
|
||||
<text x="404" y="74" class="zone" fill="#5b6068">SPRING MEDIATOR</text>
|
||||
<text x="404" y="92" class="sub">confidential · client_secret_basic</text>
|
||||
|
||||
<rect x="404" y="106" width="200" height="44" rx="4" class="box"/>
|
||||
<text x="416" y="126" class="lbl">authorized client</text>
|
||||
<text x="416" y="142" class="sub">access · refresh</text>
|
||||
|
||||
<rect x="656" y="100" width="152" height="96" rx="5" class="box"/>
|
||||
<text x="668" y="122" class="zone" fill="#5b6068">RESOURCE SERVER</text>
|
||||
<text x="668" y="148" class="lbl">검증</text>
|
||||
<text x="668" y="166" class="sub">서명 · issuer · audience</text>
|
||||
|
||||
<path class="arw" d="M314 122 H388"/>
|
||||
<text x="316" y="114" class="sub">/token/access</text>
|
||||
|
||||
<path class="arw" d="M388 174 H316"/>
|
||||
|
||||
<path class="arw" d="M160 224 V258 H732 V200"/>
|
||||
<text x="380" y="252" class="sub">Authorization: Bearer</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,62 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 285" role="img"
|
||||
aria-labelledby="t d">
|
||||
<title id="t">AP3 BFF custody 경계</title>
|
||||
<desc id="d">브라우저에는 HttpOnly AP3_SESSION과 JavaScript가 읽을 수 있는 XSRF-TOKEN만 있고 OAuth token은 없다. BFF가 authorized client에서 access token과 refresh token을 들고 있으며, Resource Server로 가는 Bearer 요청은 BFF에서 새로 만들어진다. 브라우저의 session cookie는 downstream으로 전달되지 않는다. 브라우저 실행 영역 전체가 실행 중 XSS가 닿는 범위다.</desc>
|
||||
|
||||
<style>
|
||||
.lbl { font: 13px system-ui, -apple-system, "Segoe UI", sans-serif; fill: #17181b; }
|
||||
.sub { font: 11px system-ui, -apple-system, sans-serif; fill: #5b6068; }
|
||||
.zone { font: 600 11px system-ui, -apple-system, sans-serif; letter-spacing: .06em; }
|
||||
.box { fill: #fff; stroke: #b9bdc4; stroke-width: 1; }
|
||||
.gone { fill: #fff; stroke: #b9bdc4; stroke-width: 1; stroke-dasharray: 4 3; }
|
||||
.arw { stroke: #6b7079; stroke-width: 1.4; fill: none; marker-end: url(#h); }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="h" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#6b7079"/>
|
||||
</marker>
|
||||
<pattern id="x" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#c2695c" stroke-width="1" opacity=".28"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect x="16" y="52" width="270" height="190" rx="7" fill="url(#x)" stroke="#c2695c" stroke-width="1.5"/>
|
||||
<text x="16" y="42" class="zone" fill="#a8483c">실행 중 XSS가 닿는 범위</text>
|
||||
|
||||
<rect x="28" y="66" width="246" height="164" rx="5" class="box"/>
|
||||
<text x="42" y="88" class="zone" fill="#5b6068">브라우저</text>
|
||||
|
||||
<rect x="42" y="100" width="218" height="42" rx="4" class="box"/>
|
||||
<text x="56" y="119" class="lbl">AP3_SESSION</text>
|
||||
<text x="56" y="134" class="sub">HttpOnly · JavaScript 읽기 x</text>
|
||||
|
||||
<rect x="42" y="150" width="218" height="42" rx="4" class="box"/>
|
||||
<text x="56" y="169" class="lbl">XSRF-TOKEN</text>
|
||||
<text x="56" y="184" class="sub">JavaScript 읽기 o</text>
|
||||
|
||||
<rect x="42" y="198" width="218" height="26" rx="4" class="gone"/>
|
||||
<text x="56" y="215" class="lbl">OAuth token</text>
|
||||
<text x="240" y="215" class="lbl">x</text>
|
||||
|
||||
<rect x="350" y="52" width="230" height="170" rx="5" class="box"/>
|
||||
<text x="362" y="74" class="zone" fill="#5b6068">BFF</text>
|
||||
<text x="362" y="92" class="sub">confidential · client_secret_basic</text>
|
||||
|
||||
<rect x="362" y="106" width="206" height="42" rx="4" class="box"/>
|
||||
<text x="374" y="125" class="lbl">authorized client</text>
|
||||
<text x="374" y="140" class="sub">access · refresh</text>
|
||||
|
||||
<rect x="636" y="100" width="150" height="90" rx="5" class="box"/>
|
||||
<text x="648" y="122" class="zone" fill="#5b6068">RESOURCE SERVER</text>
|
||||
<text x="648" y="148" class="lbl">검증</text>
|
||||
<text x="648" y="166" class="sub">서명 · issuer · audience</text>
|
||||
|
||||
<path class="arw" d="M288 122 H346"/>
|
||||
<text x="290" y="114" class="sub">/bff/api/me</text>
|
||||
|
||||
<path class="arw" d="M346 174 H290"/>
|
||||
|
||||
<path class="arw" d="M465 224 V260 H711 V194"/>
|
||||
<text x="500" y="254" class="sub">Authorization: Bearer</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 790 250" role="img"
|
||||
aria-labelledby="t2 d2">
|
||||
<title id="t2">AP3 CSRF token 두 갈래</title>
|
||||
<desc id="d2">BFF의 CSRF endpoint 하나가 두 결과를 만든다. XSRF-TOKEN cookie에는 raw token이 들어가고 JSON 응답 본문에는 XOR로 가린 token과 headerName이 들어간다. SPA는 JSON에서 headerName만 읽고 실제 header 값은 cookie의 raw token을 쓴다. POST에 도달한 cookie와 header를 Spring CSRF filter가 대조한다.</desc>
|
||||
|
||||
<style>
|
||||
.lbl { font: 13px system-ui, -apple-system, "Segoe UI", sans-serif; fill: #17181b; }
|
||||
.sub { font: 11px system-ui, -apple-system, sans-serif; fill: #5b6068; }
|
||||
.zone { font: 600 11px system-ui, -apple-system, sans-serif; letter-spacing: .06em; }
|
||||
.box { fill: #fff; stroke: #b9bdc4; stroke-width: 1; }
|
||||
.arw { stroke: #6b7079; stroke-width: 1.4; fill: none; marker-end: url(#h2); }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="h2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#6b7079"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect x="16" y="92" width="136" height="60" rx="5" class="box"/>
|
||||
<text x="30" y="116" class="lbl">/bff/csrf</text>
|
||||
<text x="30" y="134" class="sub">GET</text>
|
||||
|
||||
<rect x="212" y="30" width="200" height="56" rx="5" class="box"/>
|
||||
<text x="226" y="54" class="lbl">XSRF-TOKEN</text>
|
||||
<text x="226" y="72" class="sub">cookie · raw token</text>
|
||||
|
||||
<rect x="212" y="158" width="200" height="56" rx="5" class="box"/>
|
||||
<text x="226" y="182" class="lbl">JSON body</text>
|
||||
<text x="226" y="200" class="sub">masked token · headerName</text>
|
||||
|
||||
<rect x="466" y="92" width="152" height="60" rx="5" class="box"/>
|
||||
<text x="480" y="116" class="lbl">X-XSRF-TOKEN</text>
|
||||
<text x="480" y="134" class="sub">= raw token</text>
|
||||
|
||||
<rect x="666" y="92" width="112" height="60" rx="5" class="box"/>
|
||||
<text x="678" y="116" class="zone" fill="#5b6068">CSRF FILTER</text>
|
||||
<text x="678" y="134" class="lbl">대조</text>
|
||||
|
||||
<path class="arw" d="M154 112 H182 V58 H208"/>
|
||||
<path class="arw" d="M154 132 H182 V186 H208"/>
|
||||
|
||||
<path class="arw" d="M414 58 H440 V112 H462"/>
|
||||
<path class="arw" d="M414 186 H440 V132 H462"/>
|
||||
<text x="418" y="150" class="sub">headerName</text>
|
||||
|
||||
<path class="arw" d="M620 122 H662"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,66 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 820 285" role="img"
|
||||
aria-labelledby="t4 d4">
|
||||
<title id="t4">AP4 edge 신뢰 경계</title>
|
||||
<desc id="d4">브라우저는 AP4_SESSION과 함께 client가 만든 identity header도 보낼 수 있지만 그 header는 Nginx에서 덮어써진다. Nginx는 oauth2-proxy의 internal auth endpoint에 subrequest를 보내 user와 email을 받고, 그 값과 자신이 가진 internal token으로 upstream 요청을 새로 만든다. oauth2-proxy와 Spring upstream은 host port가 닫혀 있어 외부에서 직접 닿을 수 없다.</desc>
|
||||
|
||||
<style>
|
||||
.lbl { font: 13px system-ui, -apple-system, "Segoe UI", sans-serif; fill: #17181b; }
|
||||
.sub { font: 11px system-ui, -apple-system, sans-serif; fill: #5b6068; }
|
||||
.zone { font: 600 11px system-ui, -apple-system, sans-serif; letter-spacing: .06em; }
|
||||
.box { fill: #fff; stroke: #b9bdc4; stroke-width: 1; }
|
||||
.gone { fill: #fff; stroke: #b9bdc4; stroke-width: 1; stroke-dasharray: 4 3; }
|
||||
.arw { stroke: #6b7079; stroke-width: 1.4; fill: none; marker-end: url(#h4); }
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<marker id="h4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#6b7079"/>
|
||||
</marker>
|
||||
<pattern id="x4" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="7" stroke="#c2695c" stroke-width="1" opacity=".28"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect x="16" y="52" width="210" height="150" rx="7" fill="url(#x4)" stroke="#c2695c" stroke-width="1.5"/>
|
||||
<text x="16" y="42" class="zone" fill="#a8483c">외부 · 신뢰하지 않는 입력</text>
|
||||
|
||||
<rect x="28" y="66" width="186" height="124" rx="5" class="box"/>
|
||||
<text x="42" y="88" class="zone" fill="#5b6068">브라우저</text>
|
||||
|
||||
<rect x="42" y="100" width="158" height="38" rx="4" class="box"/>
|
||||
<text x="54" y="118" class="lbl">AP4_SESSION</text>
|
||||
<text x="54" y="132" class="sub">HttpOnly · Lax</text>
|
||||
|
||||
<rect x="42" y="144" width="158" height="38" rx="4" class="gone"/>
|
||||
<text x="54" y="162" class="lbl">client 제공 header</text>
|
||||
<text x="54" y="176" class="sub">덮어쓰기 대상</text>
|
||||
|
||||
<rect x="280" y="52" width="150" height="150" rx="5" class="box"/>
|
||||
<text x="292" y="74" class="zone" fill="#5b6068">NGINX</text>
|
||||
<text x="292" y="92" class="sub">8088 공개</text>
|
||||
<text x="292" y="122" class="lbl">header 덮어쓰기</text>
|
||||
<text x="292" y="140" class="sub">trusted proxy</text>
|
||||
|
||||
<rect x="490" y="40" width="316" height="210" rx="7" class="gone"/>
|
||||
<text x="640" y="30" class="zone" fill="#5b6068">HOST PORT 닫힘</text>
|
||||
|
||||
<rect x="504" y="56" width="288" height="64" rx="5" class="box"/>
|
||||
<text x="518" y="80" class="lbl">oauth2-proxy</text>
|
||||
<text x="518" y="98" class="sub">internal /oauth2/auth</text>
|
||||
|
||||
<rect x="504" y="160" width="288" height="76" rx="5" class="box"/>
|
||||
<text x="518" y="182" class="zone" fill="#5b6068">SPRING UPSTREAM</text>
|
||||
<text x="518" y="204" class="lbl">/edge/me</text>
|
||||
<text x="518" y="222" class="sub">user header + internal token</text>
|
||||
|
||||
<path class="arw" d="M228 122 H276"/>
|
||||
|
||||
<path class="arw" d="M432 80 H500"/>
|
||||
<text x="436" y="72" class="sub">auth_request</text>
|
||||
|
||||
<path class="arw" d="M500 104 H434"/>
|
||||
<text x="436" y="118" class="sub">user · email</text>
|
||||
|
||||
<path class="arw" d="M355 204 V262 H648 V240"/>
|
||||
<text x="390" y="256" class="sub">nginx-owned header · internal token</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
Reference in New Issue
Block a user