<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>코딩하는 짱구</title>
    <link>https://tlsgkstj.tistory.com/</link>
    <description>짱구의 성장 일기</description>
    <language>ko</language>
    <pubDate>Mon, 6 Apr 2026 12:42:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>tlsgkstj</managingEditor>
    <image>
      <title>코딩하는 짱구</title>
      <url>https://tistory1.daumcdn.net/tistory/8165931/attach/8b01d0e6e04e4ecf990d5037b9a5b58c</url>
      <link>https://tlsgkstj.tistory.com</link>
    </image>
    <item>
      <title>주간 KPT 회고록(2026.03.09 ~ 15)</title>
      <link>https://tlsgkstj.tistory.com/161</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Keep&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #353638;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;구름톤, 알바 지원&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;포트폴리오 작성&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;공간 분리(공부할 곳과 쉬는 곳 나눔)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개념이 많이 부족함.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;취업 준비 미흡&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Try&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개인 공부 열심히 하기&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle; color: #353638;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;사이트 구현&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;코테 공부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;블로그 매일 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이력서, 포트폴리오 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;매일 회사 지원하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>Growth &amp;amp; Career/Retrospectives</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/161</guid>
      <comments>https://tlsgkstj.tistory.com/161#entry161comment</comments>
      <pubDate>Sat, 14 Mar 2026 17:26:35 +0900</pubDate>
    </item>
    <item>
      <title>주간 KPT 회고록(2026.03.02 ~ 08)</title>
      <link>https://tlsgkstj.tistory.com/160</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Keep&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #353638;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;학원 안빠지고 다 나옴&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;SQLD 시험 공부 및 시험&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개념이 많이 부족함.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;취업 준비 미흡&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Try&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개인 공부 열심히 하기&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle; color: #353638;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;사이트 구현&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;코테 공부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;블로그 매일 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이력서, 포트폴리오 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;매일 회사 지원하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>Growth &amp;amp; Career/Retrospectives</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/160</guid>
      <comments>https://tlsgkstj.tistory.com/160#entry160comment</comments>
      <pubDate>Sat, 14 Mar 2026 17:24:09 +0900</pubDate>
    </item>
    <item>
      <title>[Control Tower] 개발 및 운영 회고</title>
      <link>https://tlsgkstj.tistory.com/159</link>
      <description>&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4&quot;&gt;1. 프로젝트 개요&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1865&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJIoA/dJMcabpWj8A/eeSPv0KbBPwag18qTlGVH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJIoA/dJMcabpWj8A/eeSPv0KbBPwag18qTlGVH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJIoA/dJMcabpWj8A/eeSPv0KbBPwag18qTlGVH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJIoA%2FdJMcabpWj8A%2FeeSPv0KbBPwag18qTlGVH1%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;1865&quot; height=&quot;900&quot; data-origin-width=&quot;1865&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;한 줄 소개:&lt;/b&gt; &quot;복잡한 항공 인력 스케줄링과 인사 행정을 한눈에 관리하는 SaaS 플랫폼&quot;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,1,0&quot;&gt;개발 기간:&lt;/b&gt; 2026.01.06 - 2026.03.03 (팀 프로젝트)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,2,0&quot;&gt;기술 스택:&lt;/b&gt; Java, Spring Boot, React, MySQL, AWS (EC2), Spring AI, Nginx&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,3,0&quot;&gt;주요 기능:&lt;/b&gt; 대시보드를 통한 실시간 인력 현황 모니터링
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ERD 설계를 기반으로 한 체계적인 인사 데이터 관리&lt;/li&gt;
&lt;li&gt;Spring AI를 활용한 명함 인식 회원가입 및 이미지 기반 근태 정정 신청&lt;/li&gt;
&lt;li&gt;AWS를 활용한 안정적인 클라우드 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;6&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7&quot;&gt;2. 나의 역할&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서 &lt;b data-index-in-node=&quot;10&quot; data-path-to-node=&quot;8&quot;&gt;형상관리자&lt;/b&gt;와 &lt;b data-index-in-node=&quot;17&quot; data-path-to-node=&quot;8&quot;&gt;풀스택 개발자&lt;/b&gt; 역할을 겸임했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Frontend:&lt;/b&gt; 전체적인 프론트엔드 UI/UX 구현 (React)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Backend:&lt;/b&gt; 근태/휴가 관리 시스템, 건강 프로그램 신청 로직 구현&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;AI Integration:&lt;/b&gt; Spring AI를 활용한 이미지 OCR 기반 데이터 처리&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;DevOps:&lt;/b&gt; AWS EC2 환경 구축 및 Nginx를 이용한 서비스 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLhAI6/dJMcaadt8vq/TCLp357IDoGOmAGmim6FFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLhAI6/dJMcaadt8vq/TCLp357IDoGOmAGmim6FFk/img.png&quot; data-alt=&quot;복잡한 항공 인사 행정 시스템을 구현하기 위해 설계한 ERD입니다. 사원 정보를 중심으로 근태, 스케줄, 건강 프로그램 등이 유기적으로 연결되도록 구조화했습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLhAI6/dJMcaadt8vq/TCLp357IDoGOmAGmim6FFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLhAI6%2FdJMcaadt8vq%2FTCLp357IDoGOmAGmim6FFk%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;1202&quot; height=&quot;710&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;복잡한 항공 인사 행정 시스템을 구현하기 위해 설계한 ERD입니다. 사원 정보를 중심으로 근태, 스케줄, 건강 프로그램 등이 유기적으로 연결되도록 구조화했습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;3. 핵심 트러블슈팅: 땀 흘리며 배운 배포와 형상관리&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;① 보안 사고 방지: .gitignore와 캐시 삭제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트 초기, 팀원이 실수로 DB 비밀번호와 JWT 설정이 담긴 application_secret.yaml을 GitHub에 업로드하는 일이 발생했습니다. 형상관리자로서 가장 먼저 대응한 이슈였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;문제:&lt;/b&gt; 이미 원격 저장소에 올라간 파일은 .gitignore에 추가해도 계속 추적됨.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;해결:&lt;/b&gt; git rm --cached 명령어를 통해 원격 저장소의 캐시를 삭제하고, 팀 전체에 Pull -&amp;gt; Conflict 해결 -&amp;gt; Push 워크플로우를 전파하여 보안 이슈를 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;② Nginx 설정과 실시간 통신(SSE, Chat) 삽질기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;가장 고생했던 부분은 AWS 배포 환경에서의 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;16&quot;&gt;양방향 통신 설정&lt;/b&gt;이었습니다. 로컬에서는 잘 작동하던 채팅과 알림 기능이 배포만 하면 작동하지 않았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;고충:&lt;/b&gt; 배포 환경에서는 수정 사항을 확인하려면 매번 빌드와 배포를 반복해야 했고, CMD를 통해 로그를 트래킹하며 에러를 예상해야 하는 과정이 힘들었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;해결:&lt;/b&gt; Nginx가 WebSocket과 SSE를 제대로 전달하지 못하는 것이 원인이었습니다. 설정 파일에 Upgrade, Connection 헤더 설정을 명시하고, 타임아웃 시간을 넉넉히 늘려주어 문제를 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;18&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19&quot;&gt;4. 프로젝트 후기: 기술보다 값진 '사람'에 대한 이해&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20&quot;&gt;  협업의 경험: &quot;신뢰가 곧 효율이다&quot;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 깃 충돌(Conflict)이 무서워 팀원들의 푸시/풀 과정을 일일이 옆에서 확인하고 직접 머지해줬습니다. 하지만 문득 이것이 &lt;b data-index-in-node=&quot;75&quot; data-path-to-node=&quot;21&quot;&gt;팀원을 신뢰하지 못하는 행동&lt;/b&gt;이라는 것을 깨달았습니다. 저 또한 과거에 충돌을 겪으며 성장했듯이, 팀원들에게도 그 과정이 필요함을 인정하기로 했습니다. 팀원들을 믿고 권한을 나눈 결과, 우리 팀 모두가 깃 사용법에 익숙해졌고 협업 속도는 더욱 빨라졌습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;22&quot;&gt;  기술적 성장&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;23&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,0,0&quot;&gt;React &amp;amp; MVC:&lt;/b&gt; 리액트의 컴포넌트 구조와 Spring MVC 패턴의 데이터 흐름을 완벽히 체득했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,1,0&quot;&gt;AI 활용:&lt;/b&gt; 단순히 코드를 짜는 것을 넘어, Figma AI로 UI를 그리거나 API 명세 초안을 잡는 등 AI를 효율적인 조력자로 활용하는 법을 배웠습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;23,2,0&quot;&gt;코드 분석:&lt;/b&gt; 동료들의 코드를 리뷰하며 타인의 로직을 빠르게 분석하는 힘을 길렀습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24&quot;&gt;  아쉬운 점과 향후 계획&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;현재는 수정 사항이 생길 때마다 수동으로 빌드하고 SCP로 파일을 전송하고 있습니다. 이 비효율을 줄이기 위해 차후 프로젝트에서는 &lt;b data-index-in-node=&quot;73&quot; data-path-to-node=&quot;25&quot;&gt;GitHub Actions나 Jenkins를 활용한 CI/CD 파이프라인&lt;/b&gt;을 구축하여 자동 배포 환경을 만들어보고 싶습니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;26&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;4,0&quot;&gt;마치며: 기술적 완성도를 넘어 협업의 가치를 깨닫다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;4,1&quot; data-ke-size=&quot;size16&quot;&gt;'Control Tower' 프로젝트는 단순한 코드의 집합이 아니라, 한 명의 개발자로서 제가 가진 &lt;b data-index-in-node=&quot;56&quot; data-path-to-node=&quot;4,1&quot;&gt;기술적 고집과 팀원들에 대한 신뢰 사이의 균형&lt;/b&gt;을 배우게 해준 소중한 경험이었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,2&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 형상관리자로서 모든 것을 완벽하게 통제해야 한다는 강박에 팀원들의 코드를 일일이 확인하기도 했습니다. 하지만 그 과정을 내려놓고 팀원들을 믿기 시작했을 때, 오히려 프로젝트는 더 역동적으로 흘러갔고 예상치 못한 창의적인 해결책들이 쏟아져 나왔습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,3&quot; data-ke-size=&quot;size16&quot;&gt;또한, 처음 시도해 본 AWS 배포와 Nginx 설정 과정에서의 '삽질'은 저에게 시스템의 흐름을 이해하는 안목을 선물해 주었습니다. 눈에 보이는 화면 뒤에서 데이터가 어떻게 흐르고, 보안이 왜 중요한지를 몸소 깨달은 시간이었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,4&quot; data-ke-size=&quot;size16&quot;&gt;이제 이 경험을 발판 삼아, 다음에는 수동 배포의 번거로움을 해결할 &lt;b data-index-in-node=&quot;38&quot; data-path-to-node=&quot;4,4&quot;&gt;CI/CD 자동화&lt;/b&gt;에 도전해 보려 합니다. '멈춰있는 개발자'가 아닌, 어제의 문제를 오늘의 동력으로 삼는 개발자가 되겠습니다. 긴 글 읽어주셔서 감사합니다!&lt;/p&gt;</description>
      <category>Projects/Team Projects</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/159</guid>
      <comments>https://tlsgkstj.tistory.com/159#entry159comment</comments>
      <pubDate>Sat, 14 Mar 2026 17:14:23 +0900</pubDate>
    </item>
    <item>
      <title>주간 KPT 회고록(2026.02.23 ~ 03.01)</title>
      <link>https://tlsgkstj.tistory.com/158</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Keep&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #353638;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;2차 개발 완성(회원가입 ocr 구현)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;학원에서 AWS 배포 열심히 함&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;학원 안빠지고 다 나옴&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Problem&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개념이 많이 부족함.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;취업 준비 미흡&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;27일 이후 마무리 과정에서 나태해짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Try&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #353638;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;개인 공부 열심히 하기&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle; color: #353638;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;사이트 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;블로그 매일 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이력서 작성하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;배포 마무리하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description>
      <category>Growth &amp;amp; Career/Retrospectives</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/158</guid>
      <comments>https://tlsgkstj.tistory.com/158#entry158comment</comments>
      <pubDate>Sat, 7 Mar 2026 17:06:24 +0900</pubDate>
    </item>
    <item>
      <title>[Control Tower]Spring Boot + AWS RDS + Nginx 배포기: 삽질하며 배운 인프라의 중요성</title>
      <link>https://tlsgkstj.tistory.com/157</link>
      <description>&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;  왜 EC2와 RDS를 분리했는가?&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;처음 배포를 구상할 때 EC2 한 대에 모든 것을 넣을 수도 있었지만, 실제 상용 서비스 수준의 아키텍처를 지향하며 **컴퓨팅(EC2)**과 **데이터 저장(RDS)**을 분리했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;보안:&lt;/b&gt; DB를 사설망에 격리하고 EC2의 보안 그룹을 통해서만 접근을 허용하여 데이터 노출 위험을 줄였습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;가용성:&lt;/b&gt; 서버가 다운되어도 데이터는 안전하게 보존되며, RDS의 자동 백업 기능을 활용할 수 있기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;확장성:&lt;/b&gt; 트래픽이 몰릴 때 WAS와 DB의 자원 점유를 분리하여 성능 저하를 방지했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size26&quot;&gt;  배포 아키텍처: Nginx 리버스 프록시와 보안&lt;/h2&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 api.도메인으로 접속하면 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;8&quot;&gt;Nginx&lt;/b&gt;가 이를 받아 내부의 **8080 포트(Spring Boot)**로 넘겨주는 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;8&quot;&gt;리버스 프록시&lt;/b&gt; 구조를 채택했습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;Nginx를 앞세운 이유&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;포트 은닉:&lt;/b&gt; 8080 포트를 외부에 노출하지 않고 80/443 포트로 깔끔하게 서비스할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;HTTPS 적용:&lt;/b&gt; Certbot을 연동하여 SSL 인증서 관리와 리다이렉션 설정을 자동화했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;정적 자원 처리:&lt;/b&gt; API 요청 외의 처리를 분담하여 WAS의 부담을 덜어줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;  고난의 기록: 배포를 생각하지 않은 개발의 대가&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서 가장 뼈아프게 배운 점은 &quot;로컬 환경과 배포 환경은 천차만별이다&quot;라는 사실입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;1. 양방향 통신(WebSocket/SSE)과 Nginx의 충돌&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;우리 서비스의 핵심인 **실시간 알림(SSE)**과 **채팅(WebSocket)**을 배포했을 때 정상적으로 동작하지 않았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;원인:&lt;/b&gt; Nginx의 기본 설정이 지속적인 연결(Persistent Connection)을 끊어버렸기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;해결:&lt;/b&gt; Nginx 설정 파일에 Upgrade, Connection 헤더 설정을 명시하고, 타임아웃 시간을 늘려주는 설정을 추가하며 해결했습니다. 배포 단계에서 네트워크 프로토콜에 대한 깊은 이해가 왜 필요한지 깨닫는 계기가 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;2. &quot;내 컴퓨터에선 됐는데...&quot;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;로컬 DB 설정과 AWS 환경 설정이 섞여 있어 배포 시 빌드 에러가 잦았습니다. 이를 방지하기 위해 application-prod.yml을 분리하고, DB 접속 정보와 같은 민감 정보는 &lt;b data-index-in-node=&quot;105&quot; data-path-to-node=&quot;17&quot;&gt;EC2 환경변수&lt;/b&gt;를 통해 주입받도록 고도화했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size26&quot;&gt;  향후 고도화 계획&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;1. CI/CD 파이프라인 구축&lt;/h3&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;현재는 수정 사항이 생길 때마다 수동으로 빌드하고 scp로 파일을 넘기고 있습니다. 나중에는 로컬에서 코드를 Push하면 &lt;b data-index-in-node=&quot;68&quot; data-path-to-node=&quot;20&quot;&gt;GitHub Actions&lt;/b&gt;나 &lt;b data-index-in-node=&quot;84&quot; data-path-to-node=&quot;20&quot;&gt;Jenkins&lt;/b&gt;를 통해 AWS에 자동으로 배포되는 환경을 구축해보고 싶습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;2. 아키텍처 고도화 (S3 + CloudFront)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;현재 정적 자원도 서버에서 처리하는 부분이 일부 있는데, 이를 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;22&quot;&gt;S3와 CloudFront&lt;/b&gt; 조합으로 완전히 분리하여 백엔드 서버는 오직 비즈니스 로직(API)에만 집중할 수 있는 구조로 개선할 예정입니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;23&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;✨ 마치며&lt;/h2&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;&quot;코딩만 잘하면 배포는 금방 하겠지&quot;라는 생각이 얼마나 안일했는지 알게 된 프로젝트였습니다. 하지만 직접 서버를 세팅하고, 도메인을 연결하고, Nginx 설정을 만지며 &lt;b data-index-in-node=&quot;94&quot; data-path-to-node=&quot;25&quot;&gt;서비스 전체의 흐름을 보는 시야&lt;/b&gt;를 가질 수 있었습니다. 다음 프로젝트에서는 설계 단계부터 '배포 환경'을 최우선으로 고려하는 개발자가 되겠습니다.&lt;/p&gt;</description>
      <category>Projects/Team Projects</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/157</guid>
      <comments>https://tlsgkstj.tistory.com/157#entry157comment</comments>
      <pubDate>Sat, 7 Mar 2026 17:05:46 +0900</pubDate>
    </item>
    <item>
      <title>[Control Tower] AWS S3 + CloudFront로 React 앱 정적 배포하기 (보안과 성능 최적화)</title>
      <link>https://tlsgkstj.tistory.com/156</link>
      <description>&lt;h2 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size26&quot;&gt;1. 도입부: &quot;왜 S3 단독 배포가 아닌 CloudFront인가?&quot;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;React 프로젝트 빌드 파일을 S3에 올리고 '정적 웹 사이트 호스팅'을 켜는 것만으로도 배포는 가능합니다. 하지만 'Control Tower'와 같은 SaaS 서비스를 운영하기에는 다음과 같은 한계가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;보안 이슈:&lt;/b&gt; S3 단독 호스팅은 HTTP만 지원하여 데이터 전송 시 보안에 취약합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;속도 문제:&lt;/b&gt; 특정 리전에만 데이터가 존재하여 글로벌 사용자에게 일관된 속도를 제공하기 어렵습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;비용과 효율:&lt;/b&gt; 데이터 전송료(Data Transfer Out)와 캐싱 부재로 인한 비효율이 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 **CloudFront(CDN)**를 결합하여 HTTPS 보안과 글로벌 캐싱 성능을 동시에 확보했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;2. 아키텍처 설계 및 서비스별 역할&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;Amazon S3:&lt;/b&gt; 정적 파일(Build Artifacts)의 안전한 저장소 역할을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;Amazon CloudFront:&lt;/b&gt; 전 세계 엣지 로케이션을 통해 콘텐츠를 캐싱하고 HTTPS 게이트웨이 역할을 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;ACM (Certificate Manager):&lt;/b&gt; SSL/TLS 인증서를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;Route 53:&lt;/b&gt; 도메인 연결 및 DNS를 관리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;3. 실전 구축 프로세스 (핵심 요약)&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;  S3 &amp;amp; OAC 설정&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;버킷을 퍼블릭으로 열어두는 것은 보안상 매우 위험합니다. 저는 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;12&quot;&gt;OAC(Origin Access Control)&lt;/b&gt; 설정을 통해 오직 CloudFront를 통해서만 S3 버킷에 접근할 수 있도록 권한을 제한했습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;  ACM 인증서 리전 팁&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;CloudFront에 커스텀 도메인과 HTTPS를 적용하려면 인증서가 필요합니다. 여기서 주의할 점은 CloudFront가 글로벌 서비스이기 때문에 인증서는 반드시 &lt;b data-index-in-node=&quot;92&quot; data-path-to-node=&quot;14&quot;&gt;버지니아 북부(us-east-1) 리전&lt;/b&gt;에서 발급받아야만 목록에 나타난다는 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; data-path-to-node=&quot;22&quot; /&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;4.   Deep Dive: 트러블슈팅 경험&lt;/h2&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;  SPA 404 Error 해결&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;React Router와 같은 CSR 기반 라이브러리를 사용하면 /login 같은 경로로 직접 접속할 때 S3에는 해당 경로의 실제 파일이 없으므로 404 에러가 발생합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;해결:&lt;/b&gt; CloudFront의 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;18,0,0&quot;&gt;Error Pages&lt;/b&gt; 설정에서 403/404 발생 시 응답 페이지를 /index.html로 리다이렉트하고 상태 코드를 &lt;b data-index-in-node=&quot;82&quot; data-path-to-node=&quot;18,0,0&quot;&gt;200&lt;/b&gt;으로 반환하게 설정하여 해결했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;19&quot; data-ke-size=&quot;size23&quot;&gt;  CloudFront Invalidation (캐시 무효화)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;코드 수정 후 S3에 업로드해도 바로 반영되지 않는 문제가 있었습니다. 이는 CDN의 캐시 정책 때문입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;21,0,0&quot;&gt;해결:&lt;/b&gt; 배포 시마다 /* 경로로 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;21,0,0&quot;&gt;Invalidation&lt;/b&gt; 처리를 해주어 전 세계 엣지의 캐시를 초기화하고 최신 코드가 즉시 반영되도록 구현했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;22&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size26&quot;&gt;✨ 마치며&lt;/h2&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;이번 AWS 정적 배포 과정을 통해 단순한 코드 작성을 넘어, '어떻게 하면 사용자에게 더 안전하고 빠르게 서비스를 전달할 수 있을까?'를 고민하는 인프라적 시야를 가질 수 있었습니다. 특히 OAC를 활용한 보안 강화와 SPA 라우팅 이슈 해결은 실제 배포 환경에서 겪을 수 있는 값진 경험이었습니다.&lt;/p&gt;</description>
      <category>Projects/Team Projects</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/156</guid>
      <comments>https://tlsgkstj.tistory.com/156#entry156comment</comments>
      <pubDate>Sat, 7 Mar 2026 16:25:28 +0900</pubDate>
    </item>
    <item>
      <title>[Control Tower] Spring AI로 구현한 항공사 사원 가입 자동화 (명함 OCR &amp;amp; 프롬프트 엔지니어링)</title>
      <link>https://tlsgkstj.tistory.com/155</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배경: &quot;입사 첫날의 번거로운 회원가입을 1초 만에&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항공사 HR SaaS인 &lt;b&gt;'Control Tower'&lt;/b&gt; 프로젝트를 진행하며 가장 신경 쓴 부분 중 하나는 사용자 경험(UX)입니다. 기업용 서비스 특성상 이름, 부서, 직급 등 입력해야 할 정보가 많았고, 이는 신규 입사자에게 다소 피로한 첫인상이 될 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 아이디어를 얻었습니다. &lt;b&gt;&quot;신규 직원이 이미 발급받은 명함을 찍기만 하면, 모든 정보가 자동으로 채워지면 어떨까?&quot;&lt;/b&gt; 단순히 기능을 추가하는 것을 넘어, 서비스의 전문성을 높이는 '스마트 온보딩'을 기획하게 되었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  기술적 선택: 왜 Tesseract 대신 'Spring AI'인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 전통적인 OCR 라이브러리(Tesseract 등)를 검토했지만, 두 가지 결정적인 한계가 있었습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;라벨링(Labeling)의 한계:&lt;/b&gt; 항공사마다 명함 양식이 다릅니다. 특정 좌표에서 텍스트를 읽어오는 방식으로는 대한항공, 아시아나, 제주항공 등 다양한 디자인에 대응할 수 없었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구조화의 어려움:&lt;/b&gt; 단순 OCR은 글자를 읽을 뿐, 이 글자가 '이름'인지 '부서'인지 구분하는 문맥 이해 능력이 부족했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 문제를 해결하기 위해 &lt;b&gt;Spring AI&lt;/b&gt;와 **멀티모달 LLM(openAI)**을 도입했습니다. AI에게 이미지 전체의 문맥을 분석하게 하여, &lt;b&gt;라벨링 없이도 정교한 데이터 추출&lt;/b&gt;이 가능하도록 설계했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚙️ 구현 핵심: 정밀한 프롬프트 엔지니어링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비정형 이미지에서 정형 데이터(JSON)를 완벽하게 뽑아내기 위해, VisionService에서 다음과 같은 프롬프트 전략을 사용했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문맥 이해 및 구조적 추론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &quot;읽어줘&quot;가 아니라, 데이터의 성격을 명확히 규정했습니다.&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;날짜:&lt;/b&gt; YYYY-MM-DD 형식 준수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시간:&lt;/b&gt; HH:mm 형식 준수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사유:&lt;/b&gt; 핵심 내용을 요약하여 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 구조화된 응답(Structured Output) 강제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM이 마크다운 블록(```json) 없이 순수 JSON만 반환하도록 요청하고, 백엔드에서 ObjectMapper를 통해 바로 Java 객체(`ProtestDto.OcrResponse`)로 변환했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[핵심 소스 코드: VisionService.java]&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class VisionService {
    private final ChatClient.Builder chatClientBuilder;
    private final ObjectMapper objectMapper;

    public ProtestDto.OcrResponse extractText(MultipartFile file) {
        // 프롬프트 엔지니어링: 항공사별 다른 양식에 대응하기 위한 명확한 지침 설계
        String promptText = &quot;&quot;&quot;
            이 이미지에서 다음 정보를 추출해서 순수 JSON 형식으로만 반환해.
            - targetDate: 문서에 적힌 날짜 (YYYY-MM-DD 형식)
            - updateTime: 문서에 적힌 시간 (HH:mm 형식)
            - reason: 문서의 핵심 내용 요약
            &quot;&quot;&quot;;

        ChatClient chatClient = chatClientBuilder.build();
        String response = chatClient.prompt()
                .user(userSpec -&amp;gt; userSpec
                        .text(promptText)
                        .media(MimeTypeUtils.parseMimeType(file.getContentType()), resource))
                .call()
                .content();

        // LLM 응답을 바로 객체로 파싱하여 구조화된 데이터 반환
        return objectMapper.readValue(removeMarkdownCodeBlocks(response), ProtestDto.OcrResponse.class);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  'Control Tower' 프로젝트에서의 트러블슈팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 항공사 도메인 지식의 부재&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 AI가 항공사 로고를 사원 이름으로 착각하는 경우가 있었습니다. 이를 해결하기 위해 &quot;텍스트의 크기와 문맥상 항공사명은 회사 필드로, 사람은 이름 필드로 분류하라&quot;는 도메인 특화 지침을 프롬프트에 추가하여 정확도를 대폭 높였습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 가입 프로세스와의 유연한 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 컨트롤러(EmpController)와 OCR 컨트롤러(ProtestApplyController)를 분리하여, 사용자가 사진을 올리는 즉시 API가 동작하고 결과를 프론트엔드 입력 폼에 &lt;b&gt;Auto-fill(자동 채우기)&lt;/b&gt; 하도록 구현했습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✨ 성과 및 배운 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유지보수성 향상:&lt;/b&gt; 새로운 항공사가 추가되어도 코드를 수정하거나 다시 라벨링할 필요가 없습니다. 오직 프롬프트 하나로 대응 가능한 확장성을 얻었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 경험(UX) 극대화:&lt;/b&gt; 복잡한 가입 절차를 사진 한 장으로 해결하여 '스마트한 HR 서비스'라는 프로젝트 정체성을 확립했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백엔드 개발자의 역할 재정의:&lt;/b&gt; 단순히 데이터를 저장하는 것을 넘어, &lt;b&gt;AI를 활용해 비정형 데이터를 가치 있는 비즈니스 데이터로 변환하는 브릿지 역할&lt;/b&gt;의 중요성을 체감했습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Projects/Team Projects</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/155</guid>
      <comments>https://tlsgkstj.tistory.com/155#entry155comment</comments>
      <pubDate>Sat, 7 Mar 2026 16:14:00 +0900</pubDate>
    </item>
    <item>
      <title>Daily Dev Q&amp;amp;A: XSS와 CSRF</title>
      <link>https://tlsgkstj.tistory.com/154</link>
      <description>&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2&quot;&gt;Topic (오늘의 주제)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;XSS(Cross-Site Scripting)와 CSRF(Cross-Site Request Forgery)의 차이 및 방어 메커니즘&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;웹 서비스에서 사용자의 권한과 데이터를 탈취하는 가장 대표적인 두 공격의 원리를 이해하고, 클라이언트와 서버 양측에서 이를 원천 차단하는 방법을 학습합니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;4&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Why (왜 사용하는가? 왜 중요한가?)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;실무:&lt;/b&gt; 이 방어 로직이 뚫리면 관리자 계정이 해킹되어 사이트 전체에 악성 스크립트가 유포되거나(XSS), 사용자가 인지하지 못한 채 비밀번호가 변경되고 무단 결제가 발생하는(CSRF) 치명적인 보안 사고와 서비스 장애로 이어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;구조적 의미:&lt;/b&gt; 프론트엔드와 백엔드 간의 신뢰 경계를 명확히 하고, 입력값 검증(Sanitization)과 안전한 인증 헤더/쿠키 관리를 통해 시스템의 견고성(Robustness)을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;면접 의도:&lt;/b&gt; 지원자가 웹 보안의 기본 동작 원리를 아는지, 단순히 보안 프레임워크에 의존하는 것을 넘어 각 계층에서 어떤 책임을 져야 하는지(예: 쿠키의 HttpOnly 속성, XSS 필터링)를 정확히 이해하고 있는지 확인하려 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;Core Concept (핵심 개념 정리)&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;9&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;요소&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;XSS (크로스 사이트 스크립팅)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;CSRF (사이트 간 요청 위조)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0,0&quot;&gt;개념 정의&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,1,0&quot;&gt;공격자가 웹 사이트에 &lt;b data-index-in-node=&quot;12&quot; data-path-to-node=&quot;9,1,1,0&quot;&gt;악성 스크립트를 삽입&lt;/b&gt;하여, 다른 사용자의 브라우저에서 실행되게 만드는 공격입니다.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,2,0&quot;&gt;사용자가 자신의 의지와 무관하게, 공격자가 의도한 행위(수정/삭제/결제 등)를 &lt;b data-index-in-node=&quot;44&quot; data-path-to-node=&quot;9,1,2,0&quot;&gt;서버에 요청하게 만드는&lt;/b&gt; 공격입니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0,0&quot;&gt;동작 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,1,0&quot;&gt;게시판 등에 &amp;lt;script&amp;gt; 태그 삽입 ➔ 다른 사용자가 게시물 열람 ➔ 브라우저에서 스크립트 자동 실행 ➔ 쿠키/세션 탈취&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,2,0&quot;&gt;로그인된 사용자에게 악성 링크 클릭 유도 ➔ 브라우저가 사용자 인증 쿠키를 포함해 변조된 요청 자동 전송 ➔ 서버는 정상 요청으로 오인하여 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0,0&quot;&gt;핵심 목적&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,1,0&quot;&gt;사용자의 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;9,3,1,0&quot;&gt;정보(세션, 토큰 등) 탈취&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,2,0&quot;&gt;인증된 사용자의 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;9,3,2,0&quot;&gt;권한 도용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0,0&quot;&gt;방어 조건 (해결책)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;1. &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;9,4,1,0&quot;&gt;입력/출력값 치환:&lt;/b&gt; HTML Entity 인코딩 (&amp;lt; 를 &amp;amp;lt; 로 치환)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;2. &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;9,4,1,2&quot;&gt;HttpOnly 쿠키:&lt;/b&gt; 자바스크립트에서 document.cookie로 접근할 수 없도록 차단&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;1. &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;9,4,2,0&quot;&gt;CSRF Token:&lt;/b&gt; 상태를 변경하는 요청 시 서버가 발급한 임의의 난수 토큰 검증&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;2. &lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;9,4,2,2&quot;&gt;SameSite 쿠키:&lt;/b&gt; 타 도메인에서 쿠키가 전송되는 것을 제한&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0,0&quot;&gt;예시/비교&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,5,1,0&quot;&gt;스크립트를 통해 탈취한 인증 정보로 공격자가 직접 행동함.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,5,2,0&quot;&gt;공격자는 인증 정보 자체는 모르지만, 브라우저의 특성을 이용해 요청만 위조함.&lt;/span&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;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;Interview Answer Version (면접 답변식 요약)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;&quot;XSS는 악성 스크립트를 주입해 브라우저에서 실행되게 하여 사용자의 정보를 탈취하는 공격이고, CSRF는 인증된 사용자의 브라우저가 가진 권한을 도용해 위조된 요청을 보내는 공격입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;XSS를 방어하기 위해서는 서버와 클라이언트 양측에서 입력값을 필터링 및 이스케이프 처리해야 하며, 쿠키에 HttpOnly 옵션을 적용해 스크립트 접근을 막아야 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;CSRF를 방어하기 위해서는 POST, PUT, DELETE 같은 상태 변경 요청에 대해 CSRF 토큰을 발급하여 검증하거나 쿠키의 SameSite 속성을 활용합니다. 최근 REST API 서버에서는 브라우저가 자동으로 첨부하는 쿠키 대신, Authorization 헤더에 JWT를 담아 보내는 방식을 사용하여 CSRF 위협을 근본적으로 줄이고 있습니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;13&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;Practical Tip (사용시 주의할 점 or 활용 예)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;실무 활용 예 (프레임워크 적용):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,0,0&quot;&gt;React (XSS 방어):&lt;/b&gt; 기본적으로 JSX는 렌더링 시 데이터 바인딩 과정에서 문자열을 이스케이프 처리하여 XSS를 막아줍니다. 하지만 에디터 기능 등을 위해 dangerouslySetInnerHTML 속성을 사용할 때는 이스케이프가 무력화되므로, 반드시 DOMPurify 같은 라이브러리로 코드를 정제(Sanitize)한 후 렌더링해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,1,0&quot;&gt;Spring Boot (CSRF 방어):&lt;/b&gt; Spring Security를 적용하면 기본적으로 CSRF 방어가 켜져 있어 토큰이 없는 POST 요청은 403 에러가 납니다. 하지만 세션(Session) 인증이 아닌, &lt;b data-index-in-node=&quot;119&quot; data-path-to-node=&quot;15,0,1,1,0&quot;&gt;RESTful 기반의 JWT 토큰 인증&lt;/b&gt;을 구현할 때는 브라우저의 쿠키 자동 전송 메커니즘을 타지 않으므로 http.csrf(AbstractHttpConfigurer::disable) 설정을 통해 무력화하는 것이 일반적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;설정 시 반드시 고려해야 할 보안 속성:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증과 관련된 쿠키(Session ID, Refresh Token 등)를 구울 때는 HttpOnly=true(XSS 방어), Secure=true(HTTPS 통신만 허용), SameSite=Lax 또는 Strict(CSRF 방어) 파라미터를 반드시 함께 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;&quot;이걸 모르고 사용하면 생기는 문제&quot; (CORS와 CSRF의 혼동):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종종 CORS(교차 출처 리소스 공유) 설정을 빡빡하게 하면 CSRF도 막아줄 것이라고 오해하는 경우가 많습니다. CORS는 '응답(Response)'을 브라우저가 읽지 못하게 막는 정책일 뿐, CSRF처럼 브라우저가 서버로 '요청(Request)'을 보내고 서버가 이를 실행해 버리는 것 자체를 막아주지는 못합니다. 별도의 CSRF 대책이 반드시 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;예상 꼬리질문 정리&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;React에서는 기본적으로 XSS를 어떻게 방어하나요? 프론트엔드 개발자로서 예외적으로 취약해지는 상황(예: 게시판 에디터)에서 어떻게 대응하시겠습니까?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,1,0,0&quot;&gt;(의도: JSX의 이스케이프 특징과 DOMPurify 등을 활용한 실무적인 방어 경험 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;Spring Security에서 JWT를 사용할 때 CSRF 설정을 disable 하는 이유는 무엇인가요? JWT 자체는 탈취 위험이 없나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,1,0,0&quot;&gt;(의도: 세션 인증과 토큰 인증 구조의 차이를 알고, Authorization 헤더의 동작 방식을 이해하는지 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0&quot;&gt;CORS(Cross-Origin Resource Sharing) 에러가 무엇인지 설명해 주시고, 이것이 방금 말씀하신 CSRF 공격을 막아줄 수 있는지 설명해 주세요.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,1,0,0&quot;&gt;(의도: 동일 출처 정책(SOP)과 CORS의 목적을 이해하고, 보안 취약점 대책과 혼동하지 않는지 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Archive/Daily Dev Q&amp;amp;A</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/154</guid>
      <comments>https://tlsgkstj.tistory.com/154#entry154comment</comments>
      <pubDate>Wed, 25 Feb 2026 09:40:03 +0900</pubDate>
    </item>
    <item>
      <title>Daily Dev Q&amp;amp;A: HTTP 메서드</title>
      <link>https://tlsgkstj.tistory.com/153</link>
      <description>&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2&quot;&gt;Topic (오늘의 주제)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;HTTP 메서드(HTTP Methods)의 종류와 의미, 그리고 RESTful API 설계&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버에게 요청의 목적(행위)을 알리는 수단인 HTTP 메서드의 개념을 이해하고, 실무에서 이를 어떻게 표준(REST)에 맞게 활용하는지 학습합니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;4&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Why (왜 사용하는가? 왜 중요한가?)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;실무:&lt;/b&gt; API를 설계할 때 메서드를 용도에 맞게 분리하지 않으면(예: 모든 요청을 POST로 처리), 다른 개발자가 코드를 보았을 때 해당 API가 데이터를 조회하는지, 수정하는지, 삭제하는지 엔드포인트(URL)만 보고는 파악할 수 없어 유지보수와 협업이 극도로 힘들어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;구조적 의미:&lt;/b&gt; 자원(Resource)은 URL로 표현하고, 행위(Action)는 HTTP 메서드로 분리함으로써 시스템의 인터페이스를 직관적이고 일관성 있게(RESTful) 최적화합니다. 또한 HTTP의 캐싱 메커니즘을 효율적으로 활용할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;면접 의도:&lt;/b&gt; 지원자가 웹 생태계의 기본 통신 규약을 잘 이해하고 있는지, **멱등성(Idempotence)**과 **안전성(Safety)**의 개념을 아는지, 그리고 PUT과 PATCH의 차이 같은 디테일한 설계 역량을 갖추고 있는지 확인하려 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;Core Concept (핵심 개념 정리)&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;9&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;요소&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0,0&quot;&gt;개념 정의&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,1,0&quot;&gt;클라이언트가 웹 서버에게 요청하는 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;9,1,1,0&quot;&gt;목적 및 종류&lt;/b&gt;를 알리는 표준 수단입니다. 주로 CRUD(Create, Read, Update, Delete) 연산과 매핑됩니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0,0&quot;&gt;동작 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;HTTP 요청 메시지의 첫 줄(Start Line)에 명시되어 서버로 전달됩니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&amp;bull; &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;9,2,1,2&quot;&gt;GET:&lt;/b&gt; 데이터 조회 (Read)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&amp;bull; &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;9,2,1,4&quot;&gt;POST:&lt;/b&gt; 새로운 데이터 생성 (Create)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&amp;bull; &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;9,2,1,6&quot;&gt;PUT:&lt;/b&gt; 데이터 전체 교체/수정 (Update)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&amp;bull; &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;9,2,1,8&quot;&gt;PATCH:&lt;/b&gt; 데이터 부분 수정 (Update)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&amp;bull; &lt;b data-index-in-node=&quot;2&quot; data-path-to-node=&quot;9,2,1,10&quot;&gt;DELETE:&lt;/b&gt; 데이터 삭제 (Delete)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0,0&quot;&gt;장점/단점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,0&quot;&gt;장점:&lt;/b&gt; API 엔드포인트(URL)를 깔끔하게 유지할 수 있고, 캐싱(GET) 등 HTTP 프로토콜의 내장 기능을 100% 활용할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,2&quot;&gt;단점/한계:&lt;/b&gt; 구형 브라우저나 일부 엄격한 방화벽 환경에서는 GET과 POST만 지원하는 경우가 있어, 이를 우회하기 위한 설정(Hidden method 지원 등)이 필요할 때가 있습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0,0&quot;&gt;핵심 조건 (특징)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,1,0&quot;&gt;1. 안전성 (Safe):&lt;/b&gt; 호출해도 서버의 상태(데이터)를 변경하지 않는가? (GET 등)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,1,2&quot;&gt;2. 멱등성 (Idempotent):&lt;/b&gt; 동일한 요청을 1번 보내나 100번 보내나 서버의 결과 상태가 똑같은가? (GET, PUT, DELETE)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0,0&quot;&gt;예시/비교&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,1,0&quot;&gt;PUT vs PATCH:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;회원 정보 중 '이름'만 바꿀 때, PUT을 쓰면 이름 외의 나머지 정보(나이, 이메일 등)를 같이 보내지 않을 경우 &lt;b data-index-in-node=&quot;66&quot; data-path-to-node=&quot;9,5,1,2&quot;&gt;null이나 기본값으로 덮어씌워지는 대참사&lt;/b&gt;가 발생할 수 있습니다. 일부만 수정할 때는 PATCH를 써야 합니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;Interview Answer Version (면접 답변식 요약)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;&quot;HTTP 메서드는 클라이언트가 서버에게 수행하고자 하는 행위를 알려주는 통신 규약입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;핵심 메서드로는 조회를 위한 GET, 생성을 위한 POST, 수정을 위한 PUT과 PATCH, 삭제를 위한 DELETE가 있습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 URL은 자원만 명시하고 행위는 메서드에 위임하는 RESTful한 설계를 할 수 있습니다. 특히 실무에서는 '멱등성'을 고려하는 것이 중요한데, 데이터를 전체 교체하는 PUT이나 삭제하는 DELETE는 여러 번 요청해도 결과가 같아야 하지만, POST는 호출할 때마다 새로운 데이터가 생성되므로 재시도 로직을 설계할 때 멱등성 여부를 반드시 주의해야 합니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;13&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;Practical Tip (사용시 주의할 점 or 활용 예)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;실무 활용 예:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색, 필터링, 페이징 처리는 데이터의 변경이 없으므로 쿼리 파라미터(Query String)와 함께 GET을 사용합니다.&lt;/li&gt;
&lt;li&gt;결제 처리, 회원 가입 등 새로운 리소스가 생성되는 작업은 POST를 사용하며, Body에 민감한 데이터를 담아 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;설정 시 반드시 고려해야 할/주의할 점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,0,0&quot;&gt;GET 요청에 Body 담기 금지:&lt;/b&gt; 이론적으로 GET 요청에도 HTTP Body를 담을 수는 있으나, 표준에 어긋납니다. 많은 웹 서버나 프록시 장비가 GET 요청의 Body를 무시하거나 버리기 때문에 예상치 못한 버그가 발생합니다. 검색 조건이 너무 길어 URL에 담기 어렵다면 예외적으로 POST를 활용하여 검색 API를 설계하기도 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;&quot;이걸 모르고 사용하면 생기는 문제&quot; (멱등성 부재로 인한 중복 결제):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 지연으로 인해 클라이언트가 '결제하기(POST)' 버튼을 두 번 눌렀을 때, POST는 멱등성을 보장하지 않으므로 서버에 그대로 2건의 결제가 생성될 수 있습니다. 이를 막기 위해 클라이언트에서 고유한 Idempotency Key(멱등성 키)를 헤더에 담아 보내고, 서버가 이를 검증해 중복 POST 요청을 차단하는 방어 로직을 실무에서는 필수로 구현해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;예상 꼬리질문 정리&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;GET과 POST의 차이점에 대해 캐싱(Caching)과 보안(Security) 관점을 포함하여 설명해 주시겠어요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,1,0,0&quot;&gt;(의도: URL 파라미터 노출 여부, HTTP 캐시 컨트롤 적용 가능 여부 등 기본적인 차이를 명확히 아는지 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;PUT과 PATCH의 차이는 무엇이며, 각각 멱등성(Idempotence)을 만족하나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,1,0,0&quot;&gt;(의도: 전체 덮어쓰기와 부분 수정의 차이를 알고, 부분 수정(PATCH) 로직 작성 시 발생할 수 있는 멱등성 파괴 사례를 아는지 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0&quot;&gt;HTTP 1.1에 도입된 OPTIONS 메서드는 실무에서 언제 주로 발생하며, 어떤 역할을 하나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,1,0,0&quot;&gt;(의도: 프론트엔드와 백엔드 통신 시 자주 마주치는 CORS(Cross-Origin Resource Sharing)와 Preflight Request 개념 이해도 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Archive/Daily Dev Q&amp;amp;A</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/153</guid>
      <comments>https://tlsgkstj.tistory.com/153#entry153comment</comments>
      <pubDate>Wed, 25 Feb 2026 09:38:01 +0900</pubDate>
    </item>
    <item>
      <title>Daily Dev Q&amp;amp;A: 동기와 비동기</title>
      <link>https://tlsgkstj.tistory.com/152</link>
      <description>&lt;div id=&quot;model-response-message-contentr_7c036896e8a3edca&quot;&gt;
&lt;h2 data-path-to-node=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;2&quot;&gt;Topic (오늘의 주제)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;3&quot;&gt;동기(Synchronous)와 비동기(Asynchronous)의 차이와 활용&lt;/b&gt; 작업의 실행 순서와 흐름 제어를 결정하는 핵심 원리로, 효율적인 시스템 설계와 사용자 경험(UX) 향상을 위해 어떻게 코드를 구조화해야 하는지 학습합니다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;4&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5&quot;&gt;Why (왜 사용하는가? 왜 중요한가?)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;실무:&lt;/b&gt; 네트워크 API 호출, DB 쿼리, 파일 I/O 등 응답이 지연될 수 있는 작업에서 동기식을 사용하면 화면이 멈추거나(UI 프리징) 시스템이 응답을 멈추는 병목 현상이 발생합니다. 이를 막기 위해 비동기 처리가 필수적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;구조적 의미:&lt;/b&gt; CPU나 스레드가 작업 완료를 기다리며 쉬는 시간(Idle)을 없애고, 그 시간에 다른 작업을 처리하게 함으로써 한정된 시스템 자원을 극대화하고 처리량(Throughput)을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,0&quot;&gt;면접 의도:&lt;/b&gt; 지원자가 시스템의 병목 구간(I/O Bound)을 인지하고 최적화할 수 있는지, 멀티 스레드나 이벤트 루프 등 언어별 비동기 처리 메커니즘을 제대로 이해하고 코드를 작성해 보았는지를 확인하려 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;7&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;Core Concept (핵심 개념 정리)&lt;/b&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-ved=&quot;0CAAQ3ecQahgKEwjPr5H3qu6SAxUAAAAAHQAAAAAQtQk&quot; data-hveid=&quot;0&quot;&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;9&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,0,0,0&quot;&gt;요소&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,0,1,0&quot;&gt;내용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0,0&quot;&gt;개념 정의&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,0&quot;&gt;동기(Sync):&lt;/b&gt; 요청과 그 결과가 순차적으로 일어나는 방식 (이전 작업이 끝나야 다음 작업 시작).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,2&quot;&gt;비동기(Async):&lt;/b&gt; 요청을 보낸 후 결과를 기다리지 않고 곧바로 다음 작업을 진행하는 방식.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0,0&quot;&gt;동작 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,1&quot;&gt;동기:&lt;/b&gt; 스레드가 Task A를 실행 후, 결과가 반환될 때까지 대기(Block)하다가 Task B를 실행합니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,1,3&quot;&gt;비동기:&lt;/b&gt; Task A를 백그라운드(또는 다른 스레드)에 위임하고, 결과를 기다리지 않고 즉시 Task B를 실행합니다. Task A의 결과는 추후 콜백(Callback)이나 이벤트 형태로 전달받습니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0,0&quot;&gt;장점/단점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,0&quot;&gt;동기:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;장점: 코드가 위에서 아래로 실행되어 설계가 직관적이고 흐름 파악과 디버깅이 쉽다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;단점: 응답이 올 때까지 아무것도 못하고 자원을 낭비한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,1,6&quot;&gt;비동기:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;장점: 대기 시간 없이 다른 작업을 수행해 자원 효율과 응답성이 극도로 높다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;단점: 흐름 추적이 어렵고(콜백 지옥), 동기화 문제나 예외 처리가 까다롭다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,4,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0,0&quot;&gt;필요 조건&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,4,1,0&quot;&gt;비동기 결과를 처리하기 위한 반환 객체(Promise, Future)나 콜백 함수(Callback), 그리고 이를 스케줄링할 백그라운드 스레드나 이벤트 루프(Event Loop) 환경이 필요합니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,5,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0,0&quot;&gt;예시/비교&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,1,0&quot;&gt;Sync/Async vs Blocking/Non-blocking:&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span&gt;이 둘은 자주 혼용되지만 다릅니다. 동기/비동기는 &quot;작업 완료 여부를 누가 신경 쓰는가(순서의 문제)&quot;이고, 블로킹/논블로킹은 &quot;함수를 호출했을 때 제어권(실행 권한)을 바로 돌려주는가(대기의 문제)&quot;입니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;Interview Answer Version (면접 답변식 요약)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;&quot;동기와 비동기의 가장 큰 차이는 &lt;b data-index-in-node=&quot;19&quot; data-path-to-node=&quot;12,0&quot;&gt;작업의 순차적 실행 여부와 대기 시간&lt;/b&gt;에 있습니다. 동기 방식은 한 작업이 끝나야만 다음 작업을 수행하므로 설계가 직관적이지만, I/O 작업 시 스레드가 대기 상태에 빠져 자원 효율이 떨어집니다. 반면, 비동기 방식은 작업 처리를 백그라운드나 다른 스레드에 맡기고 제어권을 즉시 돌려받아 다른 작업을 수행합니다. 실무에서는 외부 API 호출이나 DB 조회와 같은 병목 구간에 비동기나 논블로킹 방식을 적용하여 시스템의 응답 속도와 전체 처리량(Throughput)을 최적화하는 데 사용합니다.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-path-to-node=&quot;13&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;Practical Tip (사용시 주의할 점 or 활용 예)&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;실무 활용 예:&lt;/b&gt; * 프론트엔드: 서버에 사용자 데이터를 요청(fetch API)하고, 데이터를 받아오는 동안 로딩 스피너 애니메이션을 보여줄 때.
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드: 사용자 회원가입 시, 핵심 로직(DB 저장) 후 시간이 걸리는 '환영 이메일 발송' 작업을 비동기(예: Spring @Async, 메시지 큐)로 처리하여 응답 속도를 높일 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;설정 시 반드시 고려해야 할/주의할 파라미터:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,0,0&quot;&gt;동시에 여러 비동기 실행하기:&lt;/b&gt; JavaScript에서 async/await를 쓸 때, 서로 의존성 없는 두 I/O 작업을 await로 각각 대기시키면 안 됩니다. Promise.all()을 사용하여 두 작업을 동시에 병렬로 던져 시간을 절약해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,1,0&quot;&gt;스레드 풀(Thread Pool) 관리:&lt;/b&gt; Java 환경 등에서 비동기 처리를 위해 무분별하게 스레드를 생성하면 Context Switching 비용이 더 커집니다. 반드시 스레드 풀 크기를 적절히 제한해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;&quot;이걸 모르고 사용하면 생기는 문제&quot; (예외 처리의 함정):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 코드 블록 내부에서 발생한 예외(Exception)는 메인 흐름의 일반적인 try-catch문으로 잡히지 않습니다. 비동기로 던져진 작업은 호출한 컨텍스트와 이미 분리되었기 때문입니다. 콜백 함수 내에서 에러 처리를 따로 하거나, .catch()를 명시적으로 작성하지 않으면 &lt;b data-index-in-node=&quot;158&quot; data-path-to-node=&quot;15,2,1,0,0&quot;&gt;'Unhandled Promise Rejection'&lt;/b&gt; 등으로 인해 시스템이 조용히 실패하거나 크래시될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;16&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17&quot;&gt;예상 꼬리질문 정리&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;18&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,0&quot;&gt;동기/비동기(Sync/Async)와 블로킹/논블로킹(Blocking/Non-blocking)을 각각 설명하고, 이 두 개념이 조합된 4가지 모델(예: Sync-Blocking, Async-NonBlocking 등)에 대해 설명해 주시겠어요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,0,1,0,0&quot;&gt;(의도: 제어권 반환이라는 개념과 완료 통지 방식의 차이를 정확히 구분할 수 있는지 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,0&quot;&gt;JavaScript는 싱글 스레드 언어라고 알고 있는데, 어떻게 네트워크 요청 같은 비동기 처리를 동시에 수행할 수 있나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,1,1,0,0&quot;&gt;(의도: Call Stack, Web API, Task Queue, Event Loop로 이어지는 런타임 환경 구조 이해도 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,0&quot;&gt;Java 환경 등에서 비동기 프로그래밍을 할 때 공유 자원에 접근해야 한다면, 어떤 동시성(Concurrency) 이슈가 발생할 수 있고 어떻게 해결해야 하나요?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;18,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,2,1,0,0&quot;&gt;(의도: 멀티 스레드 기반의 비동기 처리 시 Race Condition 제어 및 ThreadLocal 컨텍스트 유실 문제 해결 능력 확인)&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;20&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Archive/Daily Dev Q&amp;amp;A</category>
      <author>tlsgkstj</author>
      <guid isPermaLink="true">https://tlsgkstj.tistory.com/152</guid>
      <comments>https://tlsgkstj.tistory.com/152#entry152comment</comments>
      <pubDate>Tue, 24 Feb 2026 11:48:33 +0900</pubDate>
    </item>
  </channel>
</rss>