<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>김신건의 로그 - 위키 / auth-security</title><description>&apos;auth-security&apos; 카테고리 위키 페이지 최신 업데이트</description><link>https://shinkeonkim.com/</link><language>ko-KR</language><item><title>[Auth] CSRF: 위조 요청 공격과 방어</title><link>https://shinkeonkim.com/wiki/auth-security/csrf/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/csrf/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CSRF (Cross-Site Request Forgery)&lt;/strong&gt; 는 &lt;em&gt;사용자가 로그인된 상태&lt;/em&gt; 에서 &lt;em&gt;공격자가 만든 페이지&lt;/em&gt; 가 &lt;em&gt;피해자 브라우저로 위조 요청&lt;/em&gt; 을 보내는 공격. 쿠키 기반 인증의 &lt;em&gt;큰 약점&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;공격-시나리오&quot;&gt;공격 시나리오&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant V as Victim&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Bank as bank.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Evil as evil.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V-&gt;&gt;Bank: 로그인&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bank--&gt;&gt;V: Set-Cookie: session=ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V-&gt;&gt;Evil: 다른 탭에서 evil.com 방문&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Evil--&gt;&gt;V: HTML with &amp;#x3C;form action=&quot;bank.com/transfer&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over V: HTML 안의 form 자동 submit (JS)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    V-&gt;&gt;Bank: POST /transfer&amp;#x3C;br/&gt;(자동 첨부 cookie: session=ABC)&amp;#x3C;br/&gt;amount=1000&amp;#x26;to=attacker&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bank-&gt;&gt;Bank: 인증된 세션 → 정상 처리&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Bank--&gt;&gt;V: 200 OK (이미 이체 됨)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심: &lt;em&gt;브라우저가 쿠키를 자동 첨부&lt;/em&gt; 한다는 점을 &lt;em&gt;악용&lt;/em&gt;. &lt;em&gt;XSS 와 다름&lt;/em&gt;. CSRF 는 &lt;em&gt;공격자가 페이지에 코드 주입 못 해도 가능&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;4가지-방어-패턴&quot;&gt;4가지 방어 패턴&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{방어 패턴}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; A[1. SameSite Cookie&amp;#x3C;br/&gt;가장 단순]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; B[2. CSRF Token&amp;#x3C;br/&gt;Synchronizer]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; C[3. Double Submit Cookie]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; D[4. Custom Header]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;1-samesite-cookie&quot;&gt;1. SameSite Cookie&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; SID=abc; HttpOnly; Secure; SameSite=Lax&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt; 가 cross-site form POST 차단. &lt;em&gt;2020 부터 Chrome 의 기본값&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;strong&gt;SameSite=Lax 만으로도 대부분의 CSRF 차단&lt;/strong&gt;. &lt;em&gt;옛 브라우저 호환&lt;/em&gt; 이 필요하면 추가 패턴.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;2-csrf-token-synchronizer-token&quot;&gt;2. CSRF Token (Synchronizer Token)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Server: GET /form&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server-&gt;&gt;Server: token = random()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server--&gt;&gt;C: HTML + &amp;#x3C;input name=&quot;_csrf&quot; value=&quot;token&quot;&gt;&amp;#x3C;br/&gt;(session 에 token 저장)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Server: POST /transfer&amp;#x3C;br/&gt;_csrf=token + body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server-&gt;&gt;Server: session 의 token == 받은 token?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server--&gt;&gt;C: OK / 거절&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;각 폼/요청마다 고유 token&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;session 에 &lt;em&gt;서버 측 저장&lt;/em&gt; + 폼 hidden field.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Rails, Django, Spring&lt;/em&gt; 기본 적용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-double-submit-cookie&quot;&gt;3. Double Submit Cookie&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;GET&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /form&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;→ Set-Cookie: csrf_token=ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;→ HTML: &amp;#x3C;input name=&quot;_csrf&quot; value=&quot;ABC&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#F97583&quot;&gt;POST&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt; /transfer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;→ Cookie: csrf_token=ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;→ Body: _csrf=ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Server&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; cookie 의 csrf_token == body 의 _csrf ?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;서버 측 저장 없이&lt;/em&gt; 동작.&lt;/li&gt;
&lt;li&gt;단점: subdomain 의 &lt;em&gt;cookie 침해&lt;/em&gt; 시 우회.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-custom-header-xhrfetch-한정&quot;&gt;4. Custom Header (XHR/Fetch 한정)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;js&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;fetch&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;/api/transfer&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  method: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  headers: {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &apos;X-Requested-With&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;XMLHttpRequest&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;    &apos;Content-Type&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&apos;application/json&apos;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  body: &lt;/span&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;JSON&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;stringify&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;({&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Simple Request 가 아니므로 CORS preflight 발생&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;다른 origin 이면 preflight 거절 → 위조 요청 &lt;em&gt;애초에 불가&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;SPA + API&lt;/em&gt; 의 &lt;em&gt;깔끔한 패턴&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;어디에-어떤-방어&quot;&gt;어디에 어떤 방어?&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;환경&lt;/th&gt;&lt;th&gt;권장&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;클래식 서버 렌더링 (Rails, Django)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;CSRF token + SameSite=Lax&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SPA + REST API&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Custom header + SameSite=Strict&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API + JWT in &lt;code&gt;Authorization&lt;/code&gt; 헤더&lt;/td&gt;&lt;td&gt;&lt;em&gt;대부분 영향 없음&lt;/em&gt; (cookie 안 씀)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;모바일 앱 (자체 HTTP 라이브러리)&lt;/td&gt;&lt;td&gt;&lt;em&gt;영향 없음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3rd-party 통합 (iframe)&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SameSite=None + Secure&lt;/strong&gt; + token&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;get-의-위험성&quot;&gt;GET 의 위험성&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;html&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;img&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; src&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://bank.com/transfer?to=attacker&amp;#x26;amount=1000&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;상태 변경 GET&lt;/em&gt; 은 &lt;em&gt;CSRF 의 가장 흔한 함정&lt;/em&gt;. &lt;em&gt;모든 변경 요청은 POST/PUT/DELETE 만&lt;/em&gt;. &lt;em&gt;GET 은 idempotent + read-only&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SameSite=None&lt;/code&gt; 인데 &lt;code&gt;Secure&lt;/code&gt; 없음&lt;/strong&gt; = 브라우저가 거절. 옛 코드 깨짐.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF token 을 &lt;em&gt;모든 요청에서 동일&lt;/em&gt;&lt;/strong&gt; = &lt;em&gt;하나만 탈취&lt;/em&gt; 되면 모든 요청 위조. &lt;em&gt;세션 단위 회전&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GET 의 &lt;em&gt;상태 변경&lt;/em&gt;&lt;/strong&gt; = SameSite=Lax 가 top-level GET 허용 → CSRF.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multipart form 의 &lt;em&gt;Content-Type&lt;/em&gt;&lt;/strong&gt; = simple request 라 &lt;em&gt;preflight 없음&lt;/em&gt;. Custom header 방어가 &lt;em&gt;부분적&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[CORS]]&lt;/li&gt;
&lt;li&gt;[[Session Cookie]]&lt;/li&gt;
&lt;li&gt;[[JWT]]&lt;/li&gt;
&lt;li&gt;[[XSS]] (관련 공격)&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>csrf</category><category>security</category><category>auth</category><category>browser</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Auth] JWT: 구조, 서명, 만료, refresh token</title><link>https://shinkeonkim.com/wiki/auth-security/jwt/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/jwt/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7519&quot;&gt;RFC 7519&lt;/a&gt;) 은 &lt;em&gt;base64url 인코딩된 JSON + 서명&lt;/em&gt; 형태의 &lt;em&gt;self-contained token&lt;/em&gt;. 서버가 &lt;em&gt;세션 저장 없이 클레임 검증&lt;/em&gt; 가능.&lt;/p&gt;
&lt;h2 id=&quot;구조-3-부분&quot;&gt;구조: 3 부분&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJrb2EiLCJleHAiOjE3MjAxMjM0NTZ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;↑ header                                  ↑ payload                                            ↑ signature&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;부분&lt;/th&gt;&lt;th&gt;형식&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Header&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;{ &quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot; }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Payload&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;{ &quot;sub&quot;: &quot;42&quot;, &quot;name&quot;: &quot;koa&quot;, &quot;exp&quot;: 1720123456 }&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Signature&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;HMAC-SHA256(base64(header) + ”.” + base64(payload), secret)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;표준-클레임&quot;&gt;표준 클레임&lt;/h2&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;클레임&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;iss&lt;/code&gt;&lt;/td&gt;&lt;td&gt;issuer&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;&lt;td&gt;subject (user ID)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/td&gt;&lt;td&gt;audience&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;만료 시각 (Unix epoch)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;nbf&lt;/code&gt;&lt;/td&gt;&lt;td&gt;not before&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;iat&lt;/code&gt;&lt;/td&gt;&lt;td&gt;issued at&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;jti&lt;/code&gt;&lt;/td&gt;&lt;td&gt;JWT ID (unique, replay 방지)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;서명-알고리즘&quot;&gt;서명 알고리즘&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Alg&lt;/th&gt;&lt;th&gt;종류&lt;/th&gt;&lt;th&gt;비고&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HS256/384/512&lt;/code&gt;&lt;/td&gt;&lt;td&gt;HMAC (대칭)&lt;/td&gt;&lt;td&gt;양측이 같은 secret. 내부&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;RS256/384/512&lt;/code&gt;&lt;/td&gt;&lt;td&gt;RSA (비대칭)&lt;/td&gt;&lt;td&gt;private 으로 sign, public 으로 verify&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ES256/384/512&lt;/code&gt;&lt;/td&gt;&lt;td&gt;ECDSA&lt;/td&gt;&lt;td&gt;짧고 빠름&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;EdDSA&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Ed25519&lt;/td&gt;&lt;td&gt;가장 빠름, 안전&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;none&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;서명 없음&lt;/em&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;사용 금지&lt;/strong&gt; (취약점 다수)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
&lt;em&gt;&lt;code&gt;alg: none&lt;/code&gt;&lt;/em&gt; 을 받는 라이브러리는 &lt;em&gt;치명적 취약점&lt;/em&gt;. 항상 &lt;em&gt;허용 alg 화이트리스트&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흐름&quot;&gt;흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Auth as Auth Server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant API as API Server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Auth: POST /login (id, password)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth-&gt;&gt;Auth: 검증 + JWT 생성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;C: { access_token, refresh_token }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;API: GET /me&amp;#x3C;br/&gt;Authorization: Bearer &amp;#x3C;jwt&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API-&gt;&gt;API: JWT 검증 (서명 + exp)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API--&gt;&gt;C: 200 OK&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C: 만료 임박&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Auth: POST /refresh (refresh_token)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;C: 새 access_token&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;access-token-vs-refresh-token&quot;&gt;Access Token vs Refresh Token&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Access Token&lt;/th&gt;&lt;th&gt;Refresh Token&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;수명&lt;/td&gt;&lt;td&gt;&lt;em&gt;짧음&lt;/em&gt; (5분 ~ 1시간)&lt;/td&gt;&lt;td&gt;&lt;em&gt;길음&lt;/em&gt; (7일 ~ 30일)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;저장&lt;/td&gt;&lt;td&gt;메모리 (브라우저 SPA)&lt;/td&gt;&lt;td&gt;HttpOnly cookie 또는 안전한 저장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;매 요청 전송&lt;/td&gt;&lt;td&gt;&lt;em&gt;예&lt;/em&gt;&lt;/td&gt;&lt;td&gt;갱신 시만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;탈취 영향&lt;/td&gt;&lt;td&gt;짧은 시간 피해&lt;/td&gt;&lt;td&gt;큰 피해, &lt;em&gt;반드시 HttpOnly&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Revoke&lt;/td&gt;&lt;td&gt;만료까지 어려움&lt;/td&gt;&lt;td&gt;DB 에 저장 + revoke 가능&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Access token 을 localStorage 에 저장&lt;/em&gt; 하면 XSS 시 즉시 탈취. &lt;em&gt;메모리 + 새로고침 시 refresh&lt;/em&gt; 패턴이 정통.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;stateless-vs-stateful&quot;&gt;Stateless vs Stateful&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Session (stateful)&lt;/th&gt;&lt;th&gt;JWT (stateless)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;서버 저장소&lt;/td&gt;&lt;td&gt;필수&lt;/td&gt;&lt;td&gt;불필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Revoke&lt;/td&gt;&lt;td&gt;즉시&lt;/td&gt;&lt;td&gt;만료까지 어려움&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;스케일&lt;/td&gt;&lt;td&gt;sticky session 필요&lt;/td&gt;&lt;td&gt;&lt;em&gt;수평 확장 자유&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이로드&lt;/td&gt;&lt;td&gt;session ID 만 (작음)&lt;/td&gt;&lt;td&gt;&lt;em&gt;전체 클레임&lt;/em&gt; (큼)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;갱신&lt;/td&gt;&lt;td&gt;서버 갱신&lt;/td&gt;&lt;td&gt;새 JWT 발급&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;“JWT 가 항상 더 좋다”&lt;/em&gt; 는 미신. &lt;em&gt;작은 monolith&lt;/em&gt; 에서는 &lt;em&gt;session 이 더 단순 + 즉시 revoke 가능&lt;/em&gt;. JWT 는 &lt;em&gt;분산 + 마이크로서비스 + 모바일 SDK&lt;/em&gt; 에서 진가.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;revoke-전략-stateless-의-단점-보완&quot;&gt;Revoke 전략 (Stateless 의 단점 보완)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q{revoke 필요}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; A[짧은 exp + refresh&amp;#x3C;br/&gt;대부분의 경우]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; B[&quot;Blacklist (Redis)&amp;#x3C;br/&gt;jti 등록&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Q --&gt; C[Versioning&amp;#x3C;br/&gt;user.token_version&amp;#x3C;br/&gt;JWT 안에 version]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    A --&gt; Best[추천]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    B --&gt; Heavy[캐시 부담]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C --&gt; Sync[DB 한 번 조회 필요]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;xss--csrf-와의-관계&quot;&gt;XSS / CSRF 와의 관계&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store{저장 위치}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; A[localStorage] --&gt;|XSS 시 탈취| Bad1[X]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; B[메모리 + HttpOnly cookie refresh] --&gt;|XSS 영향 적음| Good[O]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Store --&gt; C[HttpOnly cookie + SameSite=Strict] --&gt;|CSRF 안전 + XSS 안전| Best[권장]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;Cookie 기반&lt;/em&gt; 이면 &lt;em&gt;CSRF 방어&lt;/em&gt; 필요 (SameSite=Strict 또는 CSRF token). &lt;em&gt;Bearer header&lt;/em&gt; 면 &lt;em&gt;XSS 방어&lt;/em&gt; 가 중요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;alg: none&lt;/code&gt; 허용 라이브러리&lt;/strong&gt; = 옛 라이브러리 일부. 즉시 업데이트.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HS256 vs RS256 혼동&lt;/strong&gt; = 공개키를 secret 으로 &lt;em&gt;오해해 서명&lt;/em&gt; 받으면 &lt;em&gt;모든 토큰 위조 가능&lt;/em&gt; (CVE 다수).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;만료 너무 김&lt;/strong&gt; = 탈취 시 피해 큼. 15분 ~ 1시간 권장.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;secret 이 짧음&lt;/strong&gt; = &lt;code&gt;secret&lt;/code&gt; 같이 무차별 공격 가능한 키. &lt;em&gt;32+ 바이트 랜덤&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;민감 정보 페이로드&lt;/strong&gt; = JWT 는 &lt;em&gt;암호화 아님&lt;/em&gt; (base64 인코딩). password / 카드 번호 &lt;em&gt;절대 금지&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;jwe-암호화-변형&quot;&gt;JWE (암호화 변형)&lt;/h2&gt;
&lt;p&gt;JWT 가 &lt;em&gt;서명만&lt;/em&gt; 이면 &lt;em&gt;내용 노출&lt;/em&gt;. &lt;strong&gt;JWE&lt;/strong&gt; 는 &lt;em&gt;암호화&lt;/em&gt; 까지. 그러나 &lt;em&gt;느리고 복잡&lt;/em&gt;. 대부분의 경우 &lt;em&gt;민감 정보를 페이로드에 안 넣고 JWS (서명만) 만으로 충분&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[OAuth2]]&lt;/li&gt;
&lt;li&gt;[[OpenID Connect]]&lt;/li&gt;
&lt;li&gt;[[Session Cookie]]&lt;/li&gt;
&lt;li&gt;[[CORS]]&lt;/li&gt;
&lt;li&gt;[[CSRF]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>jwt</category><category>auth</category><category>security</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Auth] mTLS (Mutual TLS): 양방향 인증서 인증</title><link>https://shinkeonkim.com/wiki/auth-security/mtls/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/mtls/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;mTLS (Mutual TLS)&lt;/strong&gt; 는 &lt;em&gt;서버뿐 아니라 클라이언트도 TLS 인증서로 자기 신분 증명&lt;/em&gt;. 일반 TLS = &lt;em&gt;서버 인증만&lt;/em&gt;, mTLS = &lt;em&gt;양방향&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;활용: &lt;em&gt;서비스 메시 (Istio, Linkerd)&lt;/em&gt;, &lt;em&gt;기업 내부 API&lt;/em&gt;, &lt;em&gt;금융 / B2B 통합&lt;/em&gt;, &lt;em&gt;IoT 기기&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tls-vs-mtls-흐름&quot;&gt;TLS vs mTLS 흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant S as Server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    rect rgb(220,240,220)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C,S: 일반 TLS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: ClientHello&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: ServerHello + Certificate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: 서버 인증서 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C--&gt;&gt;S: Finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    rect rgb(255,235,220)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over C,S: mTLS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: ClientHello&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: ServerHello + Certificate&amp;#x3C;br/&gt;+ CertificateRequest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;S: ClientCertificate + Finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S-&gt;&gt;S: 클라이언트 인증서 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    S--&gt;&gt;C: Finished&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;인증서-발급-흐름&quot;&gt;인증서 발급 흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    CA[Root CA] --&gt;|sign| IntCA[Intermediate CA]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IntCA --&gt;|sign| ServerCert[Server Cert]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IntCA --&gt;|sign| ClientCert[Client Cert]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|보유| ClientCert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Server --&gt;|보유| ServerCert&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자세한 인증서 체인은 [[TLS]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;사용-시나리오&quot;&gt;사용 시나리오&lt;/h2&gt;
&lt;h3 id=&quot;1-service-mesh-istio-linkerd-consul&quot;&gt;1. Service Mesh (Istio, Linkerd, Consul)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph A[Service A]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        AApp[App]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        AProxy[Sidecar Envoy]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    subgraph B[Service B]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BProxy[Sidecar Envoy]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        BApp[App]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AApp --&gt;|plain HTTP| AProxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    AProxy --&gt;|mTLS auto| BProxy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    BProxy --&gt;|plain HTTP| BApp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;애플리케이션이 mTLS 를 신경 쓰지 않음&lt;/em&gt;. Sidecar 가 자동.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Zero Trust&lt;/em&gt; 네트워크의 토대.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-spiffespire-워크로드-신원&quot;&gt;2. SPIFFE/SPIRE: 워크로드 신원&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;SPIFFE ID = spiffe://example.com/ns/prod/sa/web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;쿠버네티스 ServiceAccount → &lt;em&gt;SVID (SPIFFE Verifiable Identity Document)&lt;/em&gt; → 짧은 mTLS 인증서.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;수동 인증서 발급/배포 없이&lt;/em&gt; SPIRE 가 &lt;em&gt;자동 회전&lt;/em&gt;. 회전 주기는 &lt;em&gt;시간 단위&lt;/em&gt;. 침해 시 영향 최소화.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-기업-vpn--zero-trust&quot;&gt;3. 기업 VPN / Zero Trust&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;BeyondCorp 류 &lt;em&gt;디바이스 인증&lt;/em&gt; + 사용자 인증.&lt;/li&gt;
&lt;li&gt;기기 인증서 + 사용자 OIDC.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-iot--디바이스&quot;&gt;4. IoT / 디바이스&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;수백만 디바이스&lt;/em&gt; 각자 인증서.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;제조 시점에 발급&lt;/em&gt; (factory cert).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;인증서-회전&quot;&gt;인증서 회전&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;gantt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    title 인증서 라이프사이클&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    dateFormat YYYY-MM-DD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    section Cert&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    유효기간 1: 2026-01-01, 90d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    유효기간 2 (회전):  active, 2026-03-15, 90d&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    유효기간 3 (회전):  2026-06-01, 90d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;회전 주기&lt;/th&gt;&lt;th&gt;적합&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1년&lt;/td&gt;&lt;td&gt;legacy 시스템&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;90일&lt;/td&gt;&lt;td&gt;Let’s Encrypt 표준&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;24시간&lt;/td&gt;&lt;td&gt;짧은 SVID (SPIFFE)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1시간&lt;/td&gt;&lt;td&gt;고보안 환경&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;짧을수록 &lt;em&gt;침해 영향 최소화&lt;/em&gt;. &lt;em&gt;자동화 없으면 운영 불가&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;oauth--mtls-rfc-8705&quot;&gt;OAuth + mTLS (RFC 8705)&lt;/h2&gt;
&lt;p&gt;OAuth 의 &lt;em&gt;client_credentials&lt;/em&gt; 에서 &lt;em&gt;client_secret 대신 client cert&lt;/em&gt;. &lt;em&gt;큰 기업 / 금융 API&lt;/em&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service A-&gt;&gt;Auth: POST /token&amp;#x3C;br/&gt;(mTLS client cert)&amp;#x3C;br/&gt;grant_type=client_credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth-&gt;&gt;Auth: cert 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;Service A: access_token (cnf claim 에 cert hash)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service A-&gt;&gt;Service B: Bearer &amp;#x3C;token&gt;&amp;#x3C;br/&gt;(mTLS, 같은 cert)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service B-&gt;&gt;Service B: token 의 cnf == cert hash?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service B--&gt;&gt;Service A: 자원&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인증서 만료 사고&lt;/strong&gt; = 자동화 없으면 &lt;em&gt;언젠가는 만료&lt;/em&gt; → 서비스 다운. &lt;em&gt;모니터링 + 자동 갱신&lt;/em&gt; 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인증서 회전과 사용 &lt;em&gt;동시&lt;/em&gt;&lt;/strong&gt; = 회전 직후 옛 인증서로 검증 실패. &lt;em&gt;grace period&lt;/em&gt; 필요.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRL (Certificate Revocation List) 의 &lt;em&gt;체크 누락&lt;/em&gt;&lt;/strong&gt; = 침해 인증서가 &lt;em&gt;수개월 유효&lt;/em&gt;. 짧은 인증서 (SPIFFE) 가 &lt;em&gt;현실적 답&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mTLS 끝점에 &lt;em&gt;L7 LB&lt;/em&gt;&lt;/strong&gt; = TLS 종료 시점에 &lt;em&gt;클라이언트 인증서 정보 손실&lt;/em&gt;. &lt;code&gt;X-Forwarded-Client-Cert&lt;/code&gt; 같은 헤더로 &lt;em&gt;전달&lt;/em&gt; 또는 &lt;em&gt;passthrough LB&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;인증서-형식&quot;&gt;인증서 형식&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;형식&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.crt&lt;/code&gt; / &lt;code&gt;.pem&lt;/code&gt;&lt;/td&gt;&lt;td&gt;base64 인코딩 X.509 (텍스트)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.der&lt;/code&gt;&lt;/td&gt;&lt;td&gt;binary DER&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.p7b&lt;/code&gt; / &lt;code&gt;.p7c&lt;/code&gt;&lt;/td&gt;&lt;td&gt;PKCS#7 (체인 포함)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.pfx&lt;/code&gt; / &lt;code&gt;.p12&lt;/code&gt;&lt;/td&gt;&lt;td&gt;PKCS#12 (인증서 + private key)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;.key&lt;/code&gt;&lt;/td&gt;&lt;td&gt;private key 만&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[TLS]]&lt;/li&gt;
&lt;li&gt;[[OAuth2]]&lt;/li&gt;
&lt;li&gt;[[Service Mesh]] (Istio, Linkerd)&lt;/li&gt;
&lt;li&gt;[[Kubernetes RBAC]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>mtls</category><category>tls</category><category>security</category><category>service-mesh</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Auth] OAuth 2.0 / 2.1: Authorization Code + PKCE</title><link>https://shinkeonkim.com/wiki/auth-security/oauth2/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/oauth2/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;OAuth 2.0&lt;/strong&gt; (&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6749&quot;&gt;RFC 6749&lt;/a&gt;, 2012) 은 &lt;em&gt;제3자 앱이 사용자 동의로 자원에 접근&lt;/em&gt; 하게 하는 &lt;em&gt;권한 위임 프레임워크&lt;/em&gt;. &lt;em&gt;인증 (authentication)&lt;/em&gt; 이 아니라 &lt;em&gt;인가 (authorization)&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OAuth 2.1&lt;/strong&gt; 은 진행 중인 draft. 옛 flow 정리 + PKCE 의무화.&lt;/p&gt;
&lt;h2 id=&quot;핵심-4-역할&quot;&gt;핵심 4 역할&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User[Resource Owner&amp;#x3C;br/&gt;사용자] --&gt;|동의| Auth[Authorization Server&amp;#x3C;br/&gt;예: Google]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client[Client App&amp;#x3C;br/&gt;예: 우리 앱] --&gt;|토큰 요청| Auth&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth --&gt;|access_token| Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Client --&gt;|토큰 + 요청| Resource[Resource Server&amp;#x3C;br/&gt;예: Google Drive API]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;authorization-code-flow--pkce-표준&quot;&gt;Authorization Code Flow + PKCE (표준)&lt;/h2&gt;
&lt;p&gt;가장 일반적이고 OAuth 2.1 에서 &lt;em&gt;유일하게 권장&lt;/em&gt; 되는 사용자 흐름.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant C as Client&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Auth as Auth Server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant API as Resource Server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: code_verifier (랜덤 43-128자) 생성&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;C: code_challenge = SHA256(verifier) → base64url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;U: 브라우저 → Auth 로 redirect&amp;#x3C;br/&gt;?response_type=code&amp;#x26;client_id=...&amp;#x3C;br/&gt;&amp;#x26;code_challenge=...&amp;#x26;code_challenge_method=S256&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;Auth: 로그인 + 동의&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;U: redirect to Client&amp;#x3C;br/&gt;?code=ABC&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U--&gt;&gt;C: code=ABC 전달&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;Auth: POST /token&amp;#x3C;br/&gt;code=ABC&amp;#x26;code_verifier=&amp;#x3C;원본&gt;&amp;#x26;client_id=...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth-&gt;&gt;Auth: SHA256(verifier) == challenge?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;C: { access_token, refresh_token }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C-&gt;&gt;API: Authorization: Bearer &amp;#x3C;token&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    API--&gt;&gt;C: 자원&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;pkce-가-왜-필수&quot;&gt;PKCE 가 왜 필수?&lt;/h3&gt;
&lt;p&gt;OAuth 2.0 의 옛날 &lt;em&gt;implicit flow&lt;/em&gt; + &lt;em&gt;public client (모바일 / SPA)&lt;/em&gt; 에서 &lt;em&gt;intercept 공격&lt;/em&gt;. &lt;em&gt;code_verifier&lt;/em&gt; 가 &lt;em&gt;동적 공유 secret&lt;/em&gt; 처럼 작용.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;모든 OAuth 클라이언트는 PKCE 필수&lt;/em&gt;. 옛 라이브러리도 &lt;em&gt;반드시 최신 버전&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;다른-flow&quot;&gt;다른 Flow&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Flow&lt;/th&gt;&lt;th&gt;사용&lt;/th&gt;&lt;th&gt;상태&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authorization Code + PKCE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;사용자 로그인 (웹/모바일)&lt;/td&gt;&lt;td&gt;&lt;em&gt;권장&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Client Credentials&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;서버 ↔ 서버 (사용자 없음)&lt;/td&gt;&lt;td&gt;권장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Device Authorization&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;TV / CLI / 헤드리스&lt;/td&gt;&lt;td&gt;권장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Refresh Token&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;access_token 갱신&lt;/td&gt;&lt;td&gt;권장&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Implicit&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;(옛 SPA)&lt;/td&gt;&lt;td&gt;&lt;em&gt;폐기 (OAuth 2.1)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Resource Owner Password&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;password 직접 전달&lt;/td&gt;&lt;td&gt;&lt;em&gt;폐기 (OAuth 2.1)&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;client-credentials-서버끼리&quot;&gt;Client Credentials (서버끼리)&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service A-&gt;&gt;Auth: POST /token&amp;#x3C;br/&gt;client_id + client_secret&amp;#x3C;br/&gt;grant_type=client_credentials&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;Service A: { access_token, expires_in }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service A-&gt;&gt;Service B: Bearer &amp;#x3C;token&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Service B--&gt;&gt;Service A: 자원&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;device-flow&quot;&gt;Device Flow&lt;/h3&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Device-&gt;&gt;Auth: POST /device/code&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Auth--&gt;&gt;Device: { device_code, user_code, verification_uri }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Device-&gt;&gt;User: &quot;다른 기기로 https://x.com/code 가서 ABC123 입력&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User-&gt;&gt;Auth: 브라우저에서 ABC123 입력 + 동의&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    loop poll&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Device-&gt;&gt;Auth: POST /token (device_code)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        Auth--&gt;&gt;Device: 200 (성공) 또는 authorization_pending&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    end&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Apple TV, Roku, GitHub CLI (&lt;code&gt;gh auth login&lt;/code&gt;)&lt;/em&gt; 같은 &lt;em&gt;키보드 없는 기기&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;oauth-20-vs-oauth-21&quot;&gt;OAuth 2.0 vs OAuth 2.1&lt;/h2&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;2.0&lt;/th&gt;&lt;th&gt;2.1&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Implicit flow&lt;/td&gt;&lt;td&gt;허용&lt;/td&gt;&lt;td&gt;&lt;em&gt;제거&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Resource owner password&lt;/td&gt;&lt;td&gt;허용&lt;/td&gt;&lt;td&gt;&lt;em&gt;제거&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PKCE&lt;/td&gt;&lt;td&gt;권장&lt;/td&gt;&lt;td&gt;&lt;em&gt;필수&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bearer token in URL&lt;/td&gt;&lt;td&gt;허용 (?token=)&lt;/td&gt;&lt;td&gt;&lt;em&gt;제거&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Refresh token rotation&lt;/td&gt;&lt;td&gt;옵션&lt;/td&gt;&lt;td&gt;&lt;em&gt;권장&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HTTPS&lt;/td&gt;&lt;td&gt;권장&lt;/td&gt;&lt;td&gt;&lt;em&gt;필수&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;scope&quot;&gt;Scope&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;scope=read:files write:profile&lt;/code&gt; 같이 &lt;em&gt;권한 범위 명시&lt;/em&gt;. Auth Server 가 사용자 동의 화면에 표시.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope=openid profile email     ← OpenID Connect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;scope=read:items write:orders   ← 자원 권한&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;state-parameter-csrf-방어&quot;&gt;State Parameter (CSRF 방어)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /authorize?...&amp;#x26;state=&amp;#x3C;random&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Auth 가 &lt;em&gt;같은 state 를 redirect 에 포함&lt;/em&gt;. 다른 사이트가 &lt;em&gt;위조한 redirect&lt;/em&gt; 를 클라이언트가 &lt;em&gt;거절&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;em&gt;state 검증 누락&lt;/em&gt; = CSRF 공격에 OAuth 흐름 위조 가능. 모든 클라이언트 필수.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;oauth-vs-openid-connect&quot;&gt;OAuth vs OpenID Connect&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;OAuth 2.0&lt;/th&gt;&lt;th&gt;OIDC&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;목적&lt;/td&gt;&lt;td&gt;&lt;em&gt;권한 위임&lt;/em&gt; (자원 접근)&lt;/td&gt;&lt;td&gt;&lt;em&gt;인증&lt;/em&gt; (누구인지)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;토큰&lt;/td&gt;&lt;td&gt;access_token&lt;/td&gt;&lt;td&gt;+ &lt;em&gt;id_token&lt;/em&gt; (JWT)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;User info&lt;/td&gt;&lt;td&gt;자원 API 로 별도 조회&lt;/td&gt;&lt;td&gt;id_token 안에&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;표준화&lt;/td&gt;&lt;td&gt;기본&lt;/td&gt;&lt;td&gt;OAuth 2.0 위&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;자세한 건 [[OpenID Connect]] 참고.&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;redirect_uri 검증 부재&lt;/strong&gt; = open redirect 공격으로 &lt;em&gt;코드 탈취&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;state 미사용&lt;/strong&gt; = CSRF.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PKCE 없이 public client&lt;/strong&gt; = &lt;em&gt;intercept 공격&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;access_token 을 URL 에&lt;/strong&gt; = 로그 / referrer 로 노출.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;refresh_token 영구&lt;/strong&gt; = 탈취 시 영구 침해. &lt;em&gt;rotation&lt;/em&gt; (한 번 쓰면 새 발급 + 옛 무효화).&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[JWT]]&lt;/li&gt;
&lt;li&gt;[[OpenID Connect]]&lt;/li&gt;
&lt;li&gt;[[SAML]]&lt;/li&gt;
&lt;li&gt;[[mTLS]]&lt;/li&gt;
&lt;li&gt;[[CSRF]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>oauth</category><category>auth</category><category>security</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Auth] OpenID Connect (OIDC): OAuth 위의 인증 레이어</title><link>https://shinkeonkim.com/wiki/auth-security/openid-connect/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/openid-connect/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;OpenID Connect (OIDC)&lt;/strong&gt; 는 &lt;em&gt;OAuth 2.0 위에 얹은 인증 (authentication) 표준&lt;/em&gt;. OAuth 가 &lt;em&gt;권한 위임 (authorization)&lt;/em&gt;, OIDC 가 &lt;em&gt;“누구인지”&lt;/em&gt; 알려준다.&lt;/p&gt;
&lt;p&gt;핵심 추가:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;id_token&lt;/code&gt;&lt;/strong&gt; (JWT): 사용자 식별 정보&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/userinfo&lt;/code&gt;&lt;/strong&gt; endpoint: 추가 프로필 정보&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discovery&lt;/strong&gt;: &lt;code&gt;.well-known/openid-configuration&lt;/code&gt; 으로 자동 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scope &lt;code&gt;openid&lt;/code&gt;&lt;/strong&gt;: OIDC 활성화 신호&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;흐름&quot;&gt;흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant App as Client App&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant Idp as OIDC Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over App: scope=openid profile email&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;U: redirect to Idp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;Idp: 로그인 + 동의&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idp--&gt;&gt;App: code (Authorization Code)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Idp: POST /token&amp;#x3C;br/&gt;code + verifier&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idp--&gt;&gt;App: { access_token, id_token, refresh_token }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;App: id_token (JWT) 검증&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App-&gt;&gt;Idp: GET /userinfo (옵션)&amp;#x3C;br/&gt;Bearer access_token&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idp--&gt;&gt;App: { sub, name, email, picture, ... }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;id_token-의-표준-클레임&quot;&gt;id_token 의 표준 클레임&lt;/h2&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;클레임&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;iss&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Issuer (Idp URL)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;sub&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Subject (사용자 ID, &lt;em&gt;영구 식별자&lt;/em&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;aud&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Audience (Client ID)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;exp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;만료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;iat&lt;/code&gt;&lt;/td&gt;&lt;td&gt;발급 시각&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;auth_time&lt;/code&gt;&lt;/td&gt;&lt;td&gt;사용자가 &lt;em&gt;마지막으로 인증한&lt;/em&gt; 시각&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;nonce&lt;/code&gt;&lt;/td&gt;&lt;td&gt;replay 방지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;acr&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Authentication Context Class Reference&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;amr&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Authentication Methods (&lt;code&gt;[&quot;pwd&quot;, &quot;mfa&quot;]&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;azp&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Authorized Party&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;프로필&lt;/td&gt;&lt;td&gt;&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;given_name&lt;/code&gt;, &lt;code&gt;family_name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;email_verified&lt;/code&gt;, &lt;code&gt;picture&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;discovery-well-knownopenid-configuration&quot;&gt;Discovery: &lt;code&gt;.well-known/openid-configuration&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://accounts.google.com/.well-known/openid-configuration&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;issuer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://accounts.google.com&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;authorization_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://accounts.google.com/o/oauth2/v2/auth&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;token_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://oauth2.googleapis.com/token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;userinfo_endpoint&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://openidconnect.googleapis.com/v1/userinfo&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;jwks_uri&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://www.googleapis.com/oauth2/v3/certs&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;scopes_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;openid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;response_types_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;code&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;id_token&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;...&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;id_token_signing_alg_values_supported&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;RS256&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#FDAEB7;font-style:italic&quot;&gt;  ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;클라이언트는 &lt;em&gt;Idp URL&lt;/em&gt; 하나만 알면 &lt;em&gt;모든 endpoint 자동 발견&lt;/em&gt;. 신규 OIDC Provider 통합이 &lt;em&gt;1줄&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;jwks-json-web-key-set&quot;&gt;JWKS (JSON Web Key Set)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; https://www.googleapis.com/oauth2/v3/certs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;  &quot;keys&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;kty&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;RSA&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;alg&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;RS256&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;use&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;sig&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;kid&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;abc123&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;n&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#79B8FF&quot;&gt;      &quot;e&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;AQAB&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  ]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Idp 가 &lt;em&gt;공개키 공개&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;클라이언트가 id_token 의 &lt;code&gt;kid&lt;/code&gt; 로 &lt;em&gt;해당 키 찾아 검증&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;키 회전&lt;/em&gt; 시 &lt;em&gt;복수 키 동시 공존&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;oidc-흐름-종류&quot;&gt;OIDC 흐름 종류&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Flow&lt;/th&gt;&lt;th&gt;응답 타입&lt;/th&gt;&lt;th&gt;사용&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Authorization Code Flow + PKCE&lt;/td&gt;&lt;td&gt;&lt;code&gt;response_type=code&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;em&gt;권장&lt;/em&gt; (모든 클라이언트)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Implicit Flow&lt;/td&gt;&lt;td&gt;&lt;code&gt;response_type=id_token token&lt;/code&gt;&lt;/td&gt;&lt;td&gt;폐기&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Hybrid Flow&lt;/td&gt;&lt;td&gt;&lt;code&gt;response_type=code id_token&lt;/code&gt;&lt;/td&gt;&lt;td&gt;일부 경우&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;sso-single-sign-on&quot;&gt;SSO (Single Sign-On)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User --&gt; App1[App 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User --&gt; App2[App 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User --&gt; App3[App 3]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App1 --&gt; Idp[OIDC Provider]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App2 --&gt; Idp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    App3 --&gt; Idp&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Idp --&gt; Login[중앙 로그인 1회]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한 번 로그인 → 여러 앱 &lt;em&gt;자동 인증&lt;/em&gt;. &lt;em&gt;기업 SSO&lt;/em&gt; (Okta, Auth0, Keycloak) 의 토대.&lt;/p&gt;
&lt;h2 id=&quot;사용-라이브러리&quot;&gt;사용 라이브러리&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;언어&lt;/th&gt;&lt;th&gt;라이브러리&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Node&lt;/td&gt;&lt;td&gt;passport-openidconnect, openid-client&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Python&lt;/td&gt;&lt;td&gt;authlib, python-jose&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Java&lt;/td&gt;&lt;td&gt;spring-security-oauth2-client, Nimbus JOSE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Go&lt;/td&gt;&lt;td&gt;go-oidc, oidc-mw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ruby&lt;/td&gt;&lt;td&gt;omniauth-openid-connect&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;oidc-vs-oauth-vs-saml&quot;&gt;OIDC vs OAuth vs SAML&lt;/h2&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;OAuth 2.0&lt;/th&gt;&lt;th&gt;OIDC&lt;/th&gt;&lt;th&gt;SAML 2.0&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;목적&lt;/td&gt;&lt;td&gt;권한 위임&lt;/td&gt;&lt;td&gt;인증&lt;/td&gt;&lt;td&gt;인증 + 권한&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;토큰&lt;/td&gt;&lt;td&gt;access_token&lt;/td&gt;&lt;td&gt;+ id_token (JWT)&lt;/td&gt;&lt;td&gt;SAML Assertion (XML)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이로드&lt;/td&gt;&lt;td&gt;bearer&lt;/td&gt;&lt;td&gt;JWT&lt;/td&gt;&lt;td&gt;XML&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;모바일 친화&lt;/td&gt;&lt;td&gt;좋음&lt;/td&gt;&lt;td&gt;&lt;em&gt;최고&lt;/em&gt;&lt;/td&gt;&lt;td&gt;떨어짐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;기업&lt;/td&gt;&lt;td&gt;일부&lt;/td&gt;&lt;td&gt;모던 표준&lt;/td&gt;&lt;td&gt;옛 표준, &lt;em&gt;여전히 많음&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;id_token 만 신뢰&lt;/strong&gt; = &lt;code&gt;aud&lt;/code&gt;, &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt; 모두 검증해야 함.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWKS &lt;em&gt;매 요청&lt;/em&gt; fetch&lt;/strong&gt; = 캐싱 필요 (보통 24h). 단 &lt;em&gt;key rotation&lt;/em&gt; 시 빠르게 갱신.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sub 변경&lt;/strong&gt; = 같은 Idp 의 같은 사용자는 &lt;em&gt;&lt;code&gt;sub&lt;/code&gt; 가 영구 동일&lt;/em&gt;. 다른 Idp 의 같은 이메일은 &lt;em&gt;다른 sub&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;email 만 보고 사용자 식별&lt;/strong&gt; = 이메일 변경 가능. &lt;em&gt;항상 &lt;code&gt;sub&lt;/code&gt; 로 매핑&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[OAuth2]]&lt;/li&gt;
&lt;li&gt;[[JWT]]&lt;/li&gt;
&lt;li&gt;[[SAML]]&lt;/li&gt;
&lt;li&gt;[[Session Cookie]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>oidc</category><category>oauth</category><category>auth</category><category>sso</category><category>backend</category><author>koa (김신건)</author></item><item><title>[Auth] SAML 2.0: XML 기반 기업 SSO</title><link>https://shinkeonkim.com/wiki/auth-security/saml/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/saml/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SAML 2.0&lt;/strong&gt; (Security Assertion Markup Language) 는 &lt;em&gt;XML 기반 기업 SSO&lt;/em&gt; 표준. 2005년 OASIS 표준화. 모바일 친화도 / 단순성에서 OIDC 에 밀리지만 &lt;em&gt;기업 / SaaS 통합의 &lt;em&gt;옛 표준&lt;/em&gt; 으로 여전히 광범위&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;핵심-역할&quot;&gt;핵심 역할&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User --&gt; SP[Service Provider&amp;#x3C;br/&gt;예: Salesforce]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    User --&gt; IdP[Identity Provider&amp;#x3C;br/&gt;예: Okta]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SP --&gt; IdP&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IdP --&gt; SP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sp-initiated-sso-흐름&quot;&gt;SP-initiated SSO 흐름&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    autonumber&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant U as User&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant SP as Service Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    participant IdP as Identity Provider&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;SP: GET /app (로그인 안 됨)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SP-&gt;&gt;U: 302 → IdP&amp;#x3C;br/&gt;SAMLRequest (AuthnRequest, base64+deflate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;IdP: SAMLRequest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IdP-&gt;&gt;U: 로그인 화면&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;IdP: id + password&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IdP-&gt;&gt;IdP: SAML Response 생성&amp;#x3C;br/&gt;(Assertion + 서명)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    IdP-&gt;&gt;U: HTML form 자동 submit&amp;#x3C;br/&gt;SAMLResponse&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    U-&gt;&gt;SP: POST /acs&amp;#x3C;br/&gt;SAMLResponse&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SP-&gt;&gt;SP: Assertion 검증 (서명 + 유효시간)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    SP--&gt;&gt;U: 세션 시작&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;SP-initiated = &lt;em&gt;서비스 (SaaS) 가 먼저 redirect&lt;/em&gt;. IdP-initiated = &lt;em&gt;IdP 의 사용자 포털에서 클릭&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;saml-assertion-xml&quot;&gt;SAML Assertion (XML)&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;xml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Assertion&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; ID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; IssueInstant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:00:00Z&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Issuer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://idp.example.com&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Issuer&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;ds:Signature&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;...&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;ds:Signature&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Subject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:NameID&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Format&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;...:emailAddress&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;koa@example.com&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:NameID&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:SubjectConfirmation&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Method&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;bearer&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:SubjectConfirmationData&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        Recipient&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;https://sp.example.com/acs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        NotOnOrAfter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:05:00Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;        InResponseTo&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;&amp;#x3C;원래 AuthnRequest ID&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:SubjectConfirmation&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Subject&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Conditions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    NotBefore&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:00:00Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#B392F0&quot;&gt;    NotOnOrAfter&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:05:00Z&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AudienceRestriction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Audience&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;https://sp.example.com&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Audience&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AudienceRestriction&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Conditions&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnStatement&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; AuthnInstant&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;2026-06-25T12:00:00Z&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnContextClassRef&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;        urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnContextClassRef&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnContext&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AuthnStatement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeStatement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Attribute&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;koa@example.com&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Attribute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Attribute&lt;/span&gt;&lt;span style=&quot;color:#B392F0&quot;&gt; Name&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt;&quot;department&quot;&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;      &amp;#x3C;&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;engineering&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeValue&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Attribute&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;  &amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:AttributeStatement&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&amp;#x3C;/&lt;/span&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;saml:Assertion&lt;/span&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;metadata-교환&quot;&gt;Metadata 교환&lt;/h2&gt;
&lt;p&gt;SP 와 IdP 가 &lt;em&gt;XML metadata 파일&lt;/em&gt; 로 사전 합의:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;SP metadata&lt;/th&gt;&lt;th&gt;IdP metadata&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Entity ID&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://sp.example.com&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://idp.example.com&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ACS URL&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://sp.example.com/acs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SSO URL&lt;/td&gt;&lt;td&gt;-&lt;/td&gt;&lt;td&gt;&lt;code&gt;https://idp.example.com/sso&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;SLO URL&lt;/td&gt;&lt;td&gt;logout&lt;/td&gt;&lt;td&gt;logout&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Certificate&lt;/td&gt;&lt;td&gt;서명 검증용 공개키&lt;/td&gt;&lt;td&gt;동일&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;대부분의 SaaS 는 &lt;em&gt;URL 한 줄로 metadata 자동 교환&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;saml-vs-oidc&quot;&gt;SAML vs OIDC&lt;/h2&gt;


















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;SAML 2.0&lt;/th&gt;&lt;th&gt;OIDC&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;출시&lt;/td&gt;&lt;td&gt;2005&lt;/td&gt;&lt;td&gt;2014&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;페이로드&lt;/td&gt;&lt;td&gt;XML&lt;/td&gt;&lt;td&gt;JSON (JWT)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;크기&lt;/td&gt;&lt;td&gt;큼 (수 KB)&lt;/td&gt;&lt;td&gt;작음&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;모바일&lt;/td&gt;&lt;td&gt;떨어짐&lt;/td&gt;&lt;td&gt;&lt;em&gt;우수&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;디버깅&lt;/td&gt;&lt;td&gt;복잡 (XML 서명)&lt;/td&gt;&lt;td&gt;단순&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;기업 SSO&lt;/td&gt;&lt;td&gt;&lt;em&gt;전통적&lt;/em&gt;&lt;/td&gt;&lt;td&gt;부상 중&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MFA / step-up&lt;/td&gt;&lt;td&gt;가능 (acr)&lt;/td&gt;&lt;td&gt;가능 (acr)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;사용자 의도&lt;/td&gt;&lt;td&gt;토큰 발급 후 &lt;em&gt;세션&lt;/em&gt;&lt;/td&gt;&lt;td&gt;토큰 자체 &lt;em&gt;반복 사용&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;2026 시점&lt;/em&gt;: 모바일 / 소비자 앱 = OIDC. &lt;em&gt;기업 SaaS 통합&lt;/em&gt; (Workday, Salesforce, ServiceNow, Slack 등) = 여전히 SAML 이 &lt;em&gt;압도적&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;단점--함정&quot;&gt;단점 / 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;XML Canonicalization 의 함정&lt;/strong&gt; = &lt;em&gt;공백 처리&lt;/em&gt; 까지 정확해야 서명 검증. 라이브러리 버그 다수 (XSW 공격).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시계 어긋남&lt;/strong&gt; = NotBefore / NotOnOrAfter 검증에서 &lt;em&gt;수십 초 clock skew&lt;/em&gt; 가 문제. NTP 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메시지 크기&lt;/strong&gt; = HTTP-Redirect 으로 보내는 인코딩된 SAMLRequest 가 URL 길이 한계 초과 가능 → HTTP-POST binding 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assertion 의 &lt;em&gt;audience&lt;/em&gt; 검증 누락&lt;/strong&gt; = &lt;em&gt;다른 SP 의 assertion&lt;/em&gt; 이 재사용 가능.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;보안-assertion-검증-체크리스트&quot;&gt;보안: Assertion 검증 체크리스트&lt;/h2&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;검증&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Signature&lt;/td&gt;&lt;td&gt;XML-DSig 로 IdP 공개키 검증&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Audience&lt;/td&gt;&lt;td&gt;&lt;em&gt;내가 그 audience 인지&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NotBefore / NotOnOrAfter&lt;/td&gt;&lt;td&gt;유효 시간&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Issuer&lt;/td&gt;&lt;td&gt;신뢰하는 IdP 인지&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;InResponseTo&lt;/td&gt;&lt;td&gt;원래 보낸 AuthnRequest ID 와 일치&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replay&lt;/td&gt;&lt;td&gt;NotOnOrAfter 까지 &lt;em&gt;받은 ID 중복 거절&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[OAuth2]]&lt;/li&gt;
&lt;li&gt;[[OpenID Connect]]&lt;/li&gt;
&lt;li&gt;[[JWT]]&lt;/li&gt;
&lt;li&gt;[[Session Cookie]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>saml</category><category>sso</category><category>enterprise</category><category>auth</category><category>security</category><author>koa (김신건)</author></item><item><title>[Auth] Session Cookie: HttpOnly, SameSite, Secure flag</title><link>https://shinkeonkim.com/wiki/auth-security/session-cookie/</link><guid isPermaLink="true">https://shinkeonkim.com/wiki/auth-security/session-cookie/</guid><pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;정의&quot;&gt;정의&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Session Cookie&lt;/strong&gt; 는 &lt;em&gt;서버가 발급한 임의 ID 를 쿠키에 보관&lt;/em&gt;. 매 요청 자동 첨부. &lt;em&gt;서버 측 세션 store&lt;/em&gt; (Redis, DB) 와 짝.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!IMPORTANT]
&lt;em&gt;JWT 와의 비교&lt;/em&gt;: session = &lt;em&gt;stateful&lt;/em&gt;, JWT = &lt;em&gt;stateless&lt;/em&gt;. 작은-중간 모놀리스에서는 &lt;em&gt;session 이 더 단순 + 즉시 revoke 가능&lt;/em&gt;. 분산 환경에서는 JWT.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;쿠키-속성&quot;&gt;쿠키 속성&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;http&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#85E89D&quot;&gt;Set-Cookie&lt;/span&gt;&lt;span style=&quot;color:#F97583&quot;&gt;:&lt;/span&gt;&lt;span style=&quot;color:#9ECBFF&quot;&gt; SID=abc123; Path=/; Domain=example.com;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Secure; HttpOnly; SameSite=Lax;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;            Max-Age=3600&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;속성&lt;/th&gt;&lt;th&gt;의미&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;HttpOnly&lt;/code&gt;&lt;/td&gt;&lt;td&gt;JS 에서 접근 불가 (XSS 방어)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Secure&lt;/code&gt;&lt;/td&gt;&lt;td&gt;HTTPS 만&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;SameSite=Strict|Lax|None&lt;/code&gt;&lt;/td&gt;&lt;td&gt;cross-site 동작 제어&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Path&lt;/code&gt;&lt;/td&gt;&lt;td&gt;URL prefix&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Domain&lt;/code&gt;&lt;/td&gt;&lt;td&gt;도메인 범위&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Max-Age&lt;/code&gt; / &lt;code&gt;Expires&lt;/code&gt;&lt;/td&gt;&lt;td&gt;만료&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Partitioned&lt;/code&gt; &lt;em&gt;(2024+)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;3rd-party cookie 격리&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;__Host-&lt;/code&gt; prefix&lt;/td&gt;&lt;td&gt;Secure + Path=/ + Domain 없음 강제&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;samesite-3-모드&quot;&gt;SameSite 3 모드&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart TB&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Strict[&quot;Strict&amp;#x3C;br/&gt;같은 site 만 cookie 전송&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lax[&quot;Lax (기본)&amp;#x3C;br/&gt;top-level GET 만 허용&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    None[&quot;None&amp;#x3C;br/&gt;모든 cross-site 허용 (+Secure 필수)&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Strict --&gt; S1[&quot;가장 안전&amp;#x3C;br/&gt;외부 링크 → 로그인 풀림&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Lax --&gt; L1[&quot;보안 + UX 균형&amp;#x3C;br/&gt;대부분 권장&quot;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    None --&gt; N1[&quot;3rd-party 통합&amp;#x3C;br/&gt;(예: 임베드)&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Strict&lt;/th&gt;&lt;th&gt;Lax&lt;/th&gt;&lt;th&gt;None&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;직접 입력 (주소창)&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;외부 사이트 링크 클릭&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;O (top-level GET)&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;외부 폼 POST&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;#x3C;img src=&quot;other.com&quot;&gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;iframe POST&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;X&lt;/td&gt;&lt;td&gt;O&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;2020 부터 &lt;em&gt;Chrome 의 기본값 = Lax&lt;/em&gt;. 명시하지 않은 옛 쿠키도 &lt;em&gt;Lax&lt;/em&gt; 적용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;session-store-패턴&quot;&gt;Session Store 패턴&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;flowchart LR&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C[Client] --&gt;|SID=abc| Web[Web App 1]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    C2[Client] --&gt;|SID=abc| Web2[Web App 2]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Web --&gt; Store[(Session Store&amp;#x3C;br/&gt;Redis)]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Web2 --&gt; Store&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;세션 store 옵션:&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Store&lt;/th&gt;&lt;th&gt;특징&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;메모리&lt;/td&gt;&lt;td&gt;단일 노드, 재시작 시 손실&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;파일&lt;/td&gt;&lt;td&gt;단일 노드&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Redis&lt;/td&gt;&lt;td&gt;&lt;em&gt;분산 표준&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DB (Postgres / MySQL)&lt;/td&gt;&lt;td&gt;단순 / 큰 부담&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;클라이언트 (encrypted cookie)&lt;/td&gt;&lt;td&gt;&lt;em&gt;stateless 처럼&lt;/em&gt;. Express의 cookie-session, Rails encrypted cookie&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;stateless-vs-stateful&quot;&gt;Stateless vs Stateful&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;항목&lt;/th&gt;&lt;th&gt;Session (cookie)&lt;/th&gt;&lt;th&gt;JWT&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;서버 store&lt;/td&gt;&lt;td&gt;필수&lt;/td&gt;&lt;td&gt;불필요&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Revoke&lt;/td&gt;&lt;td&gt;&lt;em&gt;즉시 (DB 삭제)&lt;/em&gt;&lt;/td&gt;&lt;td&gt;만료까지 어려움&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cookie 자동 첨부&lt;/td&gt;&lt;td&gt;&lt;em&gt;예&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Bearer header 수동&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CSRF&lt;/td&gt;&lt;td&gt;&lt;em&gt;위험&lt;/em&gt; (SameSite 로 완화)&lt;/td&gt;&lt;td&gt;&lt;em&gt;덜 위험&lt;/em&gt; (헤더면)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;XSS&lt;/td&gt;&lt;td&gt;&lt;em&gt;덜 위험&lt;/em&gt; (HttpOnly)&lt;/td&gt;&lt;td&gt;localStorage 면 &lt;em&gt;위험&lt;/em&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;보안-체크리스트&quot;&gt;보안 체크리스트&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ HttpOnly                (XSS 방어)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ Secure                  (HTTPS 만)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ SameSite=Lax 이상       (CSRF 완화)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ session 만료 짧게        (15-60분 inactive timeout)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ session rotation        (privilege escalation 시 새 SID)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ session_id 충분히 랜덤   (128+ bits)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;✓ 로그아웃 시 server-side 삭제&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;session-fixation&quot;&gt;Session Fixation&lt;/h2&gt;
&lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;sequenceDiagram&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Attacker-&gt;&gt;Site: GET /login (SID=ATTACKER123)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Site--&gt;&gt;Attacker: 응답&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Attacker-&gt;&gt;Victim: &quot;이 링크로 로그인해&quot; (SID=ATTACKER123 박힌 링크)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Victim-&gt;&gt;Site: 로그인 + SID=ATTACKER123 유지&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Site-&gt;&gt;Site: 인증 성공, SID=ATTACKER123 가 권한 가짐&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Attacker-&gt;&gt;Site: SID=ATTACKER123 으로 접근&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#E1E4E8&quot;&gt;    Note over Attacker,Site: Victim 의 권한 행사&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;방어&lt;/strong&gt;: &lt;em&gt;로그인 직후 새 SID 발급&lt;/em&gt;. 옛 SID 무효화.&lt;/p&gt;
&lt;h2 id=&quot;흔한-함정&quot;&gt;흔한 함정&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;HttpOnly&lt;/code&gt; 없음&lt;/strong&gt; = XSS 1줄로 세션 탈취.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Secure&lt;/code&gt; 없음&lt;/strong&gt; = HTTP → HTTPS 전환에서 &lt;em&gt;평문 노출&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SameSite&lt;/code&gt; 미설정&lt;/strong&gt; = 옛 브라우저는 None 처럼 동작 → CSRF.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션 &lt;em&gt;영구&lt;/em&gt;&lt;/strong&gt; = Max-Age 없이 영구 쿠키. 사용자 비활성 후에도 위험.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;로그아웃이 &lt;em&gt;cookie 만 삭제&lt;/em&gt;&lt;/strong&gt; = server-side store 에 그대로. 토큰이 &lt;em&gt;재사용 가능&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;관련-위키&quot;&gt;관련 위키&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[[JWT]]&lt;/li&gt;
&lt;li&gt;[[CSRF]]&lt;/li&gt;
&lt;li&gt;[[CORS]]&lt;/li&gt;
&lt;li&gt;[[Redis Cache Patterns]]&lt;/li&gt;
&lt;li&gt;[[Sticky Session]]&lt;/li&gt;
&lt;/ul&gt;</content:encoded><category>session</category><category>cookie</category><category>auth</category><category>security</category><category>backend</category><author>koa (김신건)</author></item></channel></rss>