<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>중꺾마</title>
    <link>https://okimaru.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 11 Apr 2026 02:05:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Z_Z</managingEditor>
    <image>
      <title>중꺾마</title>
      <url>https://tistory1.daumcdn.net/tistory/4920808/attach/2b80a4da6e464841ac074c0e2c1f5ebb</url>
      <link>https://okimaru.tistory.com</link>
    </image>
    <item>
      <title>async await 사용 시 promise 의 resolve 와 return; 차이</title>
      <link>https://okimaru.tistory.com/entry/async-await-%EC%82%AC%EC%9A%A9-%EC%8B%9C-promise-%EC%9D%98-resolve-%EC%99%80-return-%EC%B0%A8%EC%9D%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;new Promise 로 return 을 하는 함수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise 함수 내부에서 jquery 의 ajax 를 통해 api 요청을 한 후 count 값이 0일 경우 &quot;return;&quot; 을 사용했더니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;promise 를 await 로 호출한 부분 이후의 로직이 실행되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 new Promise 가 resolve 또는 reject 를 줄 때 까지 await 는 계속 대기하고 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 로직이 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;new Promise 함수 예시&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773117363558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function checkData() {
    return new Promise((resolve, reject) =&amp;gt; {
        $.ajax({
            url: '/api/data',
            success: (response) =&amp;gt; {
                if (response.dataCnt === 0) {
                    return;  // ❌ Promise resolve 안됨, await 대기
                }
                resolve(response);  // ✅ 올바른 방법
            }
        });
    });
}

// 호출
async function main() {
    const result = await checkData();  // dataCnt=0이면 여기서 영원히 대기!
    console.log('이 코드는 실행 안됨');  // ❌
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결 방법 3가지&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. resolve(undefined) 또는 return undefined;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773117564562&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;success: (response) =&amp;gt; {
    if (response.dataCnt === 0) {
        resolve();  // ✅ 빈 값으로 resolve
        // 또는 return undefined;  // ✅ Promise가 undefined로 resolve됨
    } else {
        resolve(response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 조건부 resolve(권장)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773117701978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;success: (response) =&amp;gt; {
    if (response.dataCnt === 0) {
        resolve({ dataCnt: 0 });  // 명시적 상태 전달
        return;
    }
    resolve(response);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return; 이 아닌 resolve&amp;nbsp; 상태 전달을 통해 await 에게 알려준다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;return; 을 하게 되면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;함수만 종료되고 await 는 계속 대기상태에 빠진다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. async/await 으로 리팩토링(가장 깔끔)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773117866545&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function checkData() {
    const response = await $.ajax({ url: '/api/data' });
    if (response.dataCnt === 0) {
        return { dataCnt: 0 };  // ✅ 자동으로 Promise resolve됨
    }
    return response;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3번째 방법은 new Promise 가 아닌 await + ajax 방식으로 바로 사용하는게 깔끔하다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;$.ajax() 는 Promise 를 반환하기 때문에 굳이 new Promise 를 사용할 필요가 없다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;return 하면 자동으로 Promise 가 resolve 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;new Promise 는 jQuery 이전 XMLHttpRequest 시대에 필요했던 패턴&lt;/span&gt;&lt;/b&gt;이라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;호출 측 처리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773120405515&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function main() {
    const result = await checkData();
    if (result.dataCnt === 0) {
        console.log('데이터 없음');
        return;  // 여기서도 안전
    }
    console.log('데이터 처리:', result);  // ✅ 정상 실행
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 await 로 요청했을 때 정상적으로 처리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript/javascript</category>
      <category>$.ajax</category>
      <category>ajax</category>
      <category>javascript</category>
      <category>jquery</category>
      <category>new Promise</category>
      <category>reject</category>
      <category>resolve</category>
      <category>spring boot2</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/478</guid>
      <comments>https://okimaru.tistory.com/entry/async-await-%EC%82%AC%EC%9A%A9-%EC%8B%9C-promise-%EC%9D%98-resolve-%EC%99%80-return-%EC%B0%A8%EC%9D%B4#entry478comment</comments>
      <pubDate>Tue, 10 Mar 2026 14:38:39 +0900</pubDate>
    </item>
    <item>
      <title>img 태그 src 속성 아이폰 브라우저 CORS 문제 해결</title>
      <link>https://okimaru.tistory.com/entry/img-%ED%83%9C%EA%B7%B8-src-%EC%86%8D%EC%84%B1-%EC%95%84%EC%9D%B4%ED%8F%B0-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-CORS-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot, Thymeleaf 환경에서 URL 자체를 넘겨 HTML 의 img src 속성에 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근한 URL 과 불러오는 이미지 URL 자체의 도메인이 달랐기 때문에 CORS 문제가 터졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 폰에서는 이미지 자체를 잘 불러왔으나 아이폰에서는 안나와서 찾아보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 의 브라우저는 CORS 정책이 엄격하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 여러가지 방법을 찾아보다가 가장 안정적이고 쉬운 방법은 backgroud-image css 설정을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용해 화면에 보여주는걸로 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 시도했던 방법은 &amp;lt;object&amp;gt; 태그다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Object 태그 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773029735772&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 기존 object에 class 추가 --&amp;gt;
&amp;lt;object class=&quot;product-image thumbnail&quot; 
        data=&quot;https://www.naver.com/image.jpg&quot; 
        type=&quot;image/jpeg&quot; 
        width=&quot;300&quot; height=&quot;200&quot;&amp;gt;
  &amp;lt;img src=&quot;https://www.naver.com/image.jpg&quot; alt=&quot;대체 이미지&quot;&amp;gt;
&amp;lt;/object&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CSS background-image 사용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773029844541&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 이렇게 하신 거죠? --&amp;gt;
&amp;lt;span th:style=&quot;'background-image: url(' + ${product.iconPath} + ')'&quot;
      class=&quot;icon-span&quot;&amp;gt;
&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&amp;lt;object&amp;gt; VS span[background-image] 차이점 정리&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 항목 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;&amp;lt;object&amp;gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; span + background-image &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;브라우저 CORS 검사&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;엄격 (iOS에서 차단)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;없음&lt;/b&gt; (CSS 백그라운드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;JS DOM 조작&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;내부 콘텐츠로 childNodes 에러&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;없음&lt;/b&gt; (단순 CSS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;JS 에러 발생&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;null.childNodes 에러 가능&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;0% 발생&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;별도 리소스 로드&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;CSS 단일 요청&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;iOS Safari 호환&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;SVG에서 문제 잦음&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;완벽&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;캐싱&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;별도 이미지 캐시&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;CSS 배경화면 캐시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;background-image 장점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;CORS 우회 : background-image 는 &amp;lt;img&amp;gt; 처럼 CORS 검사 안받는다.&lt;/li&gt;
&lt;li&gt;JS 안전 : DOM 조작 없어서 childNodes 에러 0%&lt;/li&gt;
&lt;li&gt;iOS 완벽 : Safari / Chrome 둘 다 문제없음&lt;/li&gt;
&lt;li&gt;간단 : Thymeleaf 한 줄로 해결&lt;/li&gt;
&lt;li&gt;유연 : hover, active 등 CSS 효과 무한&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;성능 최적화 팁&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773030071733&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span th:style=&quot;'background-image: url(' + ${product.iconPath} + ');background-size:contain'&quot;
      class=&quot;icon-span&quot;
      th:title=&quot;${product.name}&quot;
      th:attr=&quot;data-icon=${product.iconPath}&quot;&amp;gt;
&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span + background-image 는 실무에서 크로스 브라우저 이미지 표시의 고전적인 최선책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS 에러도 없고 iOS 도 완벽하고 유지보수도 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>HTML</category>
      <category>background-image</category>
      <category>Spring</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/477</guid>
      <comments>https://okimaru.tistory.com/entry/img-%ED%83%9C%EA%B7%B8-src-%EC%86%8D%EC%84%B1-%EC%95%84%EC%9D%B4%ED%8F%B0-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-CORS-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0#entry477comment</comments>
      <pubDate>Mon, 9 Mar 2026 13:21:42 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security OAuth2.0</title>
      <link>https://okimaru.tistory.com/entry/OIDC-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%B4%EB%9E%80</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1️⃣ OAuth 2.0이란 무엇인가?&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 2.0은 권한 위임(Authorization) 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,   사용자의 비밀번호를 공유하지 않고&amp;nbsp;   Access Token 을 이용해   다른 서버의 리소스(API) 에 접근하도록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하는 표준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsM7Pd/dJMcacCe7k0/MORfGRzvcqOO49l3wVpzkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsM7Pd/dJMcacCe7k0/MORfGRzvcqOO49l3wVpzkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsM7Pd/dJMcacCe7k0/MORfGRzvcqOO49l3wVpzkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsM7Pd%2FdJMcacCe7k0%2FMORfGRzvcqOO49l3wVpzkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;432&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8xyqd/dJMcacbatTs/0KwkIvypsUnBLO2IxxYp00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8xyqd/dJMcacbatTs/0KwkIvypsUnBLO2IxxYp00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8xyqd/dJMcacbatTs/0KwkIvypsUnBLO2IxxYp00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8xyqd%2FdJMcacbatTs%2F0KwkIvypsUnBLO2IxxYp00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;586&quot; height=&quot;429&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;OAuth 2.0이 해결하는 문제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 어떤 사이트에서 이런 버튼을 본적 있을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google 로그인&lt;/li&gt;
&lt;li&gt;Kakao 로그인&lt;/li&gt;
&lt;li&gt;GitHub 로그인&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 사용자는 아이디/비밀번호를 그 서비스에 직접 입력하지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 인증 서버가 대신 인증을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 구조는 이렇게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #666666;&quot;&gt;사용자&amp;nbsp;&amp;rarr;&amp;nbsp;내&amp;nbsp;서비스&amp;nbsp;&amp;rarr;&amp;nbsp;Google&amp;nbsp;로그인&amp;nbsp;&amp;rarr;&amp;nbsp;Google&amp;nbsp;인증&amp;nbsp;&amp;rarr;&amp;nbsp;내&amp;nbsp;서비스&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 서비스는 사용자의 비밀번호를 몰라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google 이 인증한 사용자라는 것만 확인하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이것이 &lt;b&gt;OAuth 2.0의 핵심 개념 = 권한 위임(Authorization Delegation)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 2️⃣ OAuth 2.0 주요 구성요소 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center; width: 27.093%;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center; width: 72.7907%;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.093%;&quot;&gt;Resource Owner&lt;/td&gt;
&lt;td style=&quot;width: 72.7907%;&quot;&gt;사용자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.093%;&quot;&gt;Client&lt;/td&gt;
&lt;td style=&quot;width: 72.7907%;&quot;&gt;사용자의 리소스에 접근하려는 어플리케이션(우리 Spring 서버)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.093%;&quot;&gt;Authorization Server&lt;/td&gt;
&lt;td style=&quot;width: 72.7907%;&quot;&gt;로그인/동의를 받고 Access Token 을 발급하는 서버 (Keycloak, Auth Server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.093%;&quot;&gt;Resource Server&lt;/td&gt;
&lt;td style=&quot;width: 72.7907%;&quot;&gt;API 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ OAuth2 로그인 흐름 (Spring Security 기준) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 &quot;Google 로그인&quot; 클릭&lt;/li&gt;
&lt;li&gt;Spring Security &amp;rarr; Google 로그인 페이지 redirect(Google Authorization Server 로 redirect)&lt;/li&gt;
&lt;li&gt;사용자가 Google 로그인&lt;/li&gt;
&lt;li&gt;Google &amp;rarr; authorization code 반환&lt;br /&gt;=&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Google 에서 로그인 후 Authorication Code 를 우리 시스템의 callback URL 로 반환&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; (/login/oauth2/code/google) 로 반환&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Spring Security 서버 &amp;rarr; access token 요청 (autho)&lt;br /&gt;=&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Spring Security 의 OAuth2LoginAuthenticationFilter 가 Authorication Code 를 Google 에 전송해&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Access Token 획득&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;access token 으로 사용자 정보 요청&lt;br /&gt;=&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Access Token 으로 Google 사용자 정보(이메일, 프로필 등) 로드 후 세션 생성&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Spring Security &amp;rarr; 로그인 완료&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1773208862239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;사용자 &amp;rarr; Spring Boot (/oauth2/authorization/google)
     &amp;darr;
Spring Security &amp;rarr; Google Auth Server (리다이렉트)
     &amp;darr;
Google &amp;rarr; Spring Boot (/login/oauth2/code/google?code=ABC123)  
     &amp;darr;  
Spring Security &amp;rarr; Google (code=ABC123 &amp;rarr; access_token 교환) ✓
     &amp;darr;
Spring Security &amp;rarr; 사용자 정보 로드 &amp;rarr; 로그인 성공!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요약&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Authorization Code &amp;rarr; Access Token &amp;rarr; User Info&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ Spring Security에서 OAuth2 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 에서는 spring-security-oauth2-client 로 쉽게 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 는 spring-boot-starter-oauth2-client 와 spring-security-oauth2 의존성을 추가하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth2 로그인을 쉽게 설정할 수 있다. 사용자가 소셜 로그인 버튼을 클릭하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/oauth2/authorization/{provider} 로 요청이 가고, OAuth2LoginAuthenticationFilter 가 Authorization Code 를 받아&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token 을 교환한 후 사용자 정보를 로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주요 구성 요소&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ClientRegistration&lt;/b&gt;: application.yml 에서 client-id, client-secret, redirect-uri 등을 등록한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OAuth2UserService&lt;/b&gt;: loadUser() 에서 외부 사용자 정보를 커스텀 처리(예 : DB 저장)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SecurityConfig&lt;/b&gt;: .oauth2Login() 으로 필터 체인에 OAuth2 를 추가하고, 성공/실패 핸들러를 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;application.yml 설정 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773207559476&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: your-client-id
            client-secret: your-secret
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/{registrationId}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 없이도 로그인이 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 URL : /oauth2/authorication/google&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;5️⃣ OAuth2 vs JWT (헷갈리는 부분)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; OAuth2 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; JWT &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;역할&lt;/td&gt;
&lt;td&gt;권한 위임 프로토콜&lt;/td&gt;
&lt;td&gt;토큰 포맷&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;목적&lt;/td&gt;
&lt;td&gt;외부 인증&lt;/td&gt;
&lt;td&gt;인증 정보 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;예&lt;/td&gt;
&lt;td&gt;Google 로그인&lt;/td&gt;
&lt;td&gt;access token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 6️⃣ Spring Security에서 많이 사용하는 방식 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773209553794&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OAuth2 Login
      &amp;darr;
사용자 정보 가져옴
      &amp;darr;
우리 DB에 사용자 저장
      &amp;darr;
JWT 발급
      &amp;darr;
JWT로 API 인증&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&lt;/p&gt;
&lt;pre id=&quot;code_1773209566670&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OAuth2 = 로그인
JWT = API 인증&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 7️⃣ OAuth2를 사용하는 대표 서비스 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Google Login&lt;/li&gt;
&lt;li&gt;Kakao Login&lt;/li&gt;
&lt;li&gt;Naver Login&lt;/li&gt;
&lt;li&gt;Apple Login&lt;/li&gt;
&lt;li&gt;GitHub Login&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;   Spring Security에서 OAuth2 핵심 &lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1773209636525&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;외부 인증 서버에게 로그인을 맡기고
우리 서버는 토큰만 받아 인증 처리!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>oauth2</category>
      <category>Spring boot</category>
      <category>spring security</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/476</guid>
      <comments>https://okimaru.tistory.com/entry/OIDC-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%B4%EB%9E%80#entry476comment</comments>
      <pubDate>Tue, 3 Mar 2026 13:40:23 +0900</pubDate>
    </item>
    <item>
      <title>HandlerMethodArgumentResolver 란?</title>
      <link>https://okimaru.tistory.com/entry/HandlerMethodArgumentResolver-%EB%9E%80</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HandlerMethodArgumentResolver 란?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 메서드의 파라미터를 직접 해석해서 객체로 만들어주는 확장 포인트 인터페이스다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, Spring MVC 에서 컨트롤러 메서드의 매개변수를 HTTP 요청에서 실제 인자 값으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변환하는 인터페이스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherServlet 이 HandlerMapping 에서 적절한 Mapping URL 을 찾아내고 HanlderAdapter 를 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절한 컨트롤러 메서드를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 컨트롤러 메서드를 호출하기 전 @RequestParam, @PathVariable, @RequestBody 등의 데이터들을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가공하거나 커스터마이징할 때 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 1️⃣ 왜 필요한가? (사용 이유) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1767770948912&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/users&quot;)
public String users(
    @RequestParam String id,
    @RequestHeader(&quot;Authorization&quot;) String token,
    HttpServletRequest request
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ❌ 문제점 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러마다 같은 파라미터 파싱 코드 반복&lt;/li&gt;
&lt;li&gt;인증 정보, 사용자 정보, 헤더 값 처리 로직이 컨트롤러에 섞임&lt;/li&gt;
&lt;li&gt;테스트 어려움 / 관심사 분리 안됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   그래서 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 파라미터 하나로 의미 있는 객체를 자동 주입하고 싶을 때 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;HandlerMethodArgumentResolver를 사용&lt;/span&gt;&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 2️⃣ HandlerMethodArgumentResolver 인터페이스 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1767771054384&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    Object resolveArgument(
        MethodParameter parameter,
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        WebDataBinderFactory binderFactory
    ) throws Exception;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 핵심 메서드 2개&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 메서드 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 역할 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;supportsParameter()&lt;/td&gt;
&lt;td&gt;이 파라미터를 내가 처리할 수 있는지 판단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;resolveArgument()&lt;/td&gt;
&lt;td&gt;실제로 파라미터 객체를 만들어 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ 동작 흐름 (중요 ⭐)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1767771162935&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTTP 요청
 &amp;darr;
DispatcherServlet
 &amp;darr;
HandlerMapping
 &amp;darr;
HandlerAdapter (Interceptor 처리 후 ArgumentResolver 체인 실행)
 &amp;darr;
컨트롤러 메서드 호출 전
 &amp;darr;
HandlerMethodArgumentResolver 목록 순회
   └ supportsParameter() == true ?
        └ resolveArgument() 실행
 &amp;darr;
컨트롤러 메서드 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  컨트롤러는 파라미터 생성 과정을 전혀 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ 실제 예제 (가장 이해 잘 되는 예) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   목표 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767771217328&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/me&quot;)
public String me(@LoginUser User user) {
    return user.getName();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4-1️⃣ 커스텀 어노테이션 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767771254232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4-2️⃣ Resolver 구현 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767771292971&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class CustomResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		System.out.println(&quot;parameter === &quot;+parameter.getParameterName());
		System.out.println(&quot;parameter === &quot;+parameter.getParameterType());
		System.out.println(&quot;parameter === &quot;+parameter.getParameterType().equals(int.class));
		return parameter.hasMethodAnnotation(LoginUser.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		HttpServletRequest request =
            (HttpServletRequest) webRequest.getNativeRequest();

        // 예: 세션에서 사용자 조회
        HttpSession session = request.getSession(false);
        return session != null ? session.getAttribute(&quot;LOGIN_USER&quot;) : null;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4-3️⃣ Spring MVC에 등록 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767771336776&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
	
	@Autowired
	CustomResolver customResolver;
	
	@Override
	public void addArgumentResolvers(List&amp;lt;HandlerMethodArgumentResolver&amp;gt; resolvers) {
		resolvers.add(customResolver);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4-4️⃣ 컨트롤러 사용 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767771490316&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/me&quot;)
public String me(@LoginUser User user) {
    return user.getName();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 메서드에 오기전에 파라미터에 대한 공통 작업들을 처리해놓으면 깔끔해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ 언제 쓰면 좋은가? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ✅ 쓰면 좋은 경우 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 사용자 주입(@LoginUser)&lt;/li&gt;
&lt;li&gt;JWT &amp;rarr; User 객체 변환&lt;/li&gt;
&lt;li&gt;공통 헤더 파싱( @ClientIp, @ApiVersion )&lt;/li&gt;
&lt;li&gt;Request &amp;rarr; 도메인 객체 변환 로직이 반복될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ❌ 굳이 안 써도 되는 경우 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 @RequestParam, @PathVariable&lt;/li&gt;
&lt;li&gt;컨트롤러마다 다른 파싱 로직&lt;/li&gt;
&lt;li&gt;일회성 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 6️⃣ Spring Security 와의 관계 (중요) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1767771825323&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String test(@AuthenticationPrincipal UserDetails user)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡ 내부적으로 HandlerMethodArgumentResolver 구현체가 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>HandlerMethodArgumentResolver</category>
      <category>Spring</category>
      <category>Spring boot</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/475</guid>
      <comments>https://okimaru.tistory.com/entry/HandlerMethodArgumentResolver-%EB%9E%80#entry475comment</comments>
      <pubDate>Wed, 7 Jan 2026 16:44:34 +0900</pubDate>
    </item>
    <item>
      <title>람다식이란?</title>
      <link>https://okimaru.tistory.com/entry/%EB%9E%8C%EB%8B%A4%EC%8B%9D%EC%9D%B4%EB%9E%80</link>
      <description>&lt;h4 id=&quot;toc-link-0&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 람다식이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JDK 1.8 부터 추가된 람다식(Lambda Expression)은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;메서드를 하나의 식(Expression)으로 표현&lt;/b&gt;&lt;/span&gt;한것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;람다식은 함수를 간략하면서도 명확한 식으로 표현할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;람다식은 메서드의 이름과 반환값을 제거&lt;/b&gt;&lt;/span&gt;할 수 있어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;익명함수&quot;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라고도 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본 문법&lt;/p&gt;
&lt;pre id=&quot;code_1767589248179&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(매개변수) -&amp;gt; { 실행문 }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1767589283039&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x -&amp;gt; x + 1
(a, b) -&amp;gt; a + b
() -&amp;gt; System.out.println(&quot;hello&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;✔ 함수형 인터페이스(메서드가 1개인 인터페이스)를 구현할 때 사용&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;✔ 함수형 인터페이스는 @FuntionalInterface 어노테이션을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 id=&quot;toc-link-1&quot; style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2️⃣ Spring에서 람다식이 쓰이는 이유&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring 5 / Spring Boot 2 이후부터&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;XML 설정 ❌&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;익명 클래스 ❌&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;람다식 + DSL 스타일 ⭕&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;➡ 설정 코드가 짧아지고 가독성이 좋아짐&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ Spring Security에서 가장 많이 보는 람다식 형태 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   예전 방식 (익명 클래스 느낌) &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589373239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   람다식 방식 (Spring Security 5.4+ / 6) &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589383955&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http
    .authorizeHttpRequests(auth -&amp;gt; auth
        .anyRequest().authenticated()
    )
    .formLogin(form -&amp;gt; form
        .loginPage(&quot;/login&quot;)
    );&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;람다식 구조 뜯어보면&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589421275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;auth -&amp;gt; auth.anyRequest().authenticated()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;auth : AuthorizationManagerRequestMatcherRegistry&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;-&amp;gt; : 람다식&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #333333;&quot;&gt;내부에서 설정을 조립&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767589489326&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public HttpSecurity authorizeHttpRequests(
        Customizer&amp;lt;AuthorizeHttpRequestsConfigurer&amp;lt;HttpSecurity&amp;gt;.AuthorizationManagerRequestMatcherRegistry&amp;gt; authorizeHttpRequestsCustomizer)
        throws Exception {
    ApplicationContext context = getContext();
    authorizeHttpRequestsCustomizer
        .customize(getOrApply(new AuthorizeHttpRequestsConfigurer&amp;lt;&amp;gt;(context)).getRegistry());
    return HttpSecurity.this;
}

// Customizer
@FunctionalInterface
public interface Customizer&amp;lt;T&amp;gt; {

	/**
	 * Performs the customizations on the input argument.
	 * @param t the input argument
	 */
	void customize(T t);

	/**
	 * Returns a {@link Customizer} that does not alter the input argument.
	 * @return a {@link Customizer} that does not alter the input argument.
	 */
	static &amp;lt;T&amp;gt; Customizer&amp;lt;T&amp;gt; withDefaults() {
		return (t) -&amp;gt; {
		};
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Customizer 형태로 제네릭을 받고 @FuntionalInterface 어노테이션을 통해 함수형 인터페이스로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;customize(T t) 메서드는 return 값이 없는 set 메서드만 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ Spring에서 자주 쓰이는 람다식 형태들 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ① Consumer&amp;lt;T&amp;gt; 형태 (가장 흔함) &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589585083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Consumer&amp;lt;HttpSecurity&amp;gt; consumer = http -&amp;gt; {
    http.csrf().disable();
};

// Consumer 내부
@FunctionalInterface
public interface Consumer&amp;lt;T&amp;gt; {

    void accept(T t);
    
    default Consumer&amp;lt;T&amp;gt; andThen(Consumer&amp;lt;? super T&amp;gt; after) {
        Objects.requireNonNull(after);
        return (T t) -&amp;gt; { accept(t); after.accept(t); };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer 는 인자값 한개만 받고 아무것도 리턴하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어 뜻대로 소비자와 같이 인자값만 받아 처리하고 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다식으로는 T -&amp;gt; void 로 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 내부 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1767589725832&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void authorizeHttpRequests(Consumer&amp;lt;AuthorizeHttpRequestsConfigurer&amp;gt; configurer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그래서 이렇게 쓴다&lt;/p&gt;
&lt;pre id=&quot;code_1767589744131&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http.authorizeHttpRequests(auth -&amp;gt; {
    auth.anyRequest().authenticated();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ② Runnable 형태 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589827738&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http.csrf(csrf -&amp;gt; csrf.disable());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ③ Predicate / Function 형태 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767589840094&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;request -&amp;gt; request.getMethod().equals(&quot;GET&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security RequestMatcher 구현할 때 자주 등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ @Bean 등록에서도 람다식 사용 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   CommandLineRunner &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767590222689&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
CommandLineRunner runner() {
    return args -&amp;gt; {
        System.out.println(&quot;서버 시작!&quot;);
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   ApplicationRunner &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767590246138&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
ApplicationRunner runner() {
    return args -&amp;gt; {
        System.out.println(&quot;App 실행&quot;);
    };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 6️⃣ Spring Scheduling에서 람다식 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1767590263744&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;taskScheduler.schedule(
    () -&amp;gt; System.out.println(&quot;작업 실행&quot;),
    new CronTrigger(&quot;0/10 * * * * *&quot;)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Runnable 을 람다로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7️⃣ 핵심 요약  &lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 내용 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring 람다식&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Java 람다식 + Spring DSL 설정&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 형태&lt;/td&gt;
&lt;td&gt;(args) -&amp;gt; { 실행문 }&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가장 흔한 타입&lt;/td&gt;
&lt;td&gt;Consumer&amp;lt;T&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주 사용처&lt;/td&gt;
&lt;td&gt;Security, WebFlux, Scheduler, Bean 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;코드 간결, 가독성 &amp;uarr;, XML 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>() -&amp;gt; {}</category>
      <category>&amp;gt;</category>
      <category>Spring</category>
      <category>람다식</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/474</guid>
      <comments>https://okimaru.tistory.com/entry/%EB%9E%8C%EB%8B%A4%EC%8B%9D%EC%9D%B4%EB%9E%80#entry474comment</comments>
      <pubDate>Mon, 5 Jan 2026 14:12:02 +0900</pubDate>
    </item>
    <item>
      <title>HttpSession 생명주기 (SecurityContextHolderFilter, HttpSessionSecurityContextRepository)</title>
      <link>https://okimaru.tistory.com/entry/HttpSession-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0-SecurityContextHolderFilter-HttpSessionSecurityContextRepository</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1️⃣ HttpSession 이란?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Servlet 컨테이너(Tomcat) 가 관리하는 서버 측 세션 객체&lt;/li&gt;
&lt;li&gt;클라이언트당 1개&lt;/li&gt;
&lt;li&gt;식별자는 JSESSIONID 쿠키&lt;/li&gt;
&lt;li&gt;Spring Security 는 필요할 때만 HttpSession 을 사용(기본 설정)&lt;/li&gt;
&lt;li&gt;보통 Spring Security 인증 성공 후 SecurityContextRepository 를 통해 인증정보를 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2️⃣ HttpSession은 언제 생성될까?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;❌ 자동 생성되지 않는다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpSession 은 요청이 왔다고 해서 무조건 생성되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1767142865047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HttpServletRequest request
request.getSession(false); // 세션 없으면 null
request.getSession();      // 없으면 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 생성되는 대표적인 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 로그인 성공 시 (가장 대표적)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security 가 인증에 성공하고 인증정보를 세션에 저장하려는 순간에 생성된다.&lt;br /&gt;HttpSessionSecurityContextRepository 가 대표적이다.&lt;/li&gt;
&lt;li&gt;기본적으로 Spring Security 6 부터는 SecurityContextRepository 에 직접적으로 인증객체(Authentication) 을 저장해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ➡ SuccessHandler 내부 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 SuccessHandler 에 SecurityContextRepository 에 저장하는 로직을 넣었다.&lt;/p&gt;
&lt;pre id=&quot;code_1767576174414&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SuccessHandler
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
	private final SecurityContextRepository securityContextRepository;

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();
	
	public CustomAuthenticationSuccessHandler(SecurityContextRepository securityContextRepository) {
		this.securityContextRepository = securityContextRepository;
	}
    
    @Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws ServletException, IOException {
		//set session registry
		sessionRegistry.registerNewSession(RequestContextHolder.currentRequestAttributes().getSessionId(), authentication.getPrincipal());

		SecurityContext context = securityContextHolderStrategy.createEmptyContext();
		context.setAuthentication(authentication);
		securityContextRepository.saveContext(context, request, response);
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ➡ HttpSessionSecurityContextRepository 내부 &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767143856771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// HttpSessionSecurityContextRepository 내부

@Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
    SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
            SaveContextOnUpdateOrErrorResponseWrapper.class);
    if (responseWrapper == null) {
        saveContextInHttpSession(context, request);
        return;
    }
    responseWrapper.saveContext(context);
}

private void saveContextInHttpSession(SecurityContext context, HttpServletRequest request) {
    if (isTransient(context) || isTransient(context.getAuthentication())) {
        return;
    }
    SecurityContext emptyContext = generateNewContext();
    if (emptyContext.equals(context)) {
        HttpSession session = request.getSession(false);
        removeContextFromSession(context, session);
    }
    else {
        boolean createSession = this.allowSessionCreation;
        HttpSession session = request.getSession(createSession);
        setContextInSession(context, session);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ② 세션에 값을 명시적으로 저장할 때 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 saveContextInHttpSession 메서드 내에 request.getSession(createSession); 가 존재한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767576534701&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (emptyContext.equals(context)) {
    HttpSession session = request.getSession(false);
    removeContextFromSession(context, session);
}
else {
    boolean createSession = this.allowSessionCreation;
    HttpSession session = request.getSession(createSession);
    setContextInSession(context, session);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;if (emptyContext.equals(context)) {}&lt;/span&gt;&lt;/b&gt; 를 통해 같은 SecurityContext 가 존재하면 존재하는 HttpSession 을 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닐 경우 새로운 HttpSession 을 넘겨 새로 생성하고 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;setContextInSession(context, session)&quot;&lt;/span&gt;&lt;/b&gt; 을 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;SPRING_SECURITY_CONTEXT&quot;&lt;/span&gt;&lt;/b&gt; 라는 이름으로 HttpSession 에 setAttribute 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1767576852501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static final String SPRING_SECURITY_CONTEXT_KEY = &quot;SPRING_SECURITY_CONTEXT&quot;;
private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

private void setContextInSession(SecurityContext context, HttpSession session) {
    if (session != null) {
        session.setAttribute(this.springSecurityContextKey, context);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format(&quot;Stored %s to HttpSession [%s]&quot;, context, session));
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   생성 흐름 요약 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767577511660&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[요청]
 &amp;darr;
SecurityContextRepository 가
&quot;세션에 인증정보 저장 필요&quot;
 &amp;darr;
HttpSession 생성
 &amp;darr;
JSESSIONID 쿠키 응답에 포함&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ HttpSession은 언제 설정(값 저장)될까? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;핵심 저장 대상&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SecurityContext&lt;/li&gt;
&lt;li&gt;즉, Authentication 인증 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   로그인 성공 시 흐름 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 1️⃣ 사용자가 로그인 요청 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 2️⃣ 인증 성공 &amp;rarr; Authentication 생성 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 3️⃣ SecurityContext 에 저장 &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767577813231&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();
SecurityContext context = securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authentication);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4️⃣ SecurityContextHolderFilter 종료 시점 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 5️⃣ HttpSessionSecurityContextRepository.saveContext() &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767577853234&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final SecurityContextRepository securityContextRepository;
public CustomAuthenticationSuccessHandler(SecurityContextRepository securityContextRepository) {
    this.securityContextRepository = securityContextRepository;
}

securityContextRepository.saveContext(context, request, response);

// HttpSessionSecurityContextRepository 내부
public static final String SPRING_SECURITY_CONTEXT_KEY = &quot;SPRING_SECURITY_CONTEXT&quot;;
private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;
private void setContextInSession(SecurityContext context, HttpSession session) {
    if (session != null) {
        session.setAttribute(this.springSecurityContextKey, context);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format(&quot;Stored %s to HttpSession [%s]&quot;, context, session));
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   즉 정리하면 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SecurityContext는 요청 중엔 ThreadLocal&lt;/li&gt;
&lt;li&gt;요청 종료 시 HttpSession에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ 이후 요청에서 HttpSession은 언제 사용될까? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클라이언트 요청이 오면&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저가 JSESSIONID 쿠키 전송&lt;/li&gt;
&lt;li&gt;Tomcat이 기존 HttpSession 조회&lt;/li&gt;
&lt;li&gt;SecurityContextHolderFilter 실행&lt;/li&gt;
&lt;li&gt;HttpSessionSecurityContextRepository.loadContext()&lt;/li&gt;
&lt;li&gt;ThreadLocal(SecurityContextHolder)에 복원&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ HttpSession은 언제 소멸될까? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ✅ ① 로그아웃 시 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767578107318&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/logout&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 동작&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SecurityContextHolder.clearContext()&lt;/li&gt;
&lt;li&gt;HttpSession.invalidate();&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ✅ ② 세션 타임아웃 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값: &lt;b&gt;30분&lt;/b&gt; (Tomcat)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767578178241&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  servlet:
    session:
      timeout: 30m&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;➡ 지정 시간 동안 요청이 없으면 &lt;b&gt;자동 소멸&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ✅ ③ 서버 재시작 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 세션 &amp;rarr; 전부 소멸&lt;/li&gt;
&lt;li&gt;(Redis, JDBC 세션이면 유지 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❌ 단순 요청 종료 &amp;ne; 세션 소멸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 끝나도 세션은 살아 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6️⃣ 전체 생명주기 한눈에 보기&lt;/p&gt;
&lt;pre id=&quot;code_1767578391038&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[첫 요청]
 &amp;darr;
세션 없음
 &amp;darr;
로그인 성공
 &amp;darr;
HttpSession 생성
 &amp;darr;
SecurityContext 저장
 &amp;darr;
JSESSIONID 발급
--------------------------------
[다음 요청]
 &amp;darr;
JSESSIONID 전달
 &amp;darr;
세션 조회
 &amp;darr;
SecurityContext 복원 (ThreadLocal)
 &amp;darr;
요청 처리
 &amp;darr;
ThreadLocal 비움
--------------------------------
[로그아웃 / 타임아웃]
 &amp;darr;
HttpSession.invalidate()
 &amp;darr;
세션 소멸&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7️⃣ Spring Security 6 관련 중요한 옵션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  세션 정책&lt;/p&gt;
&lt;pre id=&quot;code_1767578432114&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http
    .sessionManagement(session -&amp;gt; session
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
    );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 정책 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 설명 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;ALWAYS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;무조건 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;IF_REQUIRED&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필요할 때만 (기본값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;NEVER&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;생성하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;STATELESS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;아예 사용 안 함 (JWT)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8️⃣ 요약 한 줄 정리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpSession은 로그인 성공 등 &amp;ldquo;서버가 상태를 저장해야 할 순간&amp;rdquo;에 생성되고,&lt;br /&gt;SecurityContext는 요청 종료 시 세션에 저장되며,&lt;br /&gt;로그아웃&amp;middot;타임아웃&amp;middot;서버 종료 시 소멸된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>httpsession</category>
      <category>HttpSessionSecurityContextRepository</category>
      <category>Spring</category>
      <category>Spring boot</category>
      <category>spring security 6</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/473</guid>
      <comments>https://okimaru.tistory.com/entry/HttpSession-%EC%83%9D%EB%AA%85%EC%A3%BC%EA%B8%B0-SecurityContextHolderFilter-HttpSessionSecurityContextRepository#entry473comment</comments>
      <pubDate>Wed, 31 Dec 2025 10:17:38 +0900</pubDate>
    </item>
    <item>
      <title>SessionManagementFilter 는 매 요청마다 인증된 세션인지를 어떻게 판단하나? (Feat. SecurityContextHolderFilter)</title>
      <link>https://okimaru.tistory.com/entry/SessionManagementFilter-%EB%8A%94-%EB%A7%A4-%EC%9A%94%EC%B2%AD%EB%A7%88%EB%8B%A4-%EC%9D%B8%EC%A6%9D%EB%90%9C-%EC%84%B8%EC%85%98%EC%9D%B8%EC%A7%80%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%8C%90%EB%8B%A8%ED%95%98%EB%82%98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 는 로그인 인증 시점과 인증된 사용자의 재 요청일 경우 실행되는 필터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 는 &quot;세션 자체&quot; 를 보고 인증 여부를 판단하지 않고, SecurityContext 안&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Authentication 인증 객체의 상태를 보고 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1️⃣ 핵심 결론 한 줄 요약&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 가 &quot;인증된 세션인가?&quot; 를 확인하는 방법은&lt;/p&gt;
&lt;pre id=&quot;code_1765945820512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityContextHolder.getContext().getAuthentication()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 가져온 Authentication 이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;null 이 아니고&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;isAuthenticated() == true&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;AnonymousAuthenticationToken 이 아님&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 인증된 사용자 요청으로 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2️⃣ 왜 &amp;ldquo;세션&amp;rdquo;을 직접 안 볼까?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 에서 인증 상태의 단일 기준은 오직 이것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SecurityContext &amp;rarr; Authentication&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션은 단지 SecurityContext 를 저장하는 저장소일 뿐이다.&lt;/p&gt;
&lt;pre id=&quot;code_1765946087579&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[HttpSession]
   └── SPRING_SECURITY_CONTEXT
           └── SecurityContext
                   └── Authentication&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션이 있어도 Authentication 이 없으면 ❌&lt;/li&gt;
&lt;li&gt;세션이 없어도 (JWT 등) Authentication 이 있으면 ⭕&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3️⃣ SessionManagementFilter 내부 흐름 (중요)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1765946208707&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SessionManagementFilter extends GenericFilterBean {
	static final String FILTER_APPLIED = &quot;__spring_security_session_mgmt_filter_applied&quot;;
	...
    
    if (request.getAttribute(FILTER_APPLIED) != null) {
        chain.doFilter(request, response);
        return;
    }
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    if (!this.securityContextRepository.containsContext(request)) {
    	Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
        if (this.trustResolver.isAuthenticated(authentication)) {
        	...
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Filter 에서 가장 먼저 확인하는건 요청 속성(request attribute)을 확인하여 이미 처리된 인증인지를 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✔ 처리된 인증인지를 판단하는 요청 속성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765946557903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static final String FILTER_APPLIED = &quot;__spring_security_session_mgmt_filter_applied&quot;;

if (request.getAttribute(FILTER_APPLIED) != null) {
    chain.doFilter(request, response);
    return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Http Request 의 속성을 확인하여 Filter 에 대한 내용이 존재하는지 판단하여 이미 처리된 인증인지 판별한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증되지 않았다면 인증시작 전 setAttribute 를 통해 인증을 했다라고 속성값을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;✔ HttpSession 에 SpringSecurityContext 의 KEY 값 존재 여부 판단&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765946804523&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!this.securityContextRepository.containsContext(request)) {
	Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
	...	
}

// HttpSessionSecurityContextRepository 의 내부로직
public static final String SPRING_SECURITY_CONTEXT_KEY = &quot;SPRING_SECURITY_CONTEXT&quot;;
private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

@Override
public boolean containsContext(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) {
        return false;
    }
    return session.getAttribute(this.springSecurityContextKey) != null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 인증절차 확인인 Request 요청정보에 Security 속성값이 존재할 경우 SecurityContextHolder 에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityContext 안에 있는 Authentication 인증객체를 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ✔ Authentication 인증 객체를 불러온 후 인증된 요청인지 확인한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765952538943&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (this.trustResolver.isAuthenticated(authentication)) {
	try {
        this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
    }
}

// AuthenticationTrustResolver 내부로직
public interface AuthenticationTrustResolver {
	...
    default boolean isAuthenticated(Authentication authentication) {
		return authentication != null &amp;amp;&amp;amp; authentication.isAuthenticated() &amp;amp;&amp;amp; !isAnonymous(authentication);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 인증된 요청의 조건이 만족된다면 SessionAuthenticationStrategy 를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ SessionAuthenticationStrategy가 뭐냐? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 는 직접 처리하지 않고 아래 전략들에게 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 주요 전략들 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 전략 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 역할 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SessionFixationProtectionStrategy&lt;/td&gt;
&lt;td&gt;세션 고정 공격 방어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ConcurrentSessionControlAuthenticationStrategy&lt;/td&gt;
&lt;td&gt;동시 세션 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RegisterSessionAuthenticationStrategy&lt;/td&gt;
&lt;td&gt;SessionRegistry 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CompositeSessionAuthenticationStrategy&lt;/td&gt;
&lt;td&gt;위 전략들 묶음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ &amp;ldquo;한 번만&amp;rdquo; 실행되는 이유 (중요 포인트) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이미 처리된 인증이면 다시 실행하지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 요청 속성(request attribute)을 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1765952770939&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static final String FILTER_APPLIED = &quot;__spring_security_session_mgmt_filter_applied&quot;;
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    if (request.getAttribute(FILTER_APPLIED) != null) {
        chain.doFilter(request, response);
        return;
    }
    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 요청 안에서 중복 실행 ❌&lt;/li&gt;
&lt;li&gt;로그인 성공 직후 딱 한 번만 ⭕&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 6️⃣ 그럼 &amp;ldquo;로그인 성공 시점&amp;rdquo;은 어떻게 감지할까? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 로그인 성공 흐름 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765952937086&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UsernamePasswordAuthenticationFilter
   &amp;darr; (인증 성공)
SecurityContextHolder에 Authentication 저장
   &amp;darr;
SessionManagementFilter 실행
   &amp;darr;
&quot;오, Authentication 이 생겼네?&quot;
   &amp;darr;
SessionAuthenticationStrategy 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 인증필터가 먼저 인증&lt;/li&gt;
&lt;li&gt;그 결과를 SessionManagementFilter 가 감지&lt;/li&gt;
&lt;li&gt;여기서 SessionAuthenticationStrategy 전략들을 통해 동일 세션 관리 등을 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 7️⃣ 그럼 다음 요청에서 인증은 어떻게 복구되나? &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증된 사용자가 새로운 요청을 또 보냈을 경우 SecurityContextHolderFilter 가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HttpSessionSecurityContextRepository 에서 HttpSession 내 SecurityContext 를 조회하여 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 요청 시작 시 흐름 (매 요청마다 반복) &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765953161514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[클라이언트 요청]
   &amp;darr;
SecurityContextHolderFilter
   &amp;darr;
HttpSessionSecurityContextRepository
   &amp;darr;
HttpSession에서 SecurityContext 조회
   &amp;darr;
SecurityContextHolder.setContext()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SecurityContextHolderFilter 내부 로직&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765953277090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SecurityContextHolderFilter extends GenericFilterBean {
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws ServletException, IOException {
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        Supplier&amp;lt;SecurityContext&amp;gt; deferredContext = this.securityContextRepository.loadDeferredContext(request);
        try {
            this.securityContextHolderStrategy.setDeferredContext(deferredContext);
            chain.doFilter(request, response);
        }
        finally {
            this.securityContextHolderStrategy.clearContext();
            request.removeAttribute(FILTER_APPLIED);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; ✔ this.securityContextRepository.loadDeferredContext(request);&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityContextRepository 내 SecurityContext 를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt; ✔ this.securityContextHolderStrategy.setDeferredContext(deferredContext);&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가져온 SecurityContext 를 해당 워커 스레드 내 SecurityContextHolder 에 set 하여 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 요청이 들어와도 SecurityContextHolder 를 통해 해당 스레드에도 SecurityContext 를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청 예 (일반 API 호출)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1765953638783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[클라이언트 요청 + JSESSIONID]
   &amp;darr;
SecurityContextHolderFilter
   &amp;darr;
HttpSessionSecurityContextRepository.loadContext()
   &amp;darr;
HttpSession에서 SecurityContext 조회
   &amp;darr;
SecurityContextHolder.setContext(context)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   결과 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전 로그인 정보가 그대로 복구&lt;/li&gt;
&lt;li&gt;이후 필터들은 이미 인증된 요청으로 인식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>SecurityContextHolderFilter</category>
      <category>sessionmanagementfilter</category>
      <category>Spring</category>
      <category>Spring boot</category>
      <category>spring security 6</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/472</guid>
      <comments>https://okimaru.tistory.com/entry/SessionManagementFilter-%EB%8A%94-%EB%A7%A4-%EC%9A%94%EC%B2%AD%EB%A7%88%EB%8B%A4-%EC%9D%B8%EC%A6%9D%EB%90%9C-%EC%84%B8%EC%85%98%EC%9D%B8%EC%A7%80%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%8C%90%EB%8B%A8%ED%95%98%EB%82%98#entry472comment</comments>
      <pubDate>Wed, 17 Dec 2025 13:51:29 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security6 SecurityContextHolderFilter 동작과 ThreadLocal Clear 시점</title>
      <link>https://okimaru.tistory.com/entry/Spring-Security6-SecurityContextHolderFilter-%EB%8F%99%EC%9E%91%EA%B3%BC-ThreadLocal-Clear-%EC%8B%9C%EC%A0%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6 시점에서 SecurityContextHolderFilter 로 변경되었고, SecurityContextHolderFilter 는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 HTTP 요청이 들어왔을 때 SecurityContext 를 불러와 워커 스레드의 ThreadLocal 에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1️⃣ Tomcat 워커 스레드와 요청 처리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 &amp;rarr; HTTP 요청&lt;/li&gt;
&lt;li&gt;Tomcat 워커 스레드 하나가 요청을 할당받는다.&lt;br /&gt;Tomcat 의 메인 쓰레드는 어플리케이션 실행만 담당한다.&lt;/li&gt;
&lt;li&gt;이 워커 스레드는 요청 수명 동안 재사용됨(Thread Pool)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  그래서 &lt;b&gt;ThreadLocal 정리가 매우 중요&lt;/b&gt;함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 2️⃣ SecurityContextHolderFilter의 역할 (Spring Security 6 핵심)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6 부터는 예전의 SecurityContextPersistenceFilter 대신&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;SecurityContextHolderFilter&lt;/b&gt; 가 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 요청 시작 시 &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765869214058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[요청 진입]
&amp;rarr; SecurityContextHolderFilter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. SecurityContextRepository 에서 SecurityContext를 로드한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 HttpSessionSecurityContextRepository&lt;/li&gt;
&lt;li&gt;또는 JWT / Stateless 방식이면 다른 구현체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. SecurityContextHolder.setContext(context)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  내부적으로 &lt;b&gt;ThreadLocal&lt;/b&gt; 에 저장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1765869347079&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SecurityContextHolderFilter extends GenericFilterBean {
	
    ...
    
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		Supplier&amp;lt;SecurityContext&amp;gt; deferredContext = this.securityContextRepository.loadDeferredContext(request);
		try {
			this.securityContextHolderStrategy.setDeferredContext(deferredContext);
			chain.doFilter(request, response);
		}
		finally {
			this.securityContextHolderStrategy.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try {} 문 바로 위쪽에 securityContextRepository 에서 SecurityContext 를 로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가져온 SecurityContext 를 setDeferredContext(deferredContext) 를 통해 ThreadLocal(기본 전략)에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;filterChain 에 걸린 모든 Filter / Controller / Service 의 동일 스레드에서 접근 가능&lt;/span&gt;&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ Filter Chain 전체에서의 사용 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 필터&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AuthenticationFilter&lt;/li&gt;
&lt;li&gt;AuthorizationFilter&lt;/li&gt;
&lt;li&gt;Custom Filter&lt;/li&gt;
&lt;li&gt;Controller&lt;/li&gt;
&lt;li&gt;Service&lt;/li&gt;
&lt;li&gt;Repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 같은 워커 스레드에서 실행되므로&lt;/p&gt;
&lt;pre id=&quot;code_1765869583293&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityContextHolder.getContext().getAuthentication()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하면 항상 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ 요청 종료 시 &amp;ndash; finally에서 정리 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이 부분이 핵심이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityContextHolderFilter 내부 구조&lt;/p&gt;
&lt;pre id=&quot;code_1765869654035&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
    // SecurityContext 로드 &amp;rarr; ThreadLocal 저장
    filterChain.doFilter(request, response);
} finally {
    //   반드시 실행됨
    SecurityContextHolder.clearContext();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 반드시 clear 해야 할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tomcat 워커 스레드는 다음 요청에도 재사용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ThreadLocal 에 남아 있으면 다른 사용자가 이전 사용자 인증정보를 참조하는 치명적인 보안 이슈가 발생&lt;/span&gt;&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ &lt;b&gt;필터 체인이 정상 종료되든&lt;/b&gt;&lt;br /&gt;✔ &lt;b&gt;예외가 발생하든&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; finally 에서 무조건 해당 워커 스레드의 SecurityContextHolder 정보를 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ Spring Security 6에서의 정확한 정리 타이밍 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1765869790570&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;요청 시작
&amp;rarr; SecurityContext 로드
&amp;rarr; ThreadLocal 저장
&amp;rarr; FilterChain 전체 실행
&amp;rarr; finally 에서 ThreadLocal clear
&amp;rarr; Tomcat 스레드 반환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 6️⃣ 한 문장으로 요약 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6 에서 SecurityContextHolderFilter 는 요청 시작 시 SecurityContext 를 ThreadLocal에 저장하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터 체인이 끝난 후 finally 블록에서 반드시 ThreadLocal 을 비워 Tomcat 워커 스레드 재사용 시 보안 문제를 방지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <category>SecurityContextHolderFilter</category>
      <category>Spring</category>
      <category>spring boot3</category>
      <category>spring security 6</category>
      <category>ThreadLocal</category>
      <category>Worker Thread</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/471</guid>
      <comments>https://okimaru.tistory.com/entry/Spring-Security6-SecurityContextHolderFilter-%EB%8F%99%EC%9E%91%EA%B3%BC-ThreadLocal-Clear-%EC%8B%9C%EC%A0%90#entry471comment</comments>
      <pubDate>Tue, 16 Dec 2025 16:26:46 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security ConcurrentSessionFilter, SessionManagementFilter</title>
      <link>https://okimaru.tistory.com/entry/Spring-Security-ConcurrentSessionFilter-SessionManagementFilter</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ConcurrentSessionFilter 와 SessionManagementFilter 는 세션 기반 보안과 관련 있지만, 역할과 책임이 명확히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 1️⃣ SessionManagementFilter &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 요청의 세션 상태가 정상적인가?&quot; 를 관리하는 필터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;SessionManagementFilter 는 사용자 로그인 인증 시점과 인증된 요청 진입 시에 동작&lt;/span&gt;&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SessionManagementFilter&lt;span&gt; 는 사용자가 인증되었을 시점에 SessionAuthenticationStrategy 들을&lt;/span&gt;&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;실행시켜준다.&lt;/span&gt;&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security 6 부터는 기본 설정에서 SessionManagementFilter 가 아닌 각 인증 필터(예: UsernamePasswordAuthenticationFilter) 가 직접 SessionAuthenticationStrategy 를 호출하는 방식으로 이동해서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 요청마다 세션을 강제로 읽지 않도록 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 수행하는 주요 기능 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 1. 세션 고정 공격(Session Fixation) 방어 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 성공 시
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 세션을 새 세션으로 교체 또는 세션 ID 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1765863588798&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sessionManagement()
    .sessionFixation().changeSessionId();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 2. 인증 이후 세션 정책 적용 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증된 사용자가 새 요청을 보낼 때&lt;/li&gt;
&lt;li&gt;세션이 존재해야 하는지 / 생성 가능한지 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1765864032747&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 3. 세션 무효 상태 감지 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 만료되었거나 invalid 된 세션으로 접근 시&lt;/li&gt;
&lt;li&gt;설정된 전략(SessionAuthenticationStrategy) 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 4. Concurrent Session 제어와 연결점 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security 에 maximumSessions() 설정이 있을 경우&lt;br /&gt;  내부적으로 &lt;b&gt;ConcurrentSessionControlAuthenticationStrategy&lt;/b&gt; 호출 &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;SessionManagementFilter&lt;/span&gt;&lt;/b&gt; 는 &quot;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;로그인 시점의 세션 정책 총괄 관리자&lt;/span&gt;&lt;/b&gt;&quot; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   동작 시점 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 시점 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 설명 &lt;/b&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;인증된 요청 진입&lt;/td&gt;
&lt;td&gt;세션 상태 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 2️⃣ ConcurrentSessionFilter &lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 존재하는 모든 세션에 대해, 요청이 들어올 때마다 해당 세션이 만료(expire) 되었는지 SessionRegistry 를 통해 조회한다.&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ConcurrentSessionFilter&lt;/span&gt; &lt;/b&gt;는 로그인 시점이 아니라 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;모든 요청마다 검사&lt;/span&gt;&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   핵심 역할 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 로그인 제한(maximumSession)이 걸린 상태에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;이미 만료 처리된 세션으로 요청이 들어왔는지 검사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 수행하는 주요 기능 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 1. SessionRegistry 기반 세션 검증 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 요청의 세션 ID 기준으로 SessionRegistry 에서 세션 상태 검사&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 2. 만료된 세션이면 즉시 차단 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 로그인으로 인해 expire 처리된 세션이라면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그아웃 처리 (SecurityContextHolder 비움)&lt;/li&gt;
&lt;li&gt;SessionInformationExpiredStrategy 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SessionManagementFilter 가 &quot;특정 세션을 만료 상태로 표시&quot; 했다면, ConcurrentSessionFilter 는 그 이후 들어오는 요청마다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 만료 상태를 보고 만료된 세션은 로그아웃 처리를 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1765864871379&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http
  .sessionManagement()
  .maximumSessions(1)
  .expiredUrl(&quot;/session-expired&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ✅ 3. 요청 단위 필터 &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 시점 ❌&lt;/li&gt;
&lt;li&gt;모든 요청마다 검사 ⭕&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   동작 시점 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 시점 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 설명 &lt;/b&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;동시 로그인 발생 후&lt;/td&gt;
&lt;td&gt;이전 세션 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 3️⃣ 둘의 차이 요약 (아주 중요) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; SessionManagementFilter &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; ConcurrentSessionFilter &lt;/b&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;/tr&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;세션 생성/교체&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;간접&lt;/td&gt;
&lt;td&gt;&lt;b&gt;직접&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SessionRegistry 사용&lt;/td&gt;
&lt;td&gt;간접&lt;/td&gt;
&lt;td&gt;&lt;b&gt;직접&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maximumSessions 연관&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 4️⃣ Spring Security 6 기준 필터 체인 위치&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1765865211605&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SecurityContextHolderFilter
  &amp;darr;
LogoutFilter
  &amp;darr;
ConcurrentSessionFilter   &amp;larr; 여기!
  &amp;darr;
SessionManagementFilter
  &amp;darr;
AuthenticationFilter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 5️⃣ 한 문장으로 정리 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SessionManagementFilter&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  &amp;ldquo;로그인 후 세션을 어떻게 만들고 유지할지 결정하는 관리자&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ConcurrentSessionFilter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  &amp;ldquo;이미 만료된 세션으로 접근하는 사용자를 현관에서 차단하는 경비&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java/Spring</category>
      <category>ConcurrentSessionFilter</category>
      <category>sessionmanagementfilter</category>
      <category>Spring</category>
      <category>Spring boot</category>
      <category>spring security</category>
      <category>spring security 6</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/470</guid>
      <comments>https://okimaru.tistory.com/entry/Spring-Security-ConcurrentSessionFilter-SessionManagementFilter#entry470comment</comments>
      <pubDate>Tue, 16 Dec 2025 15:09:24 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 렌더링 과정</title>
      <link>https://okimaru.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 클라이언트로부터 요청을 받고 서버로부터 리소스를 받아 처리하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   1. 클라이언트가 서버에 접근 (HTTP Request) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 URL 을 입력하거나 링크를 클릭하면 브라우저는 다음을 수행한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DNS 조회 &amp;rarr; IP 주소 획득&lt;/li&gt;
&lt;li&gt;서버와 TCP 연결&lt;/li&gt;
&lt;li&gt;HTTPS 면 SSL/TLS 핸드셰이크&lt;/li&gt;
&lt;li&gt;서버에 HTTP 요청 전송(GET /index.html)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   2. 서버가 HTML, CSS, JS 등 리소스를 응답 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 요청된 HTML 문서를 먼저 보내고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 에 링크된 CSS/JS/이미지 파일들을 동시에 추가 요청하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   3. HTML 파싱 &amp;rarr; DOM 트리(DOM Tree) 생성 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 문서는 문자열이므로 이를 분석해 DOM(Document Object Model) 트리를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1765431822882&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;html&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;Hello&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; DOM Tree 예 (단순화)&lt;/p&gt;
&lt;pre id=&quot;code_1765432048538&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;HTML
 └─ BODY
     └─ DIV
         └─ &quot;Hello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM Tree 는 자바스크립트가 조작할 수 있는 브라우저 내부의 메모리 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   4. CSS 파싱 &amp;rarr; CSSOM 트리(CSS Object Model Tree) 생성 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 파일도 문자열이므로 브라우저는 이를 파싱해 CSSOM 을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre id=&quot;code_1765432128454&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div { color: red; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSSOM Tree 예:&lt;/p&gt;
&lt;pre id=&quot;code_1765432138910&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stylesheet
 └─ Rule: div
        └─ color: red&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   5. DOM + CSSOM 합침 &amp;rarr; Render Tree(렌더 트리) 생성 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 브라우저는 DOM 과 CSSOM 을 결합해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;실제 화면에 표시할 요소만 포함한 렌더 트리&lt;/span&gt;&lt;/b&gt;를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ display: none 은 렌더트리에 포함되지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✔ visibility: hidden 은 포함되지만 보이지 않는 상태로 렌더링된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리 예:&lt;/p&gt;
&lt;pre id=&quot;code_1765432244082&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RenderRoot
 └─ RenderBlock(html)
      └─ RenderBlock(body)
           └─ RenderText(div, style: color:red)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   6. Layout(레이아웃 또는 Reflow)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리가 만들어지면, 이제 브라우저는 각 요소의 크기(width / height), 위치(x,y) 를 계산한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면에 요소가 어디에 배치되는지&lt;/li&gt;
&lt;li&gt;요소마다 박스 모델(Box Model) 을 계산&lt;/li&gt;
&lt;li&gt;CSS 속성(Flex, Grid, position, margin 등) 모두 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  ️ 7. Painting(페인팅) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산된 렌더 트리를 기반으로 픽셀 단위로 화면에 그리기 시작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배경색&lt;/li&gt;
&lt;li&gt;텍스트&lt;/li&gt;
&lt;li&gt;테두리&lt;/li&gt;
&lt;li&gt;그림자&lt;/li&gt;
&lt;li&gt;이미지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 GPU / CPU 가 그려서 실제 화면에 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ⚙️ 8. JavaScript 실행 (파싱 + 실행) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 파싱 도중 &amp;lt;script&amp;gt; 태그를 만나면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JS 파일 다운로드&lt;/li&gt;
&lt;li&gt;JS 파싱(AST 생성 &amp;rarr; 바이트코드)&lt;/li&gt;
&lt;li&gt;JS 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  기본적으로 JS 는 DOM 생성 작업을 중단시키는 블로킹 요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; async/defer로 비동기 로딩 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS가 DOM 을 변경하면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레이아웃(Layout) 대규모 재계산(Reflow)&lt;/li&gt;
&lt;li&gt;페인팅(Paint) 재수행(Repaint)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javascript 파싱/실행은 브라우저의 JS 엔진(Chrome V8 등) 이 처리하며, 파싱 과정에서 실제로 &quot;바이너리 형태&quot;로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. JavaScript 파싱은 브라우저의 JS 엔진이 한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chrome &amp;rarr; V8 엔진&lt;/li&gt;
&lt;li&gt;Edge &amp;rarr; V8&lt;/li&gt;
&lt;li&gt;Firefox &amp;rarr; SpiderMonkey&lt;/li&gt;
&lt;li&gt;Safari &amp;rarr; JavaScriptCore&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 파싱 과정에서 AST(추상 구문 트리)를 만든 뒤, 즉시 수행 가능한 최적화된 &quot;중간 바이트코드(Bytecode)&quot; 로 변환된다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자바스크립트는 바이너리 코드(=기계어 코드) 로 &quot;미리 컴파일&quot; 하지는 않지만&amp;nbsp; 엔진 내부에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하기 위한 바이트코드 형태로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 엔진은 이 바이트코드를 JIT(Just-In-Time) 컴파일러로 필요한 부분만 네이티브 머신 코드로 변환해 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   자바스크립트 엔진의 실제 작동 흐름 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 HTML 을 파싱하다가 &amp;lt;script&amp;gt; 태그를 만나면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; JS 엔진에게 그 스크립트를 넘겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 코드 읽기 (Parsing)&lt;/b&gt;&lt;br /&gt;문자열 형태의 JavaScript 코드를 읽는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;2. 토큰화(Tokenization / Lexing)&lt;/b&gt;&lt;br /&gt;코드를 의미 있는 조각(token) 으로 분리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)&lt;/p&gt;
&lt;pre id=&quot;code_1765433471934&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let x = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; [&quot;let&quot;, &quot;x&quot;, &quot;=&quot;, &quot;10&quot;, &quot;;&quot;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 구문 분석(Parsing) &amp;rarr; AST 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Token을 기반으로 AST(추상 구문 트리)를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예)&lt;/p&gt;
&lt;pre id=&quot;code_1765433551412&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VariableDeclaration
 └─ Identifier(x)
 └─ Literal(10)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. AST &amp;rarr; 중간 바이트코드(Bytecode) 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS 엔진(예: V8 의 Ignition 인터프리터)이 AST 를 바이트코드로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트코드는 실제 CPU 기계어가 아니라 엔진이 이해하는 일종의 &quot;가벼운 기계어&quot; 라고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. JIT 컴파일 &amp;rarr; 네이티브 머신 코드로 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중 자주 호출되거나 반복되는 함수는 JS 엔진의 JIT 컴파일러가 바로 기계어(native code) 로 컴파일한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 단계 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt; 설명 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML 파싱 중 JS 발견&lt;/td&gt;
&lt;td&gt;JS 엔진에게 코드 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lexing&lt;/td&gt;
&lt;td&gt;토큰으로 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parsing&lt;/td&gt;
&lt;td&gt;AST 생성&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;JIT 컴파일&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;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   이후 화면 업데이트가 일어나는 과정 &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS, CSS 변경, DOM 변경이 발생하면 브라우저는 아래를 수행한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DOM 변경 &amp;rarr; 렌더 트리 영향 &amp;rarr; Reflow &amp;amp; Repaint&lt;/li&gt;
&lt;li&gt;CSS 변경 &amp;rarr; CSSOM 변경 &amp;rarr; Render Tree 업데이트&lt;/li&gt;
&lt;li&gt;스크롤 / 애니메이션 &amp;rarr; 계속 Repaint&lt;/li&gt;
&lt;li&gt;requestAnimationFrame &amp;rarr; 1초에 60프레임 목표&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;   전체 흐름 요약 (최종) &lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1765432828923&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 서버 요청/응답 (HTML, CSS, JS 다운로드)
2. HTML 파싱 &amp;rarr; DOM 생성
3. CSS 파싱 &amp;rarr; CSSOM 생성
4. DOM + CSSOM &amp;rarr; Render Tree 생성
5. Layout(Reflow): 요소 위치/크기 계산
6. Painting: 화면에 픽셀 렌더링
7. JavaScript 실행 (필요 시 DOM 업데이트)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript/javascript</category>
      <category>javascript</category>
      <category>Javascript 파싱</category>
      <category>브라우저 렌더링</category>
      <category>브라우저 엔진</category>
      <category>브라우저 엔진 파싱</category>
      <author>Z_Z</author>
      <guid isPermaLink="true">https://okimaru.tistory.com/469</guid>
      <comments>https://okimaru.tistory.com/entry/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-1#entry469comment</comments>
      <pubDate>Thu, 11 Dec 2025 15:16:43 +0900</pubDate>
    </item>
  </channel>
</rss>