<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>AngelPlayer`s Diary</title>
    <link>https://angelplayer.tistory.com/</link>
    <description>Web / Develop / IT / HOBBY / etc...</description>
    <language>ko</language>
    <pubDate>Sat, 9 May 2026 21:26:58 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>AngelPlayer</managingEditor>
    <image>
      <title>AngelPlayer`s Diary</title>
      <url>https://tistory1.daumcdn.net/tistory/2828603/attach/66421318a58b48f1ac14d1a78ca53165</url>
      <link>https://angelplayer.tistory.com</link>
    </image>
    <item>
      <title>[DBeaver] Cloud Computer에 설치한 DB 원격 접속 방법</title>
      <link>https://angelplayer.tistory.com/604</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8Ci6/dJMcahjQUlO/Ih1bkr5Xny11QFmDBTadG0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8Ci6/dJMcahjQUlO/Ih1bkr5Xny11QFmDBTadG0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8Ci6/dJMcahjQUlO/Ih1bkr5Xny11QFmDBTadG0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8Ci6%2FdJMcahjQUlO%2FIh1bkr5Xny11QFmDBTadG0%2Fimg.jpg&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;500&quot; height=&quot;281&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;788&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;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;운영 서버에 데이터베이스(PostgreSQL)를 구축하고 나면, 로컬 PC에서 DB 툴을 이용해 데이터를 직접 조회하거나 관리해야 할 일이 자주 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이때 가장 간편한 방법은 DB 포트(기본 5432)를 외부 인터넷으로 개방하는 것이지만, 이는 보안상 &lt;b data-index-in-node=&quot;57&quot; data-path-to-node=&quot;4&quot;&gt;절대 해서는 안 되는 위험한 행동&lt;/b&gt;입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 가장 대중적인 무료 DB 툴인 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;5&quot;&gt;DBeaver&lt;/b&gt;의 &lt;b data-index-in-node=&quot;30&quot; data-path-to-node=&quot;5&quot;&gt;'SSH 터널링(SSH Tunneling)'&lt;/b&gt; 기능을 활용하여, 포트 개방 없이 안전하고 편리하게 운영 서버의 DB에 접속하는 방법을 알아보겠습니다.&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;Step 1. SSH 터널 뚫기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d72yAp/dJMcacbJV0N/tKq4MFl3dbdZVHG5sjKKyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d72yAp/dJMcacbJV0N/tKq4MFl3dbdZVHG5sjKKyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d72yAp/dJMcacbJV0N/tKq4MFl3dbdZVHG5sjKKyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd72yAp%2FdJMcacbJV0N%2FtKq4MFl3dbdZVHG5sjKKyK%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;1350&quot; height=&quot;803&quot; data-origin-width=&quot;1350&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBeaver에서 &lt;b&gt;새 데이터베이스 연결 (Ctrl + Shift + N)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnqAO/dJMcac3QYQG/SjBOE7TruV0YMCyu7HOHK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnqAO/dJMcac3QYQG/SjBOE7TruV0YMCyu7HOHK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnqAO/dJMcac3QYQG/SjBOE7TruV0YMCyu7HOHK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnqAO%2FdJMcac3QYQG%2FSjBOE7TruV0YMCyu7HOHK1%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;926&quot; height=&quot;587&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 클라우드 컴퓨터에 테스트를 위해 PostgreSQL를 설치 했기에 데이터베이스로 PostgreSQL를 선택했습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd0NAD/dJMcaffg5dV/0epZ12BUw5IlKG7g7Kc0nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd0NAD/dJMcaffg5dV/0epZ12BUw5IlKG7g7Kc0nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd0NAD/dJMcaffg5dV/0epZ12BUw5IlKG7g7Kc0nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd0NAD%2FdJMcaffg5dV%2F0epZ12BUw5IlKG7g7Kc0nK%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;926&quot; height=&quot;587&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 우측에 &lt;b&gt;SSH, SSL, Proxy&lt;/b&gt; 버튼을 클릭 해 &lt;b&gt;SSH&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqdO4u/dJMcacW518G/ad34y85vlpzi1FYtEn4gkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqdO4u/dJMcacW518G/ad34y85vlpzi1FYtEn4gkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqdO4u/dJMcacW518G/ad34y85vlpzi1FYtEn4gkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqdO4u%2FdJMcacW518G%2Fad34y85vlpzi1FYtEn4gkK%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;926&quot; height=&quot;587&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Host/IP&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Port&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;User Name&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication Method&lt;/b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Password 방식이 아니라 Public Key 방식으로 클라우드 컴퓨터에 접속하는데, DBeaver의 경우 확장자 명이 없는 Private Key 뿐만 아니라 PuTTY 등에서 사용하는 .ppk도 지원합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsQiB/dJMcah5cyjb/FyL6C68sF8j9TsqA4AFPo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsQiB/dJMcah5cyjb/FyL6C68sF8j9TsqA4AFPo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsQiB/dJMcah5cyjb/FyL6C68sF8j9TsqA4AFPo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsQiB%2FdJMcah5cyjb%2FFyL6C68sF8j9TsqA4AFPo0%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;926&quot; height=&quot;587&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSH 탭 설정을 마쳤다면, 이제 맨 앞의 &lt;b data-index-in-node=&quot;24&quot; data-path-to-node=&quot;16&quot;&gt;Main (일반)&lt;/b&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;ul style=&quot;list-style-type: disc;&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;Host:&lt;/b&gt; &lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;17,0,0&quot;&gt;localhost&lt;/b&gt; (또는 127.0.0.1)를 입력합니다.&amp;nbsp;&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;Port:&lt;/b&gt; 5432 (PostgreSQL 기본 포트)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;Database:&lt;/b&gt; 접속할 실제 데이터베이스 이름&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,3,0&quot;&gt;Username:&lt;/b&gt; DB 접속 계정명&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,4,0&quot;&gt;Password:&lt;/b&gt; DB 계정 비밀번호&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;여기서 Host가 내 클라우드 컴퓨터 IP가 아니라 localhost인 이유는 이전에 SSH 터널을 뚫어두었기 때문에, DBeaver 입장에서는 이 터널을 통해 원격 서버가 마치 내 컴퓨터(localhost) 안에 있는 것처럼 인식하기 때문입니다.&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;모든 정보를 입력한 후 창 하단의 &lt;b data-index-in-node=&quot;20&quot; data-path-to-node=&quot;5,0&quot;&gt;[Test Connection (연결 테스트)]&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cf2UFA/dJMcadn9tZw/ir1rj0lBViCM8ibd1wEK5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cf2UFA/dJMcadn9tZw/ir1rj0lBViCM8ibd1wEK5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cf2UFA/dJMcadn9tZw/ir1rj0lBViCM8ibd1wEK5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcf2UFA%2FdJMcadn9tZw%2Fir1rj0lBViCM8ibd1wEK5K%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;664&quot; height=&quot;440&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결이 성공하면 DB 목록에 잘 뜨는 것을 확인하실 수 있습니다.&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>Programming/etc</category>
      <category>dbeaver</category>
      <category>PostgreSQL</category>
      <category>SSH</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/604</guid>
      <comments>https://angelplayer.tistory.com/604#entry604comment</comments>
      <pubDate>Mon, 20 Apr 2026 10:07:19 +0900</pubDate>
    </item>
    <item>
      <title>[Error] CORS 에러 개념 &amp;amp; 해결방법 (feat. SpringBoot)</title>
      <link>https://angelplayer.tistory.com/602</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;CORS(Cross-Origin&amp;nbsp;Resource&amp;nbsp;Sharing)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CORS는&amp;nbsp;브라우저가&amp;nbsp;다른&amp;nbsp;출처(origin)로&amp;nbsp;보낸&amp;nbsp;요청의&amp;nbsp;응답을&amp;nbsp;JavaScript에서&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있을지&amp;nbsp;결정하는&amp;nbsp;보안&amp;nbsp;정책입니다. &lt;br /&gt;&lt;br /&gt;브라우저는&amp;nbsp;악성&amp;nbsp;사이트가&amp;nbsp;사용자의&amp;nbsp;인증&amp;nbsp;정보를&amp;nbsp;이용해&amp;nbsp;민감한&amp;nbsp;API&amp;nbsp;응답을&amp;nbsp;읽는&amp;nbsp;것을&amp;nbsp;방지하기&amp;nbsp;위해&amp;nbsp;기본적으로&amp;nbsp;다른&amp;nbsp;출처의&amp;nbsp;응답을&amp;nbsp;JavaScript에서&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;&lt;br /&gt;다른&amp;nbsp;출처(origin)는&amp;nbsp;크게&amp;nbsp; &lt;br /&gt;&lt;br /&gt;-&amp;nbsp;프로토콜:&amp;nbsp;http&amp;nbsp;/&amp;nbsp;https &lt;br /&gt;-&amp;nbsp;도메인(호스트):&amp;nbsp;localhost &lt;br /&gt;-&amp;nbsp;포트&amp;nbsp;:&amp;nbsp;3000,&amp;nbsp;8080 &lt;br /&gt;&lt;br /&gt;위&amp;nbsp;3가지로&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;&lt;br /&gt;프론트와&amp;nbsp;백을&amp;nbsp;기준으로&amp;nbsp;보면&amp;nbsp;어떤&amp;nbsp;사이트(3000)가&amp;nbsp;다른&amp;nbsp;사이트(8080)에&amp;nbsp;사용자의&amp;nbsp;쿠키를&amp;nbsp;포함해서&amp;nbsp;요청을&amp;nbsp;보내는&amp;nbsp;것을&amp;nbsp;예로&amp;nbsp;들&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;허용&amp;nbsp;방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. CorsConfig&lt;/p&gt;
&lt;pre id=&quot;code_1766301910420&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();

        // ✅ 쿠키 포함 요청 허용 (credentials: 'include' 때문에 필수)
        config.setAllowCredentials(true);

        // ✅ 허용할 프론트 origin을 정확히 명시해야 함 (* 금지)
        config.setAllowedOrigins(List.of(&quot;http://localhost:3000&quot;));

        // ✅ 허용 메서드/헤더
        config.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;));
        config.setAllowedHeaders(List.of(&quot;*&quot;));

        // (선택) Set-Cookie 등을 브라우저에서 읽어야 할 때
        // config.setExposedHeaders(List.of(&quot;Set-Cookie&quot;));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, config);
        return source;
    }
}&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;2. 코드 활성&lt;/p&gt;
&lt;pre id=&quot;code_1766302837926&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors(Customizer.withDefaults())
        .csrf(csrf -&amp;gt; csrf.disable());

    return http.build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CorsConfig를 활성화하는 코드를 SecurityFilterChain 내부에에 입력면 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;&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;정리 : CORS는 브라우저가 다른 출처의 응답을 JavaScript에서 읽을 수 있을지 결정하는 보안 정책이며, Spring Security에서는 별도로 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;&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;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>Programming/etc</category>
      <category>CORS</category>
      <category>error</category>
      <category>java</category>
      <category>javascript</category>
      <category>springboot</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/602</guid>
      <comments>https://angelplayer.tistory.com/602#entry602comment</comments>
      <pubDate>Sun, 21 Dec 2025 16:46:47 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Windows에 Redis 설치하기</title>
      <link>https://angelplayer.tistory.com/601</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Windows 환경에서 레디스를 설치 후 사용하는 방법은 크게 2가지가 있습니다.&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;1. Redis for Windows 설치 파일을 통한 Windows 내 Redis 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 가상환경에 Redis 설치&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;&lt;a href=&quot;https://github.com/microsoftarchive/redis/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/microsoftarchive/redis/releases&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766207836727&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Releases &amp;middot; microsoftarchive/redis&quot; data-og-description=&quot;Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes - microsoftarchive/redis&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/microsoftarchive/redis/releases&quot; data-og-url=&quot;https://github.com/microsoftarchive/redis/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceYNzs/hyZPVReJ3e/zSsRp12mB9OTXeKbaoyOkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cJAex2/hyZPVjoWJ4/kFPcwQGKNm1iWdofelug80/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/microsoftarchive/redis/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/microsoftarchive/redis/releases&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceYNzs/hyZPVReJ3e/zSsRp12mB9OTXeKbaoyOkK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cJAex2/hyZPVjoWJ4/kFPcwQGKNm1iWdofelug80/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Releases &amp;middot; microsoftarchive/redis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes - microsoftarchive/redis&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/niJ2T/dJMcahiG112/asud7aLtK6MrjRlqkyVnZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/niJ2T/dJMcahiG112/asud7aLtK6MrjRlqkyVnZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/niJ2T/dJMcahiG112/asud7aLtK6MrjRlqkyVnZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FniJ2T%2FdJMcahiG112%2Fasud7aLtK6MrjRlqkyVnZ1%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;1466&quot; height=&quot;785&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 경우 2021년 이후 업데이트가 진행되지 않아 레디스 버전을 6.x 밖에 사용하지 못합니다.&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;따라서 저희는 WSL2 + Ubuntu 환경에 Redis를 설치하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ WSL (Windows Subsystem for Linux) : Windows 위에서 Linux를 실행하게 해주는 호환 계층(subsystem) &lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;※&lt;span&gt; &lt;/span&gt;&lt;/span&gt;WSL2 : WSL1은&amp;nbsp;Windows&amp;nbsp;커널&amp;nbsp;위에서&amp;nbsp;Linux&amp;nbsp;시스템&amp;nbsp;콜을&amp;nbsp;번역하여&amp;nbsp;실행하는&amp;nbsp;방식인&amp;nbsp;반면,&amp;nbsp;WSL2는&amp;nbsp;실제&amp;nbsp;Linux&amp;nbsp;커널을&amp;nbsp;가상&amp;nbsp;머신에서&amp;nbsp;실행하는&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;h2 data-ke-size=&quot;size26&quot;&gt;1. Ubuntu 설치&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diufQv/dJMcah33LY9/fkYZMBzhFbIkOPenQGo8e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diufQv/dJMcah33LY9/fkYZMBzhFbIkOPenQGo8e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diufQv/dJMcah33LY9/fkYZMBzhFbIkOPenQGo8e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiufQv%2FdJMcah33LY9%2FfkYZMBzhFbIkOPenQGo8e1%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;953&quot; height=&quot;613&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&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;먼저 작업 관리자에서 성능 - CPU로 들어가 가상화가 사용으로 되어있는지 확인해야 합니다.&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;만약 사용 안 함으로 되어 있다면, BIOS 설정 등에서 가상화 기능을 켜주셔야 합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/degOqb/dJMcaaw6zbs/iT8xcA5KRU5f5H791FPJbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/degOqb/dJMcaaw6zbs/iT8xcA5KRU5f5H791FPJbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/degOqb/dJMcaaw6zbs/iT8xcA5KRU5f5H791FPJbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdegOqb%2FdJMcaaw6zbs%2FiT8xcA5KRU5f5H791FPJbk%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;415&quot; height=&quot;368&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows 기능 켜기/끄기에서 아래 3가지 기능을 켜주셔야 합니다.&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;br /&gt;- Linux용 Windows 하위 시스템 &lt;br /&gt;- Hyper-V (Optional)&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;현재 테스트 중인 PC의 경우 Windows(11) Home Edition이 설치되어 있는데, Home 버전의 경우 Hyper-V 기능이 따로 없습니다.&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;다만 Hyper-V 없이도 WSL2를 사용할 수 있기 때문에 Windows Home의 경우 Hyper-V를 제외한 두 가지를 체크 한 뒤 확인을 눌러줍니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H0Um4/dJMcai2Vak0/NeCmk4o1NRGY1sRfCwJEh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H0Um4/dJMcai2Vak0/NeCmk4o1NRGY1sRfCwJEh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H0Um4/dJMcai2Vak0/NeCmk4o1NRGY1sRfCwJEh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH0Um4%2FdJMcai2Vak0%2FNeCmk4o1NRGY1sRfCwJEh1%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;951&quot; height=&quot;512&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 PowerShell을 켜줍니다.&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;PowerShell 실행 시 관리자 권한으로 실행해야 합니다.&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;pre id=&quot;code_1766208282080&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wsl --set-default-version 2

wsl --status       # Default Version: 2 출력을 확인하기

wsl --update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PowerShell에서 위 명령을 입력한 뒤 정상적으로 완료되는지 확인합니다.&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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YCWkd/dJMcahwdZZQ/c4C1ZHFo41C6nogzAKPW91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YCWkd/dJMcahwdZZQ/c4C1ZHFo41C6nogzAKPW91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YCWkd/dJMcahwdZZQ/c4C1ZHFo41C6nogzAKPW91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYCWkd%2FdJMcahwdZZQ%2Fc4C1ZHFo41C6nogzAKPW91%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;1404&quot; height=&quot;932&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Microsoft Store에서 Ubuntu를 설치합니다.&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;저는 22.04 버전으로 설치를 진행하였습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9vw10/dJMcahXjpzC/sasMyQMZ1ssxAhq7r7Tirk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9vw10/dJMcahXjpzC/sasMyQMZ1ssxAhq7r7Tirk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9vw10/dJMcahXjpzC/sasMyQMZ1ssxAhq7r7Tirk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9vw10%2FdJMcahXjpzC%2FsasMyQMZ1ssxAhq7r7Tirk%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;925&quot; height=&quot;125&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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;※ Ubuntu 설치 후 실행 시 에러 발생 시&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYwrFJ/dJMcadN4vNk/2gJb1SPPPYxbKNqUwOoMTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYwrFJ/dJMcadN4vNk/2gJb1SPPPYxbKNqUwOoMTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYwrFJ/dJMcadN4vNk/2gJb1SPPPYxbKNqUwOoMTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYwrFJ%2FdJMcadN4vNk%2F2gJb1SPPPYxbKNqUwOoMTk%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;1113&quot; height=&quot;626&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 설치가 제대로 되지 않는다면 0x80370114 에러가 발생할 수 있습니다.&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;- Virtual Machine Platform 비활성화 &lt;br /&gt;- BIOS&amp;nbsp;가상화&amp;nbsp;OFF &lt;br /&gt;- Windows&amp;nbsp;기능&amp;nbsp;반영&amp;nbsp;실패&amp;nbsp;(UI&amp;nbsp;체크만&amp;nbsp;한&amp;nbsp;경우) &lt;br /&gt;- VMware&amp;nbsp;/&amp;nbsp;VirtualBox&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;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;pre id=&quot;code_1766208545879&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all

dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all


# 다시 작성
wsl --set-default-version 2

wsl --status       # Default Version: 2 출력을 확인하기

wsl --update&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다시 PowerShell을 열어서 dism 명령어를 작성합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후&amp;nbsp;재부팅&amp;nbsp;및&amp;nbsp;Ubuntu&amp;nbsp;재설치를&amp;nbsp;진행하면&amp;nbsp;Redis를&amp;nbsp;설치할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;WSL2&amp;nbsp;환경&amp;nbsp;구성이&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;h2 data-ke-size=&quot;size26&quot;&gt;2. Redis 설치&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cllsCr/dJMcahptaRc/noUtUFnykqC888GpKdPDJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cllsCr/dJMcahptaRc/noUtUFnykqC888GpKdPDJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cllsCr/dJMcahptaRc/noUtUFnykqC888GpKdPDJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcllsCr%2FdJMcahptaRc%2FnoUtUFnykqC888GpKdPDJ1%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;1113&quot; height=&quot;626&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1766209729979&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo &quot;deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main&quot; | sudo tee /etc/apt/sources.list.d/redis.list

sudo apt-get update

sudo apt-get install redis&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 Redis 설치를 진행합니다.&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;Ubuntu 터미널에서 위 명령어를 입력합니다.&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;install redis 명령이 정상적으로 동작하면 설치가 완료된 것입니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdQ9jT/dJMcafFbsTb/7V2hUCgR1TW1qOpMG3HOQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdQ9jT/dJMcafFbsTb/7V2hUCgR1TW1qOpMG3HOQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdQ9jT/dJMcafFbsTb/7V2hUCgR1TW1qOpMG3HOQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdQ9jT%2FdJMcafFbsTb%2F7V2hUCgR1TW1qOpMG3HOQ1%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;1113&quot; height=&quot;626&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후 테스트를 위해 redis-cli로 들어가 ping을 입력합니다.&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;응답으로 PONG이 오면 정상적으로 동작하는 것입니다.&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;pre id=&quot;code_1766915825618&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Redis 설치
sudo apt update
sudo apt install -y redis-server


# redis 서비스 시작
sudo service redis-server start
sudo service redis-server status


# 재부팅 후에도 자동 실행되게 하는 명령어
sudo systemctl enable redis-server


# Redis 비밀번호 설정
sudo nano /etc/redis/redis.conf

requirepass 비밀번호입력

sudo service redis-server restart 


# 테스트
redis-cli ping
redis-cli -a 비밀번호 ping&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;</description>
      <category>Web/etc</category>
      <category>redis</category>
      <category>ubuntu</category>
      <category>windows</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/601</guid>
      <comments>https://angelplayer.tistory.com/601#entry601comment</comments>
      <pubDate>Sat, 20 Dec 2025 14:50:31 +0900</pubDate>
    </item>
    <item>
      <title>[OAuth] 02 Session에서 JWT + Token 방식으로 전환</title>
      <link>https://angelplayer.tistory.com/600</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 포스트에서 Google OAuth와 SpringBoot, Next.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;기존 구현에서는 로그인한 사용자를 인증하기 위해 Session 방식을 사용하였습니다.&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;이전 포스트의 소스코드 : 세션방식&lt;br /&gt;Google&amp;nbsp;OAuth&amp;nbsp;로그인&amp;nbsp;&amp;rarr;&amp;nbsp;Spring&amp;nbsp;Security&amp;nbsp;세션에&amp;nbsp;OAuth2User&amp;nbsp;저장 &lt;br /&gt;브라우저는&amp;nbsp;JSESSIONID&amp;nbsp;쿠키로&amp;nbsp;로그인&amp;nbsp;유지 &lt;br /&gt;컨트롤러에서&amp;nbsp;@AuthenticationPrincipal&amp;nbsp;OAuth2User&amp;nbsp;principal&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;br /&gt;앞으로 개선할 방향 : JWT 방식&lt;br /&gt;OAuth&amp;nbsp;로그인&amp;nbsp;성공&amp;nbsp;시 &lt;br /&gt;서버가&amp;nbsp;Access&amp;nbsp;Token&amp;nbsp;/&amp;nbsp;Refresh&amp;nbsp;Token(JWT)&amp;nbsp;를&amp;nbsp;생성 &lt;br /&gt;이&amp;nbsp;둘을&amp;nbsp;HttpOnly&amp;nbsp;쿠키(예:&amp;nbsp;ACCESS_TOKEN,&amp;nbsp;REFRESH_TOKEN)&amp;nbsp;로&amp;nbsp;내려줌 &lt;br /&gt;&lt;br /&gt;프론트는&amp;nbsp;그냥&amp;nbsp;credentials:&amp;nbsp;'include'&amp;nbsp;그대로&amp;nbsp;사용&amp;nbsp;&amp;rarr;&amp;nbsp;쿠키&amp;nbsp;자동&amp;nbsp;전송 &lt;br /&gt;이후&amp;nbsp;API&amp;nbsp;호출 &lt;br /&gt;&lt;br /&gt;JSESSIONID&amp;nbsp;대신&amp;nbsp;ACCESS_TOKEN&amp;nbsp;쿠키를&amp;nbsp;읽어&amp;nbsp;검증 &lt;br /&gt;토큰이&amp;nbsp;유효하면&amp;nbsp;SecurityContext&amp;nbsp;에&amp;nbsp;Authentication&amp;nbsp;넣어줌 &lt;br /&gt;컨트롤러는&amp;nbsp;@AuthenticationPrincipal&amp;nbsp;로&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;사용 &lt;br /&gt;&lt;br /&gt;세션은&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;인증에&amp;nbsp;쓰이지&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;h2 data-ke-size=&quot;size26&quot;&gt;백엔드 수정 사항&lt;/h2&gt;
&lt;pre id=&quot;code_1764582179715&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;com/tistory/angelplayer/oauth
    ├── OauthApplication.java                      # 스프링 부트 메인 클래스
    │
    ├── config
    │    ├── SecurityConfig.java                   # ★ 시큐리티 설정 (OAuth2 + JWT + CORS)
    │    ├── JwtTokenProvider.java                 # ★ JWT 생성/검증 유틸
    │    └── JwtAuthenticationFilter.java          # ★ 요청마다 AccessToken 검증 필터
    │
    ├── auth
    │    ├── controller
    │    │      └── AuthController.java            # ★ /me, /refresh, /logout API
    │    │
    │    ├── handler
    │    │      └── OAuth2LoginSuccessHandler.java # ★ 구글 로그인 성공 &amp;rarr; JWT 발급 &amp;amp; 쿠키 저장
    │    │
    │    ├── entity
    │    │      └── RefreshToken.java              # ★ 사용자별 RefreshToken 저장 엔티티
    │    │
    │    ├── repository
    │    │      └── RefreshTokenRepository.java    # ★ RefreshToken JPA 리포지토리
    │    │
    │    └── service
    │           └── AuthService.java               # ★ RefreshToken 저장/검증 &amp;amp; 토큰 재발급 로직
    │
    └── guestbook
         ├── controller
         │      └── GuestbookController.java       # 방명록 API (JWT 인증 필요)
         ├── dto
         │      └── GuestbookRequestDto.java
         ├── entity
         │      └── GuestbookEntry.java
         ├── repository
         │      └── GuestbookEntryRepository.java
         └── service
                └── GuestbookService.java&lt;/code&gt;&lt;/pre&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;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;1) build.gardle&lt;/p&gt;
&lt;pre id=&quot;code_1764580338083&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    // ... 기존 내용

    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT&amp;nbsp;서명용&amp;nbsp;키와&amp;nbsp;유효시간을&amp;nbsp;설정합니다. &lt;br /&gt;&lt;br /&gt;JwtTokenProvider&amp;nbsp;가&amp;nbsp;이&amp;nbsp;값을&amp;nbsp;읽어서&amp;nbsp;토큰을&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;2) application.yml&amp;nbsp;에&amp;nbsp;JWT&amp;nbsp;설정&amp;nbsp;값&amp;nbsp;추가&lt;/p&gt;
&lt;pre id=&quot;code_1764580599809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jwt:
  secret: &quot;ZmFrZV9zZWNyZXRfZmFrZV9zZWNyZXRfZmFrZV9zZWNyZXQ=&quot;  # 32바이트 이상 Base64 문자열
  access-token-validity-in-seconds: 3600       # Access Token 1시간
  refresh-token-validity-in-seconds: 604800    # Refresh Token 7일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt:는 spring:과 동급입니다.&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;&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;3) JwtTokenProvider.java&amp;nbsp;(JWT&amp;nbsp;생성/검증&amp;nbsp;유틸)&lt;/p&gt;
&lt;pre id=&quot;code_1764582285933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Collections;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    @Value(&quot;${jwt.secret}&quot;)
    private String secret;

    @Value(&quot;${jwt.access-token-validity-in-seconds}&quot;)
    private long accessTokenValidityInSeconds;

    @Value(&quot;${jwt.refresh-token-validity-in-seconds}&quot;)
    private long refreshTokenValidityInSeconds;

    private Key key;

    @PostConstruct
    public void init() {
        // application.yml의 Base64 secret 문자열을 디코딩해서 HMAC 키 생성
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    // AccessToken 생성 (email, name claim 포함)
    public String createAccessToken(String email, String name) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + accessTokenValidityInSeconds * 1000);

        return Jwts.builder()
                .setSubject(email)          // 토큰 주체: 이메일
                .claim(&quot;name&quot;, name)        // 클레임에 이름 저장 (필요 시 사용)
                .setIssuedAt(now)           // 발급 시각
                .setExpiration(expiry)      // 만료 시각
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    // RefreshToken 생성
    public String createRefreshToken(String email) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + refreshTokenValidityInSeconds * 1000);

        return Jwts.builder()
                .setSubject(email)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    // 토큰 유효성 검증 (서명 + 만료 체크)
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            // 서명 오류, 만료, 형식 오류 등 &amp;rarr; 전부 false 처리
            return false;
        }
    }

    // 토큰에서 Claims 꺼내기 (이메일, name 등 필요하면 사용)
    public Claims getClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // 토큰에서 Authentication 객체 생성
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);
        String email = claims.getSubject();

        // 현재는 ROLE_USER 하나만 부여
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(&quot;ROLE_USER&quot;);

        User principal = new User(
                email,                               // username
                &quot;&quot;,                                  // password (사용 안 함)
                Collections.singletonList(authority) // 권한 리스트
        );

        return new UsernamePasswordAuthenticationToken(
                principal,
                token,                               // credentials 자리에 토큰 자체
                principal.getAuthorities()
        );
    }

    public long getAccessTokenValidityInSeconds() {
        return accessTokenValidityInSeconds;
    }

    public long getRefreshTokenValidityInSeconds() {
        return refreshTokenValidityInSeconds;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AccessToken&amp;nbsp;/&amp;nbsp;RefreshToken&amp;nbsp;생성 &lt;br /&gt;&lt;br /&gt;토큰의&amp;nbsp;유효성&amp;nbsp;검증 &lt;br /&gt;&lt;br /&gt;토큰에서&amp;nbsp;Authentication&amp;nbsp;객체&amp;nbsp;만들기&amp;nbsp;(email&amp;nbsp;기반&amp;nbsp;UserDetails)&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;4) JwtAuthenticationFilter.java&amp;nbsp;(요청마다&amp;nbsp;AccessToken&amp;nbsp;인증)&lt;/p&gt;
&lt;pre id=&quot;code_1764582905412&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// com/tistory/angelplayer/oauth/config/JwtAuthenticationFilter.java

package com.tistory.angelplayer.oauth.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String token = resolveToken(request);

        if (token != null &amp;amp;&amp;amp; jwtTokenProvider.validateToken(token)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    // 쿠키(ACCESS_TOKEN) 또는 Authorization 헤더에서 토큰 추출
    private String resolveToken(HttpServletRequest request) {
        // 1) 쿠키 우선
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if (&quot;ACCESS_TOKEN&quot;.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }

        // 2) Authorization: Bearer 토큰도 지원 (필요시)
        String bearer = request.getHeader(&quot;Authorization&quot;);
        if (bearer != null &amp;amp;&amp;amp; bearer.startsWith(&quot;Bearer &quot;)) {
            return bearer.substring(7);
        }

        return 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;매&amp;nbsp;HTTP&amp;nbsp;요청마다&amp;nbsp;쿠키의&amp;nbsp;ACCESS_TOKEN&amp;nbsp;또는&amp;nbsp;Authorization&amp;nbsp;헤더에서&amp;nbsp;토큰을&amp;nbsp;읽고&amp;nbsp;검증 &lt;br /&gt;&lt;br /&gt;유효하면&amp;nbsp;SecurityContext&amp;nbsp;에&amp;nbsp;Authentication&amp;nbsp;세팅 &lt;br /&gt;&lt;br /&gt;세션&amp;nbsp;없이&amp;nbsp;JWT만으로&amp;nbsp;인증을&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;5. RefreshToken.java&amp;nbsp;(RefreshToken&amp;nbsp;엔티티)&lt;/p&gt;
&lt;pre id=&quot;code_1764582965009&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// com/tistory/angelplayer/oauth/auth/entity/RefreshToken.java

package com.tistory.angelplayer.oauth.auth.entity;

import jakarta.persistence.*;
import lombok.*;

import java.time.Instant;

@Entity
@Table(name = &quot;refresh_tokens&quot;)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RefreshToken {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 이메일을 사용자 식별자처럼 사용 (별도 User 엔티티 없으므로)
    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false, length = 512)
    private String token;

    @Column(nullable = false)
    private Instant expiry;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자(email)별&amp;nbsp;현재&amp;nbsp;유효한&amp;nbsp;RefreshToken&amp;nbsp;을&amp;nbsp;DB에&amp;nbsp;저장 &lt;br /&gt;&lt;br /&gt;재발급&amp;nbsp;시&amp;nbsp;저장된&amp;nbsp;토큰과&amp;nbsp;비교하여&amp;nbsp;탈취/중복&amp;nbsp;사용&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;6) RefreshTokenRepository.java&amp;nbsp;(리포지토리)&lt;/p&gt;
&lt;pre id=&quot;code_1764583109945&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.auth.repository;

import com.tistory.angelplayer.oauth.auth.entity.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface RefreshTokenRepository extends JpaRepository&amp;lt;RefreshToken, Long&amp;gt; {

    Optional&amp;lt;RefreshToken&amp;gt; findByEmail(String email);

    Optional&amp;lt;RefreshToken&amp;gt; findByToken(String token);

    void deleteByEmail(String email);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일&amp;nbsp;기준으로&amp;nbsp;RefreshToken을&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;7) AuthService.java&amp;nbsp;(RefreshToken&amp;nbsp;관리&amp;nbsp;+&amp;nbsp;재발급&amp;nbsp;로직)&lt;/p&gt;
&lt;pre id=&quot;code_1764583169678&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.auth.service;

import com.tistory.angelplayer.oauth.auth.entity.RefreshToken;
import com.tistory.angelplayer.oauth.auth.repository.RefreshTokenRepository;
import com.tistory.angelplayer.oauth.config.JwtTokenProvider;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final JwtTokenProvider jwtTokenProvider;
    private final RefreshTokenRepository refreshTokenRepository;

    // 로그인 성공 시 RefreshToken 저장/업데이트
    @Transactional
    public void saveRefreshToken(String email, String refreshToken) {
        Instant expiry = Instant.now()
                .plusSeconds(jwtTokenProvider.getRefreshTokenValidityInSeconds());

        refreshTokenRepository.findByEmail(email)
                .ifPresentOrElse(
                        entity -&amp;gt; {
                            entity.setToken(refreshToken);
                            entity.setExpiry(expiry);
                        },
                        () -&amp;gt; {
                            RefreshToken newToken = RefreshToken.builder()
                                    .email(email)
                                    .token(refreshToken)
                                    .expiry(expiry)
                                    .build();
                            refreshTokenRepository.save(newToken);
                        }
                );
    }

    // 로그아웃 시 RefreshToken 삭제
    @Transactional
    public void deleteRefreshToken(String email) {
        refreshTokenRepository.deleteByEmail(email);
    }

    // RefreshToken으로 AccessToken + RefreshToken 재발급
    @Transactional
    public Map&amp;lt;String, String&amp;gt; reissueTokens(String refreshToken) {
        // 1) 토큰 형식/만료 검증
        if (!jwtTokenProvider.validateToken(refreshToken)) {
            throw new IllegalArgumentException(&quot;유효하지 않거나 만료된 RefreshToken 입니다.&quot;);
        }

        Claims claims = jwtTokenProvider.getClaims(refreshToken);
        String email = claims.getSubject();

        // 2) DB에 저장된 RefreshToken과 일치하는지 확인
        RefreshToken saved = refreshTokenRepository.findByEmail(email)
                .orElseThrow(() -&amp;gt; new IllegalArgumentException(&quot;저장된 RefreshToken이 없습니다.&quot;));

        if (!saved.getToken().equals(refreshToken)) {
            throw new IllegalArgumentException(&quot;RefreshToken이 일치하지 않습니다.&quot;);
        }

        if (saved.getExpiry().isBefore(Instant.now())) {
            throw new IllegalArgumentException(&quot;RefreshToken이 만료되었습니다.&quot;);
        }

        // 3) 새 AccessToken + RefreshToken 생성
        String newAccessToken = jwtTokenProvider.createAccessToken(email, (String) claims.get(&quot;name&quot;));
        String newRefreshToken = jwtTokenProvider.createRefreshToken(email);

        // 4) DB에 RefreshToken 업데이트
        saveRefreshToken(email, newRefreshToken);

        return Map.of(
                &quot;accessToken&quot;, newAccessToken,
                &quot;refreshToken&quot;, newRefreshToken
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인&amp;nbsp;성공&amp;nbsp;시&amp;nbsp;RefreshToken&amp;nbsp;저장/업데이트 &lt;br /&gt;&lt;br /&gt;전달받은&amp;nbsp;RefreshToken&amp;nbsp;검증&amp;nbsp;후,&amp;nbsp;새&amp;nbsp;AccessToken&amp;nbsp;+&amp;nbsp;RefreshToken&amp;nbsp;발급 &lt;br /&gt;&lt;br /&gt;로그아웃&amp;nbsp;시&amp;nbsp;RefreshToken&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;8) OAuth2LoginSuccessHandler.java&amp;nbsp;(구글&amp;nbsp;로그인&amp;nbsp;성공&amp;nbsp;&amp;rarr;&amp;nbsp;JWT&amp;nbsp;+&amp;nbsp;쿠키)&lt;/p&gt;
&lt;pre id=&quot;code_1764583256082&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.auth.handler;

import com.tistory.angelplayer.oauth.auth.service.AuthService;
import com.tistory.angelplayer.oauth.config.JwtTokenProvider;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final JwtTokenProvider jwtTokenProvider;
    private final AuthService authService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
            throws IOException, ServletException {

        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        Map&amp;lt;String, Object&amp;gt; attrs = oAuth2User.getAttributes();

        String email = (String) attrs.get(&quot;email&quot;);
        String name  = (String) attrs.get(&quot;name&quot;);

        // 1) Access / Refresh Token 생성
        String accessToken  = jwtTokenProvider.createAccessToken(email, name);
        String refreshToken = jwtTokenProvider.createRefreshToken(email);

        // 2) RefreshToken DB 저장
        authService.saveRefreshToken(email, refreshToken);

        // 3) 쿠키에 저장 (HttpOnly)
        addCookie(response, &quot;ACCESS_TOKEN&quot;, accessToken,
                (int) jwtTokenProvider.getAccessTokenValidityInSeconds());
        addCookie(response, &quot;REFRESH_TOKEN&quot;, refreshToken,
                (int) jwtTokenProvider.getRefreshTokenValidityInSeconds());

        // 4) 프론트엔드 페이지로 리다이렉트
        response.sendRedirect(&quot;http://localhost:3000/guestbook&quot;);
    }

    private void addCookie(HttpServletResponse response,
                           String name,
                           String value,
                           int maxAge) {

        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true);  // JS에서 접근 불가 &amp;rarr; XSS 방지
        cookie.setPath(&quot;/&quot;);
        cookie.setMaxAge(maxAge);
        // 개발 환경에서는 http, 운영에서는 https + Secure 설정 권장
        // cookie.setSecure(true);

        response.addCookie(cookie);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google OAuth2 로그인 성공 시 아래 기능을 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구글 프로필에서 email, name 추출 &lt;br /&gt;- AccessToken&amp;nbsp;+&amp;nbsp;RefreshToken&amp;nbsp;생성 &lt;br /&gt;- RefreshToken&amp;nbsp;DB&amp;nbsp;저장 &lt;br /&gt;- 두&amp;nbsp;토큰을&amp;nbsp;HttpOnly&amp;nbsp;쿠키&amp;nbsp;로&amp;nbsp;브라우저에&amp;nbsp;전달 &lt;br /&gt;- 프론트&amp;nbsp;/guestbook&amp;nbsp;페이지로&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;9) SecurityConfig.java&amp;nbsp;(시큐리티&amp;nbsp;설정&amp;nbsp;변경)&lt;/p&gt;
&lt;pre id=&quot;code_1764583334558&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.config;

import com.tistory.angelplayer.oauth.auth.handler.OAuth2LoginSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;
    private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf(csrf -&amp;gt; csrf.disable())
                .cors(Customizer.withDefaults())
                .sessionManagement(session -&amp;gt;
                        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authorizeHttpRequests(auth -&amp;gt; auth
                        .requestMatchers(&quot;/&quot;, &quot;/oauth2/**&quot;, &quot;/login/**&quot;).permitAll()
                        .requestMatchers(&quot;/api/auth/**&quot;).permitAll() // /me, /refresh, /logout
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth -&amp;gt; oauth
                        .successHandler(oAuth2LoginSuccessHandler)
                )
                .logout(logout -&amp;gt; logout
                        .logoutUrl(&quot;/logout&quot;)
                        .deleteCookies(&quot;ACCESS_TOKEN&quot;, &quot;REFRESH_TOKEN&quot;)
                        .logoutSuccessUrl(&quot;http://localhost:3000&quot;)
                );

        // JWT 필터를 UsernamePasswordAuthenticationFilter 앞에 추가
        http.addFilterBefore(
                new JwtAuthenticationFilter(jwtTokenProvider),
                UsernamePasswordAuthenticationFilter.class
        );

        return http.build();
    }

    // CORS: 프론트 개발용 설정
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(List.of(&quot;http://localhost:3000&quot;));
        config.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;));
        config.setAllowedHeaders(List.of(&quot;*&quot;));
        config.setAllowCredentials(true); // 쿠키 포함 허용

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, config);
        return source;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션을&amp;nbsp;사용하지&amp;nbsp;않고&amp;nbsp;STATELESS&amp;nbsp;로&amp;nbsp;설정 &lt;br /&gt;&lt;br /&gt;OAuth2&amp;nbsp;로그인&amp;nbsp;성공&amp;nbsp;시&amp;nbsp;OAuth2LoginSuccessHandler&amp;nbsp;사용 &lt;br /&gt;&lt;br /&gt;JwtAuthenticationFilter&amp;nbsp;를&amp;nbsp;필터&amp;nbsp;체인에&amp;nbsp;추가 &lt;br /&gt;&lt;br /&gt;CORS&amp;nbsp;설정&amp;nbsp;(localhost:3000&amp;nbsp;허용) &lt;br /&gt;&lt;br /&gt;/api/auth/**,&amp;nbsp;/oauth2/**&amp;nbsp;등&amp;nbsp;일부&amp;nbsp;엔드포인트는&amp;nbsp;인증&amp;nbsp;없이&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;10) AuthController.java&amp;nbsp;(me&amp;nbsp;+&amp;nbsp;refresh&amp;nbsp;+&amp;nbsp;logout&amp;nbsp;API)&lt;/p&gt;
&lt;pre id=&quot;code_1764583384953&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.auth.controller;

import com.tistory.angelplayer.oauth.auth.service.AuthService;
import com.tistory.angelplayer.oauth.config.JwtTokenProvider;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping(&quot;/api/auth&quot;)
@RequiredArgsConstructor
public class AuthController {

    private final JwtTokenProvider jwtTokenProvider;
    private final AuthService authService;

    // 1) 현재 로그인 정보 조회
    @GetMapping(&quot;/me&quot;)
    public Map&amp;lt;String, Object&amp;gt; me(@AuthenticationPrincipal UserDetails user) {
        if (user == null) {
            return Map.of(&quot;authenticated&quot;, false);
        }
        return Map.of(
                &quot;authenticated&quot;, true,
                &quot;email&quot;, user.getUsername()
        );
    }

    // 2) RefreshToken으로 AccessToken + RefreshToken 재발급
    @PostMapping(&quot;/refresh&quot;)
    public Map&amp;lt;String, Object&amp;gt; refresh(HttpServletRequest request,
                                       HttpServletResponse response) {

        String refreshToken = extractCookie(request, &quot;REFRESH_TOKEN&quot;);
        if (refreshToken == null) {
            throw new IllegalArgumentException(&quot;RefreshToken 쿠키가 없습니다.&quot;);
        }

        Map&amp;lt;String, String&amp;gt; tokens = authService.reissueTokens(refreshToken);

        // 쿠키 재설정
        addCookie(response, &quot;ACCESS_TOKEN&quot;, tokens.get(&quot;accessToken&quot;),
                (int) jwtTokenProvider.getAccessTokenValidityInSeconds());
        addCookie(response, &quot;REFRESH_TOKEN&quot;, tokens.get(&quot;refreshToken&quot;),
                (int) jwtTokenProvider.getRefreshTokenValidityInSeconds());

        // 응답 바디에는 간단히 ok 정도만 내려도 되고, 토큰을 넣어도 됩니다.
        return Map.of(
                &quot;status&quot;, &quot;ok&quot;
        );
    }

    // 3) 로그아웃 (RefreshToken DB 삭제 + 쿠키 삭제)
    @PostMapping(&quot;/logout&quot;)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void logout(@AuthenticationPrincipal UserDetails user,
                       HttpServletResponse response) {

        if (user != null) {
            authService.deleteRefreshToken(user.getUsername());
        }

        // 쿠키 삭제
        deleteCookie(response, &quot;ACCESS_TOKEN&quot;);
        deleteCookie(response, &quot;REFRESH_TOKEN&quot;);
    }

    // ====== 유틸 메서드 ======

    private String extractCookie(HttpServletRequest request, String name) {
        if (request.getCookies() == null) return null;
        for (Cookie cookie : request.getCookies()) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return null;
    }

    private void addCookie(HttpServletResponse response,
                           String name,
                           String value,
                           int maxAge) {

        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true);
        cookie.setPath(&quot;/&quot;);
        cookie.setMaxAge(maxAge);
        response.addCookie(cookie);
    }

    private void deleteCookie(HttpServletResponse response, String name) {
        Cookie cookie = new Cookie(name, &quot;&quot;);
        cookie.setPath(&quot;/&quot;);
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/api/auth/me &lt;br /&gt;-&amp;nbsp;현재&amp;nbsp;로그인&amp;nbsp;정보(이메일)&amp;nbsp;확인 &lt;br /&gt;&lt;br /&gt;/api/auth/refresh &lt;br /&gt;-&amp;nbsp;RefreshToken&amp;nbsp;쿠키를&amp;nbsp;읽어&amp;nbsp;새&amp;nbsp;Access/Refresh&amp;nbsp;토큰&amp;nbsp;발급 &lt;br /&gt;-&amp;nbsp;쿠키&amp;nbsp;갱신 &lt;br /&gt;&lt;br /&gt;/api/auth/logout &lt;br /&gt;-&amp;nbsp;RefreshToken&amp;nbsp;DB&amp;nbsp;삭제&amp;nbsp;+&amp;nbsp;쿠키&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;11) GuestbookController.java&amp;nbsp;(JWT&amp;nbsp;기반으로&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;받기)&lt;/p&gt;
&lt;pre id=&quot;code_1764583570090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.controller;

import com.tistory.angelplayer.oauth.guestbook.dto.GuestbookRequestDto;
import com.tistory.angelplayer.oauth.guestbook.entity.GuestbookEntry;
import com.tistory.angelplayer.oauth.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;

@RestController
@RequestMapping(&quot;/api/guestbook&quot;)
@RequiredArgsConstructor
public class GuestbookController {

    private final GuestbookService guestbookService;

    @GetMapping
    public List&amp;lt;GuestbookEntry&amp;gt; list() {
        return guestbookService.getAll();
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public GuestbookEntry create(@AuthenticationPrincipal UserDetails user,
                                 @RequestBody GuestbookRequestDto dto) {
        if (user == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, &quot;로그인이 필요합니다.&quot;);
        }

        if (dto.getMessage() == null || dto.getMessage().trim().isEmpty()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, &quot;메시지를 입력하세요.&quot;);
        }

        String email = user.getUsername();
        String name = &quot;익명&quot;; // JWT claim에서 name을 꺼내고 싶으면 추가 작업 가능

        return guestbookService.createEntry(email, name, dto);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@AuthenticationPrincipal&amp;nbsp;UserDetails&amp;nbsp;user&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;JWT에서&amp;nbsp;나온&amp;nbsp;사용자&amp;nbsp;이메일을&amp;nbsp;이용해&amp;nbsp;글&amp;nbsp;작성자&amp;nbsp;정보&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;12) GuestbookService.java&lt;/p&gt;
&lt;pre id=&quot;code_1764583660003&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.service;

import com.tistory.angelplayer.oauth.guestbook.dto.GuestbookRequestDto;
import com.tistory.angelplayer.oauth.guestbook.entity.GuestbookEntry;
import com.tistory.angelplayer.oauth.guestbook.repository.GuestbookEntryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class GuestbookService {

    private final GuestbookEntryRepository guestbookEntryRepository;

    /**
     * 방명록 전체 목록 조회
     */
    public List&amp;lt;GuestbookEntry&amp;gt; getAll() {
        // 단순 전체 조회. 필요하면 정렬 조건 추가 가능
        return guestbookEntryRepository.findAll();
    }

    /**
     * 방명록 글 등록
     * @param email 작성자 이메일 (JWT에서 추출)
     * @param name  작성자 이름(또는 닉네임)
     * @param dto   요청 DTO (message 등)
     */
    public GuestbookEntry createEntry(String email, String name, GuestbookRequestDto dto) {
        GuestbookEntry entry = new GuestbookEntry();
        entry.setUserEmail(email);
        entry.setUserName(name);
        entry.setMessage(dto.getMessage());
        entry.setCreatedAt(LocalDateTime.from(Instant.now()));

        return guestbookEntryRepository.save(entry);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.getAll() 추가&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;Front-end의 경우 기존 소스코드를 그대로 유지해도 새롭게 변경된 JWT 기능이 정상적으로 동작합니다.&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>Web/etc</category>
      <category>java</category>
      <category>javascript</category>
      <category>JWT</category>
      <category>Next</category>
      <category>react</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/600</guid>
      <comments>https://angelplayer.tistory.com/600#entry600comment</comments>
      <pubDate>Mon, 1 Dec 2025 19:55:31 +0900</pubDate>
    </item>
    <item>
      <title>[OAuth] 01 Google OAuth + SpringBoot + Next로 간편 로그인 구현</title>
      <link>https://angelplayer.tistory.com/599</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google OAuth를 활용하여 간편 로그인 기능 + 방명록을 구현해 보겠습니다.&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;&lt;b&gt;기능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Google OAuth&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Session 기반 인증 방식&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;&lt;b&gt;사용 프레임워크/라이브러리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Next.js 14&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SpringBoot 3&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;h2 data-ke-size=&quot;size26&quot;&gt;1. Google Cloud Console 설정&lt;/h2&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;a href=&quot;https://cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cloud.google.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1763299017469&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;클라우드 컴퓨팅 서비스 | Google Cloud&quot; data-og-description=&quot;데이터 관리, 하이브리드 및 멀티 클라우드, AI와 머신러닝 등 Google의 클라우드 컴퓨팅 서비스로 비즈니스 당면 과제를 해결하세요.&quot; data-og-host=&quot;cloud.google.com&quot; data-og-source-url=&quot;https://cloud.google.com/&quot; data-og-url=&quot;https://cloud.google.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cQngwa/hyZNDJZjYo/KjktQhUkM9uxNIYCqoiDNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cNQ4QM/hyZN2cwh9d/8ABeSlZJuaz17N9QQFw0G1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://cloud.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.google.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cQngwa/hyZNDJZjYo/KjktQhUkM9uxNIYCqoiDNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cNQ4QM/hyZN2cwh9d/8ABeSlZJuaz17N9QQFw0G1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;클라우드 컴퓨팅 서비스 | Google Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터 관리, 하이브리드 및 멀티 클라우드, AI와 머신러닝 등 Google의 클라우드 컴퓨팅 서비스로 비즈니스 당면 과제를 해결하세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWTtBR/dJMcabvNZl5/lxXY5HPqDcGJKKAX6Jiejk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWTtBR/dJMcabvNZl5/lxXY5HPqDcGJKKAX6Jiejk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWTtBR/dJMcabvNZl5/lxXY5HPqDcGJKKAX6Jiejk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWTtBR%2FdJMcabvNZl5%2FlxXY5HPqDcGJKKAX6Jiejk%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&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;Google Cloud Console로 이동하여 로그인 후 [프로젝트 선택 - 새 프로젝트]를 눌러줍니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBNG8P/dJMcafdT0Y6/rAhWju6Hs2hRmTWY5QIp1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBNG8P/dJMcafdT0Y6/rAhWju6Hs2hRmTWY5QIp1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBNG8P/dJMcafdT0Y6/rAhWju6Hs2hRmTWY5QIp1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBNG8P%2FdJMcafdT0Y6%2FrAhWju6Hs2hRmTWY5QIp1k%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWFTw/dJMcah3RnSq/9xTfozdylt7FsogkPM9YxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWFTw/dJMcah3RnSq/9xTfozdylt7FsogkPM9YxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWFTw/dJMcah3RnSq/9xTfozdylt7FsogkPM9YxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWFTw%2FdJMcah3RnSq%2F9xTfozdylt7FsogkPM9YxK%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 생성되었으면, 면 좌측의 메뉴에서 [API 및 서비스 - OAuth 동의 화면]을 클릭합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUtvsK/dJMcafLJXRR/hHfdz3FmETu8uo0t63hkTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUtvsK/dJMcafLJXRR/hHfdz3FmETu8uo0t63hkTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUtvsK/dJMcafLJXRR/hHfdz3FmETu8uo0t63hkTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUtvsK%2FdJMcafLJXRR%2FhHfdz3FmETu8uo0t63hkTk%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth 개요에서 [시작하기] 버튼을 눌러줍니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blsCHY/dJMcafZhcnw/hHgBFRBakj16ipwZ8yU1V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blsCHY/dJMcafZhcnw/hHgBFRBakj16ipwZ8yU1V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blsCHY/dJMcafZhcnw/hHgBFRBakj16ipwZ8yU1V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblsCHY%2FdJMcafZhcnw%2FhHgBFRBakj16ipwZ8yU1V1%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS3RTb/dJMcabo2lzB/6E6f03XXKwRTziXNteYEi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS3RTb/dJMcabo2lzB/6E6f03XXKwRTziXNteYEi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS3RTb/dJMcabo2lzB/6E6f03XXKwRTziXNteYEi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS3RTb%2FdJMcabo2lzB%2F6E6f03XXKwRTziXNteYEi0%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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;대상 탭에서는 외부를 선택해줍니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료 탭에서는 구글 OAuth 정책에 동의함을 눌러줍니다.&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;모든 탭의 내용의 작성이 완료되면 만들기를 눌러줍니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t4Lh9/dJMcafZhcoF/4hKqLzjoPtrQGZJcMQUhxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t4Lh9/dJMcafZhcoF/4hKqLzjoPtrQGZJcMQUhxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t4Lh9/dJMcafZhcoF/4hKqLzjoPtrQGZJcMQUhxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft4Lh9%2FdJMcafZhcoF%2F4hKqLzjoPtrQGZJcMQUhxk%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 OAuth 클라이언트를 만들어야 합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qFd2d/dJMcaawTGYp/HiEltgLldgrjkgX25TkSFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qFd2d/dJMcaawTGYp/HiEltgLldgrjkgX25TkSFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qFd2d/dJMcaawTGYp/HiEltgLldgrjkgX25TkSFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqFd2d%2FdJMcaawTGYp%2FHiEltgLldgrjkgX25TkSFk%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&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;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 원본의 URI의 경우, 저희는 Spring Boot 백엔드 방식을 사용할 것이므로 사용하지 않습니다. &lt;br /&gt;(Next.js 프론트로 직접 OAuth를 붙일 때만 필요함)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5vuQO/dJMb99Y3I23/YLbAUVVpPQzBDLyLwlQKlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5vuQO/dJMb99Y3I23/YLbAUVVpPQzBDLyLwlQKlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5vuQO/dJMb99Y3I23/YLbAUVVpPQzBDLyLwlQKlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5vuQO%2FdJMb99Y3I23%2FYLbAUVVpPQzBDLyLwlQKlk%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;1446&quot; height=&quot;783&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;783&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;size16&quot;&gt;승인된 리디렉션 URI는 백엔드 주소를 입력해주어야 합니다.&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 OAuth2 기본 값은 아래와 같은 형태입니다.&lt;br /&gt;http://&amp;lt;백엔드주소&amp;gt;/login/oauth2/code/{provider}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;783&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzTYxG/dJMb99Sh9Z2/zj16sgPPkJkWc0AkUW7mpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzTYxG/dJMb99Sh9Z2/zj16sgPPkJkWc0AkUW7mpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzTYxG/dJMb99Sh9Z2/zj16sgPPkJkWc0AkUW7mpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzTYxG%2FdJMb99Sh9Z2%2Fzj16sgPPkJkWc0AkUW7mpk%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;1464&quot; height=&quot;783&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;783&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 완성하였으면 Google Cloud Console에서 진행할 설정은 모두 완료된 것입니다.&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;이때 클라이언트 ID와 Password는 Spring boot에서 설정 시 필요하므로 별도로 저장해두시기 바랍니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;2. Spring 프로젝트 생성 및 설정&lt;/h2&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;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;a href=&quot;https://start.spring.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://start.spring.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/51ytb/dJMcafdUutL/GAwCMHlqFA53AhHbXWGoa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/51ytb/dJMcafdUutL/GAwCMHlqFA53AhHbXWGoa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/51ytb/dJMcafdUutL/GAwCMHlqFA53AhHbXWGoa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F51ytb%2FdJMcafdUutL%2FGAwCMHlqFA53AhHbXWGoa0%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;1464&quot; height=&quot;784&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start.spring.io에서 스프링 프로젝트 생성을 진행합니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGDhHB/dJMcaaDF6Sj/Oo7c9cnnBUaCKQEKwl6ZJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGDhHB/dJMcaaDF6Sj/Oo7c9cnnBUaCKQEKwl6ZJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGDhHB/dJMcaaDF6Sj/Oo7c9cnnBUaCKQEKwl6ZJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGDhHB%2FdJMcaaDF6Sj%2FOo7c9cnnBUaCKQEKwl6ZJ1%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;1367&quot; height=&quot;840&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1763464486823&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src/resources/application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: [GCC에서_제공받은_ID]
            client-secret: [GCC에서_제공받은 _PW]
            scope:
              - email
              - profile
        provider:
          google:
            issuer-uri: https://accounts.google.com

  datasource:
    url: jdbc:mysql://localhost:3306/내DB명?serverTimezone=Asia/Seoul&amp;amp;characterEncoding=UTF-8
    username: [USERNAME]
    password: [PASSWORD]
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: create   # 개발 중: create / update, 운영: validate 추천
    show-sql: true       # 콘솔에 SQL 출력
    properties:
      hibernate:
        format_sql: true
    database-platform: org.hibernate.dialect.MySQLDialect&lt;/code&gt;&lt;/pre&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;application.yml 파일을 생성하고 spring-security-oauth2에 설정을 작성합니다.&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;추가적으로 database도 연결을 진행해주시고, 저는 테스트를 위해 간단히 jpa까지 설정하였습니다.&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;pre id=&quot;code_1763465204554&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

	implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
	implementation 'com.fasterxml.jackson.core:jackson-databind'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}&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;다음으로 build.gradle에서 의존성을 추가해줍니다.&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;앞서 웹에서 프로젝트 생성 시 선택하지 않은 기능이 있다면 dependencies안에 추가하여 줍니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;3. Spring&amp;nbsp;Security&amp;nbsp;&amp;amp;&amp;nbsp;OAuth&amp;nbsp;로그인&amp;nbsp;기본&amp;nbsp;동작&amp;nbsp;만들기&lt;/h2&gt;
&lt;pre id=&quot;code_1763894195485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;com/tistory/angelplayer/oauth
    ├── OauthApplication.java                 # 스프링 부트 메인 클래스 (앱 실행 시작점)
    │
    ├── auth
    │    └── controller
    │         └── AuthController.java         # 로그인한 사용자 정보 반환 (/api/auth/me)
    │
    ├── config
    │    └── SecurityConfig.java              # OAuth2 + Spring Security 설정
    │                                          # (구글로그인, 세션 설정, CORS, 권한 관리 등)
    │
    └── guestbook
         ├── controller
         │    └── GuestbookController.java    # 방명록 API 진입점 (등록/조회 REST 컨트롤러)
         │
         ├── dto
         │    └── GuestbookRequestDto.java    # 방명록 등록 요청 DTO
         │
         ├── entity
         │    └── GuestbookEntry.java         # 방명록 엔티티 (DB 테이블 매핑)
         │
         ├── repository
         │    └── GuestbookEntryRepository.java  # 방명록 CRUD용 JPA Repository
         │
         └── service
              └── GuestbookService.java       # 방명록 비즈니스 로직 (저장/조회 처리)&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;백엔드&lt;span&gt;&amp;nbsp;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763473517943&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.auth.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@RequestMapping(&quot;/api/auth&quot;)
public class AuthController {

    @GetMapping(&quot;/me&quot;)
    public Map&amp;lt;String, Object&amp;gt; me(@AuthenticationPrincipal OAuth2User principal) {
        if (principal == null) {
            return Map.of(&quot;authenticated&quot;, false);
        }
        Map&amp;lt;String, Object&amp;gt; attrs = principal.getAttributes();
        return Map.of(
                &quot;authenticated&quot;, true,
                &quot;email&quot;, attrs.get(&quot;email&quot;),
                &quot;name&quot;, attrs.get(&quot;name&quot;),
                &quot;picture&quot;, attrs.get(&quot;picture&quot;)
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) AuthController.java&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;br /&gt;로그인 상태라면 OAuth2User 의 getAttributes() 를 통해 email, name, picture 등 구글 사용자 정보를 가져와서 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 안돼 있으면 { authenticated: false } 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프론트엔드(Next.js)가 초기 렌더링 시 &amp;ldquo;로그인 여부 확인&amp;rdquo; 용도로 호출하는 API&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;pre id=&quot;code_1763473638809&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -&amp;gt; csrf.disable())
                .cors(Customizer.withDefaults())
                .authorizeHttpRequests(auth -&amp;gt; auth
                        .requestMatchers(&quot;/&quot;, &quot;/error&quot;).permitAll()
                        .requestMatchers(&quot;/api/auth/**&quot;).permitAll()
                        .requestMatchers(&quot;/api/guestbook&quot;).permitAll()      // GET 허용
                        .requestMatchers(&quot;/api/guestbook/**&quot;).authenticated() // POST 등은 로그인 필요
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth -&amp;gt; oauth
                        .defaultSuccessUrl(&quot;http://localhost:3000&quot;, true)
                )
                .logout(logout -&amp;gt; logout
                        .logoutUrl(&quot;/logout&quot;)
                        .logoutSuccessUrl(&quot;http://localhost:3000&quot;)
                );

        return http.build();
    }

    // CORS 설정 (Next.js 3000 포트 허용)
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(List.of(&quot;http://localhost:3000&quot;));
        config.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;));
        config.setAllowedHeaders(List.of(&quot;*&quot;));
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, config);
        return source;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2)&amp;nbsp;SecurityConfig.java&amp;nbsp;(config) &lt;br /&gt;스프링&amp;nbsp;시큐리티&amp;nbsp;전체&amp;nbsp;설정을&amp;nbsp;담당. &lt;br /&gt;&lt;br /&gt;어떤&amp;nbsp;URL이&amp;nbsp;인증&amp;nbsp;없이&amp;nbsp;접근&amp;nbsp;가능한지&amp;nbsp;(permitAll),&amp;nbsp; &lt;br /&gt;어떤&amp;nbsp;URL이&amp;nbsp;로그인&amp;nbsp;필요한지&amp;nbsp;(authenticated)&amp;nbsp;정의. &lt;br /&gt;&lt;br /&gt;OAuth2&amp;nbsp;로그인&amp;nbsp;설정 &lt;br /&gt;&lt;br /&gt;/oauth2/authorization/google&amp;nbsp;&amp;rarr;&amp;nbsp;구글&amp;nbsp;로그인&amp;nbsp;페이지로&amp;nbsp;연결 &lt;br /&gt;&lt;br /&gt;로그인&amp;nbsp;성공&amp;nbsp;시&amp;nbsp;defaultSuccessUrl(&quot;http://localhost:3000&quot;)&amp;nbsp;로&amp;nbsp;리다이렉트 &lt;br /&gt;&lt;br /&gt;CORS&amp;nbsp;설정&amp;nbsp;:&amp;nbsp;Next.js(3000&amp;nbsp;포트)에서&amp;nbsp;오는&amp;nbsp;요청을&amp;nbsp;허용&amp;nbsp;(credentials&amp;nbsp;허용&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;pre id=&quot;code_1763894488276&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.entity;

import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;

@Entity
@Table(name = &quot;guestbook_entry&quot;)
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GuestbookEntry {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String userEmail;
    private String userName;

    @Column(length = 1000)
    private String message;

    private LocalDateTime createdAt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) GuestbookEntry.java (guestbook.entity) &lt;br /&gt;&lt;br /&gt;JPA&amp;nbsp;엔티티(=&amp;nbsp;DB&amp;nbsp;테이블과&amp;nbsp;매핑되는&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;pre id=&quot;code_1763894557601&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.repository;


import com.tistory.angelplayer.oauth.guestbook.entity.GuestbookEntry;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface GuestbookEntryRepository extends JpaRepository&amp;lt;GuestbookEntry, Long&amp;gt; {

    List&amp;lt;GuestbookEntry&amp;gt; findAllByOrderByCreatedAtDesc();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) GuestbookEntryRepository.java&amp;nbsp; &lt;br /&gt;save(),&amp;nbsp;findById(),&amp;nbsp;findAll()&amp;nbsp;등&amp;nbsp;기본&amp;nbsp;CRUD&amp;nbsp;메서드를&amp;nbsp;자동&amp;nbsp;제공. &lt;br /&gt;&lt;br /&gt;커스텀&amp;nbsp;메서드&amp;nbsp;findAllByOrderByCreatedAtDesc()&amp;nbsp;를&amp;nbsp;통해&amp;nbsp;방명록&amp;nbsp;목록을&amp;nbsp;createdAt&amp;nbsp;기준&amp;nbsp;내림차순으로&amp;nbsp;정렬해서&amp;nbsp;조회. &lt;br /&gt;&lt;br /&gt;실제로&amp;nbsp;SQL을&amp;nbsp;직접&amp;nbsp;작성하지&amp;nbsp;않아도&amp;nbsp;GuestbookEntryRepository&amp;nbsp;를&amp;nbsp;통해&amp;nbsp;DB에&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해주는&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;pre id=&quot;code_1763894676205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class GuestbookRequestDto {
    private String message;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) GuestbookRequestDto.java&amp;nbsp; &lt;br /&gt;클라이언트에서&amp;nbsp;방명록&amp;nbsp;글&amp;nbsp;작성&amp;nbsp;요청&amp;nbsp;시&amp;nbsp;받는&amp;nbsp;데이터&amp;nbsp;형태. &lt;br /&gt;&lt;br /&gt;Controller&amp;nbsp;에서&amp;nbsp;@RequestBody&amp;nbsp;로&amp;nbsp;받아서&amp;nbsp;검증/전달용&amp;nbsp;객체로&amp;nbsp;사용. &lt;br /&gt;&lt;br /&gt;엔티티(GuestbookEntry)와&amp;nbsp;분리해서,&amp;nbsp;&amp;ldquo;요청으로&amp;nbsp;들어오는&amp;nbsp;데이터&amp;rdquo;와&amp;nbsp;&amp;ldquo;DB&amp;nbsp;저장&amp;nbsp;구조&amp;rdquo;를&amp;nbsp;깔끔하게&amp;nbsp;나누는&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;pre id=&quot;code_1763898732968&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.service;

import com.tistory.angelplayer.oauth.guestbook.entity.GuestbookEntry;
import com.tistory.angelplayer.oauth.guestbook.dto.GuestbookRequestDto;
import com.tistory.angelplayer.oauth.guestbook.repository.GuestbookEntryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class GuestbookService {

    private final GuestbookEntryRepository repository;

    public List&amp;lt;GuestbookEntry&amp;gt; getAllEntries() {
        return repository.findAllByOrderByCreatedAtDesc();
    }

    public GuestbookEntry createEntry(String userEmail, String userName, GuestbookRequestDto dto) {
        GuestbookEntry entry = GuestbookEntry.builder()
                .userEmail(userEmail)
                .userName(userName)
                .message(dto.getMessage())
                .createdAt(LocalDateTime.now())
                .build();

        return repository.save(entry);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) GuestbookService.java (guestbook.service)&lt;br /&gt;비즈니스&amp;nbsp;로직&amp;nbsp;담당. &lt;br /&gt;&lt;br /&gt;getAllEntries()&amp;nbsp;:&amp;nbsp;GuestbookEntryRepository를&amp;nbsp;사용해&amp;nbsp;방명록&amp;nbsp;전체&amp;nbsp;목록&amp;nbsp;조회. &lt;br /&gt;&lt;br /&gt;createEntry(String&amp;nbsp;userEmail,&amp;nbsp;String&amp;nbsp;userName,&amp;nbsp;GuestbookRequestDto&amp;nbsp;dto)&amp;nbsp;:&amp;nbsp;DTO와&amp;nbsp;사용자&amp;nbsp;정보를&amp;nbsp;조합해&amp;nbsp;GuestbookEntry&amp;nbsp;생성 &lt;br /&gt;&lt;br /&gt;Repository로&amp;nbsp;저장&amp;nbsp;후&amp;nbsp;결과&amp;nbsp;반환 &lt;br /&gt;&lt;br /&gt;Controller는&amp;nbsp;&amp;ldquo;HTTP&amp;nbsp;요청/응답&amp;nbsp;처리&amp;rdquo;에&amp;nbsp;집중하고,&amp;nbsp;Service는&amp;nbsp;&amp;ldquo;로직/규칙/처리&amp;nbsp;순서&amp;rdquo;에&amp;nbsp;집중하게&amp;nbsp;해주는&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;pre id=&quot;code_1763899492095&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.tistory.angelplayer.oauth.guestbook.controller;


import com.tistory.angelplayer.oauth.guestbook.entity.GuestbookEntry;
import com.tistory.angelplayer.oauth.guestbook.dto.GuestbookRequestDto;
import com.tistory.angelplayer.oauth.guestbook.service.GuestbookService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping(&quot;/api/guestbook&quot;)
@RequiredArgsConstructor
public class GuestbookController {

    private final GuestbookService guestbookService;

    // 방명록 목록 조회 (누구나 볼 수 있음)
    @GetMapping
    public List&amp;lt;GuestbookEntry&amp;gt; list() {
        return guestbookService.getAllEntries();
    }

    // 방명록 글 작성 (로그인한 사용자만)
    @PostMapping
    public GuestbookEntry add(
            @RequestBody GuestbookRequestDto dto,
            @AuthenticationPrincipal OAuth2User principal
    ) {
        if (principal == null) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, &quot;로그인이 필요합니다.&quot;);
        }

        if (dto.getMessage() == null || dto.getMessage().trim().isEmpty()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, &quot;메시지를 입력하세요.&quot;);
        }

        Map&amp;lt;String, Object&amp;gt; attrs = principal.getAttributes();
        String email = (String) attrs.get(&quot;email&quot;);
        String name = (String) attrs.get(&quot;name&quot;);

        return guestbookService.createEntry(email, name, dto);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7)&amp;nbsp;GuestbookController.java &lt;br /&gt;&lt;br /&gt;GET&amp;nbsp;/api/guestbook &lt;br /&gt;누구나&amp;nbsp;방명록&amp;nbsp;목록을&amp;nbsp;조회할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;함 &lt;br /&gt;guestbookService.getAllEntries()&amp;nbsp;호출&amp;nbsp;후&amp;nbsp;결과&amp;nbsp;반환 &lt;br /&gt;&lt;br /&gt;POST&amp;nbsp;/api/guestbook &lt;br /&gt;@RequestBody&amp;nbsp;GuestbookRequestDto&amp;nbsp;로&amp;nbsp;메시지를&amp;nbsp;받고 &lt;br /&gt;@AuthenticationPrincipal&amp;nbsp;OAuth2User&amp;nbsp;로&amp;nbsp;로그인한&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;가져온&amp;nbsp;뒤 &lt;br /&gt;정상이라면&amp;nbsp;guestbookService.createEntry(...)&amp;nbsp;호출해&amp;nbsp;새&amp;nbsp;글&amp;nbsp;저장&amp;nbsp;후&amp;nbsp;반환 &lt;br /&gt;&lt;br /&gt;즉,&amp;nbsp;HTTP&amp;nbsp;&amp;rarr;&amp;nbsp;DTO/Principal&amp;nbsp;파싱&amp;nbsp;&amp;rarr;&amp;nbsp;Service&amp;nbsp;호출&amp;nbsp;&amp;rarr;&amp;nbsp;응답&amp;nbsp;을&amp;nbsp;담당하는&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;h2 data-ke-size=&quot;size26&quot;&gt;3. Next js 설정 및 소스코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 프로젝트 생성&lt;/p&gt;
&lt;pre id=&quot;code_1764566856119&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx create-next-app@latest fe

$ cd fe

$ npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.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;생성 이후 npm run dev 명령 실행 시 http://localhost:3000 페이지가 정상적으로 출력되면 프로젝트가 정상적으로 생성 된 것입니다.&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;2) 백엔드 주소 환경변수 설정&lt;/p&gt;
&lt;pre id=&quot;code_1764567020248&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// fe/.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 위치에 .env.local 파일을 생성하고 백엔드 주소와 포트를 작성해줍니다.&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;3) src/app/page.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1764571777971&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import { useEffect, useState, FormEvent } from &quot;react&quot;;

const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL!;

// 백엔드 /api/auth/me 응답 타입
type MeResponse = {
  authenticated: boolean;
  email?: string;
  name?: string;
  picture?: string;
};

// 백엔드 /api/guestbook 응답 타입
type GuestbookEntry = {
  id: number;
  userEmail: string;
  userName: string;
  message: string;
  createdAt: string;
};

export default function HomePage() {
  const [me, setMe] = useState&amp;lt;MeResponse | null&amp;gt;(null);
  const [entries, setEntries] = useState&amp;lt;GuestbookEntry[]&amp;gt;([]);
  const [message, setMessage] = useState(&quot;&quot;);
  const [loading, setLoading] = useState(false);

  // 1) 로그인 정보 가져오기
  const fetchMe = async () =&amp;gt; {
    try {
      const res = await fetch(`${API_BASE}/api/auth/me`, {
        credentials: &quot;include&quot;, // ✅ 세션 쿠키(JSESSIONID) 포함
      });
      const data: MeResponse = await res.json();
      setMe(data);
    } catch (e) {
      console.error(e);
      setMe({ authenticated: false });
    }
  };

  // 2) 방명록 목록 가져오기
  const fetchEntries = async () =&amp;gt; {
    try {
      const res = await fetch(`${API_BASE}/api/guestbook`, {
        credentials: &quot;include&quot;,
      });
      if (!res.ok) return;
      const data: GuestbookEntry[] = await res.json();
      setEntries(data);
    } catch (e) {
      console.error(e);
    }
  };

  // 첫 로딩 시 로그인 정보 확인
  useEffect(() =&amp;gt; {
    fetchMe();
  }, []);

  // 로그인 상태가 되면 방명록 목록 로딩
  useEffect(() =&amp;gt; {
    if (me?.authenticated) {
      fetchEntries();
    }
  }, [me]);

  // Google 로그인 버튼 클릭 시
  const handleLogin = () =&amp;gt; {
    window.location.href = `${API_BASE}/oauth2/authorization/google`;
  };

  // 로그아웃 (선택사항)
  const handleLogout = () =&amp;gt; {
    window.location.href = `${API_BASE}/logout`;
  };

  // 방명록 등록
  const handleSubmit = async (e: FormEvent) =&amp;gt; {
    e.preventDefault();
    if (!message.trim()) return;
    setLoading(true);
    try {
      const res = await fetch(`${API_BASE}/api/guestbook`, {
        method: &quot;POST&quot;,
        headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
        credentials: &quot;include&quot;,
        body: JSON.stringify({ message }),
      });

      if (!res.ok) {
        alert(&quot;등록에 실패했습니다. 로그인 상태를 확인해주세요.&quot;);
        return;
      }

      setMessage(&quot;&quot;);
      await fetchEntries();
    } catch (err) {
      console.error(err);
      alert(&quot;에러가 발생했습니다.&quot;);
    } finally {
      setLoading(false);
    }
  };

  // 아직 /api/auth/me 응답 전
  if (me === null) {
    return (
      &amp;lt;main style={{ padding: 24 }}&amp;gt;
        &amp;lt;h1&amp;gt;Google OAuth 방명록&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;로그인 상태 확인 중...&amp;lt;/p&amp;gt;
      &amp;lt;/main&amp;gt;
    );
  }

  // 로그인 안 된 경우
  if (!me.authenticated) {
    return (
      &amp;lt;main style={{ padding: 24 }}&amp;gt;
        &amp;lt;h1&amp;gt;Google OAuth 방명록&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;방명록을 남기려면 Google 계정으로 로그인 해주세요.&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={handleLogin}&amp;gt;Google로 로그인&amp;lt;/button&amp;gt;
      &amp;lt;/main&amp;gt;
    );
  }

  // 로그인 된 경우
  return (
    &amp;lt;main style={{ padding: 24, maxWidth: 600 }}&amp;gt;
      &amp;lt;h1&amp;gt;Google OAuth 방명록&amp;lt;/h1&amp;gt;

      &amp;lt;section style={{ marginBottom: 24 }}&amp;gt;
        &amp;lt;p&amp;gt;
          안녕하세요, &amp;lt;b&amp;gt;{me.name}&amp;lt;/b&amp;gt; ({me.email})
        &amp;lt;/p&amp;gt;
        &amp;lt;button onClick={handleLogout}&amp;gt;로그아웃&amp;lt;/button&amp;gt;
      &amp;lt;/section&amp;gt;

      &amp;lt;section style={{ marginBottom: 24 }}&amp;gt;
        &amp;lt;form onSubmit={handleSubmit}&amp;gt;
          &amp;lt;textarea
            style={{ width: &quot;100%&quot;, minHeight: 80 }}
            value={message}
            onChange={(e) =&amp;gt; setMessage(e.target.value)}
            placeholder=&quot;방명록을 남겨보세요&quot;
          /&amp;gt;
          &amp;lt;div style={{ marginTop: 8 }}&amp;gt;
            &amp;lt;button type=&quot;submit&quot; disabled={loading}&amp;gt;
              {loading ? &quot;등록 중...&quot; : &quot;등록&quot;}
            &amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/form&amp;gt;
      &amp;lt;/section&amp;gt;

      &amp;lt;section&amp;gt;
        &amp;lt;h2&amp;gt;방명록 목록&amp;lt;/h2&amp;gt;
        {entries.length === 0 ? (
          &amp;lt;p&amp;gt;아직 방명록이 없습니다.&amp;lt;/p&amp;gt;
        ) : (
          &amp;lt;ul style={{ listStyle: &quot;none&quot;, padding: 0 }}&amp;gt;
            {entries.map((e) =&amp;gt; (
              &amp;lt;li
                key={e.id}
                style={{
                  border: &quot;1px solid #ddd&quot;,
                  padding: 12,
                  marginBottom: 8,
                  borderRadius: 4,
                }}
              &amp;gt;
                &amp;lt;div&amp;gt;
                  &amp;lt;b&amp;gt;{e.userName}&amp;lt;/b&amp;gt; ({e.userEmail})
                &amp;lt;/div&amp;gt;
                &amp;lt;div style={{ margin: &quot;4px 0&quot; }}&amp;gt;{e.message}&amp;lt;/div&amp;gt;
                &amp;lt;div style={{ fontSize: 12, color: &quot;#666&quot; }}&amp;gt;
                  {new Date(e.createdAt).toLocaleString()}
                &amp;lt;/div&amp;gt;
              &amp;lt;/li&amp;gt;
            ))}
          &amp;lt;/ul&amp;gt;
        )}
      &amp;lt;/section&amp;gt;
    &amp;lt;/main&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;핵심 기능은 Goolge OAuth이므로 프론트 단은 간단히 구현하였습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;4. 요약 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 지금까지 구현한 전체 기능 요약&lt;/b&gt; &lt;br /&gt;Google OAuth 로그인 구현 &lt;br /&gt;- 프론트에서 &amp;ldquo;Google 로그인&amp;rdquo; 클릭 &lt;br /&gt;- Google&amp;nbsp;OAuth&amp;nbsp;인증&amp;nbsp;서버로&amp;nbsp;이동 &lt;br /&gt;- 인증&amp;nbsp;후&amp;nbsp;Google이&amp;nbsp;code를&amp;nbsp;우리&amp;nbsp;서버로&amp;nbsp;전달 &lt;br /&gt;- 우리&amp;nbsp;서버(Spring)가&amp;nbsp;Google&amp;nbsp;서버에서&amp;nbsp;사용자&amp;nbsp;프로필&amp;nbsp;정보&amp;nbsp;가져옴 &lt;br /&gt;- 사용자&amp;nbsp;정보를&amp;nbsp;세션에&amp;nbsp;저장 &lt;br /&gt;- 로그인&amp;nbsp;성공&amp;nbsp;후&amp;nbsp;프론트로&amp;nbsp;redirect &lt;br /&gt;- 프론트는&amp;nbsp;세션&amp;nbsp;기반으로&amp;nbsp;로그인&amp;nbsp;유지 &lt;br /&gt;&lt;br /&gt;세션(Session) 기반 사용자 인증 &lt;br /&gt;- 구글&amp;nbsp;로그인&amp;nbsp;성공&amp;nbsp;&amp;rarr;&amp;nbsp;서버가&amp;nbsp;세션&amp;nbsp;생성 &lt;br /&gt;- 세션&amp;nbsp;ID를&amp;nbsp;브라우저&amp;nbsp;쿠키(JSESSIONID)에&amp;nbsp;저장 &lt;br /&gt;- 이후&amp;nbsp;요청&amp;nbsp;시&amp;nbsp;쿠키에&amp;nbsp;담긴&amp;nbsp;세션&amp;nbsp;ID를&amp;nbsp;사용해&amp;nbsp;로그인&amp;nbsp;여부&amp;nbsp;확인 &lt;br /&gt;&lt;br /&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 코드의 동작 흐름&amp;nbsp;&lt;/b&gt;&lt;br /&gt;①&amp;nbsp;프론트&amp;nbsp;&amp;rarr;&amp;nbsp;Google&amp;nbsp;인증&amp;nbsp;요청 &lt;br /&gt;사용자가&amp;nbsp;Google&amp;nbsp;로그인&amp;nbsp;버튼&amp;nbsp;클릭 &lt;br /&gt;⬇ &lt;br /&gt;프론트에서&amp;nbsp;다음&amp;nbsp;주소로&amp;nbsp;이동: &lt;br /&gt;GET&amp;nbsp;http://localhost:8080/oauth2/authorization/google &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;②&amp;nbsp;Google&amp;nbsp;&amp;rarr;&amp;nbsp;서버로&amp;nbsp;authorization&amp;nbsp;code&amp;nbsp;전달 &lt;br /&gt;구글이&amp;nbsp;로그인&amp;nbsp;성공 &lt;br /&gt;⬇ &lt;br /&gt;Redirect: &lt;br /&gt;GET&amp;nbsp;http://localhost:8080/login/oauth2/code/google?code=xxxx &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;③&amp;nbsp;백엔드에서&amp;nbsp;Google&amp;nbsp;API&amp;nbsp;호출&amp;nbsp;&amp;amp;&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;획득 &lt;br /&gt;Spring&amp;nbsp;Security가&amp;nbsp;자동으로: &lt;br /&gt;code&amp;nbsp;&amp;rarr;&amp;nbsp;access_token&amp;nbsp;교환 &lt;br /&gt;access_token&amp;nbsp;&amp;rarr;&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;요청 &lt;br /&gt;&lt;br /&gt;얻은&amp;nbsp;정보: &lt;br /&gt;email &lt;br /&gt;name &lt;br /&gt;profile_image &lt;br /&gt;google_id &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;④&amp;nbsp;세션(Session)에&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;저장 &lt;br /&gt;Spring&amp;nbsp;Security의&amp;nbsp;기본&amp;nbsp;동작 &lt;br /&gt;&amp;rarr;&amp;nbsp;서버&amp;nbsp;세션에&amp;nbsp;Authentication&amp;nbsp;저장 &lt;br /&gt;⬇ &lt;br /&gt;JSESSIONID&amp;nbsp;쿠키&amp;nbsp;생성&amp;nbsp;후&amp;nbsp;프론트로&amp;nbsp;전달 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;⑤&amp;nbsp;프론트에서&amp;nbsp;방명록&amp;nbsp;작성&amp;nbsp;요청 &lt;br /&gt;프론트가&amp;nbsp;방명록&amp;nbsp;API&amp;nbsp;호출: &lt;br /&gt;&lt;br /&gt;POST&amp;nbsp;/api/guestbook &lt;br /&gt;content:&amp;nbsp;&quot;안녕하세요!&quot; &lt;br /&gt;&lt;br /&gt;브라우저는&amp;nbsp;자동으로&amp;nbsp;쿠키(JSESSIONID)를&amp;nbsp;함께&amp;nbsp;보냄 &lt;br /&gt;서버는&amp;nbsp;세션에서&amp;nbsp;사용자&amp;nbsp;정보&amp;nbsp;조회 &lt;br /&gt;로그인된&amp;nbsp;사용자로&amp;nbsp;판단 &lt;br /&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;&lt;b&gt;3. JSESSIONID 검증 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;①&amp;nbsp;동작&amp;nbsp;방식 &lt;br /&gt;0)&amp;nbsp;JSESSIONID&amp;nbsp;전달 &lt;br /&gt;로그인&amp;nbsp;성공&amp;nbsp;시&amp;nbsp;서버가&amp;nbsp;JSESSIONID를&amp;nbsp;내려줌 &lt;br /&gt;브라우저는&amp;nbsp;쿠키에&amp;nbsp;JSESSIONID를&amp;nbsp;저장하고&amp;nbsp;있음 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;1)&amp;nbsp;브라우저&amp;nbsp;Request &lt;br /&gt;이후&amp;nbsp;브라우저는&amp;nbsp;요청에&amp;nbsp;JSESSIONID&amp;nbsp;쿠키를&amp;nbsp;함께&amp;nbsp;실어&amp;nbsp;서버로&amp;nbsp;전달 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;2)&amp;nbsp;서버&amp;nbsp;검증 &lt;br /&gt;소스코드에서&amp;nbsp;직접&amp;nbsp;세션을&amp;nbsp;검증하는&amp;nbsp;단계는&amp;nbsp;없음 &lt;br /&gt;&lt;br /&gt;@AuthenticationPrincipal를&amp;nbsp;통해서 &lt;br /&gt;톰캣&amp;nbsp;+&amp;nbsp;스프링&amp;nbsp;시큐리티가&amp;nbsp;필터&amp;nbsp;레벨에서&amp;nbsp;자동으로&amp;nbsp;검증 &lt;br /&gt;&lt;br /&gt;-&amp;nbsp;요청이&amp;nbsp;들어오면,&amp;nbsp;톰캣이&amp;nbsp;쿠키에서&amp;nbsp;JSESSIONID&amp;nbsp;값을&amp;nbsp;읽음 &lt;br /&gt;-&amp;nbsp;이&amp;nbsp;ID로&amp;nbsp;톰캣&amp;nbsp;내부&amp;nbsp;세션&amp;nbsp;저장소(메모리)에서&amp;nbsp;HttpSession을&amp;nbsp;찾음 &lt;br /&gt;-&amp;nbsp;세션이&amp;nbsp;있고,&amp;nbsp;만료&amp;nbsp;시간이&amp;nbsp;지나지&amp;nbsp;않았다면&amp;nbsp;HttpServletRequest에&amp;nbsp;HttpSession을&amp;nbsp;붙여서&amp;nbsp;스프링에게&amp;nbsp;넘김 &lt;br /&gt;-&amp;nbsp;스프링&amp;nbsp;시큐리티는&amp;nbsp;그&amp;nbsp;세션에&amp;nbsp;저장된&amp;nbsp;SecurityContext(로그인&amp;nbsp;정보)를&amp;nbsp;꺼냄 &lt;br /&gt;-&amp;nbsp;그&amp;nbsp;안에&amp;nbsp;OAuth2User&amp;nbsp;가&amp;nbsp;있으면&amp;nbsp;&amp;ldquo;로그인&amp;nbsp;된&amp;nbsp;사용자&amp;rdquo;라고&amp;nbsp;판단 &lt;br /&gt;&lt;br /&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;br /&gt;② JSESSIONID 유효기간 / 만료 관리 &lt;br /&gt;&lt;br /&gt;1)&amp;nbsp;서버&amp;nbsp;만료 &lt;br /&gt;server.servlet.session.timeout을&amp;nbsp;통해서 &lt;br /&gt;만료시간을&amp;nbsp;설정할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;없으면&amp;nbsp;톰캣&amp;nbsp;기본값(보통&amp;nbsp;30분&amp;nbsp;비활성&amp;nbsp;시&amp;nbsp;만료)&amp;nbsp;적용 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;2)&amp;nbsp;클라이언트&amp;nbsp;만료&amp;nbsp;(브라우저&amp;nbsp;쿠키&amp;nbsp;라이프사이클) &lt;br /&gt;Max-Age나&amp;nbsp;Expires를&amp;nbsp;사용해&amp;nbsp;관리 &lt;br /&gt;따로&amp;nbsp;지정하지&amp;nbsp;않으면&amp;nbsp;세션&amp;nbsp;쿠키로&amp;nbsp;취급&amp;nbsp;(브라우저를&amp;nbsp;닫을&amp;nbsp;떄&amp;nbsp;쿠키&amp;nbsp;삭제) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;3)&amp;nbsp;로그아웃&amp;nbsp;요청 &lt;br /&gt;Spring&amp;nbsp;Security가&amp;nbsp;HttpSession.invalidate()&amp;nbsp;호출&amp;nbsp; &lt;br /&gt;&amp;rarr;&amp;nbsp;서버&amp;nbsp;쪽&amp;nbsp;세션&amp;nbsp;삭제 &lt;br /&gt;&amp;rarr;&amp;nbsp;JSESSIONID&amp;nbsp;쿠키도&amp;nbsp;만료시키는&amp;nbsp;응답&amp;nbsp;헤더를&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>Web/etc</category>
      <category>auth</category>
      <category>Google</category>
      <category>java</category>
      <category>javascript</category>
      <category>Next</category>
      <category>OAuth</category>
      <category>react</category>
      <category>Spring</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/599</guid>
      <comments>https://angelplayer.tistory.com/599#entry599comment</comments>
      <pubDate>Mon, 1 Dec 2025 15:59:36 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 20250907 개발일지 (웹 동적 크기 변환, clamp())</title>
      <link>https://angelplayer.tistory.com/598</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;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;clamp()&lt;/b&gt; &lt;br /&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/clamp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/clamp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹&amp;nbsp;화면의&amp;nbsp;모든&amp;nbsp;요소가&amp;nbsp;동적으로&amp;nbsp;크기가&amp;nbsp;바뀌도록&amp;nbsp;하고&amp;nbsp;싶었는데&amp;nbsp;clamp를&amp;nbsp;사용하면&amp;nbsp;편리하게&amp;nbsp;구현&amp;nbsp;가능할&amp;nbsp;것&amp;nbsp;같다. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;##&amp;nbsp;사용법 &lt;br /&gt;clamp(최솟값,&amp;nbsp;선호값,&amp;nbsp;최댓값) &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;##&amp;nbsp;예시 &lt;br /&gt;width:&amp;nbsp;clamp(0%,&amp;nbsp;300px,&amp;nbsp;100%); &lt;br /&gt;&lt;br /&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;aspect-ratio&lt;/b&gt; &lt;br /&gt;종횡비 &lt;br /&gt;요소를&amp;nbsp;비율대로&amp;nbsp;조정 &lt;br /&gt;&lt;br /&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;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>개인 공부/TIL</category>
      <category>CSS</category>
      <category>HTML</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/598</guid>
      <comments>https://angelplayer.tistory.com/598#entry598comment</comments>
      <pubDate>Sun, 7 Sep 2025 22:09:56 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] EC2 램 메모리 증설(SWAP)하기 (Feat. 프리티어)</title>
      <link>https://angelplayer.tistory.com/597</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpS5q/btsM2ZyFEPE/GgGzqoPmKD0aKbXkiclQp1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpS5q/btsM2ZyFEPE/GgGzqoPmKD0aKbXkiclQp1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpS5q/btsM2ZyFEPE/GgGzqoPmKD0aKbXkiclQp1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxpS5q%2FbtsM2ZyFEPE%2FGgGzqoPmKD0aKbXkiclQp1%2Fimg.jpg&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;500&quot; height=&quot;500&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&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;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;작은 용량의 RAM을 사용하면서 여러 기능을 사용하다보면 메모리 용량이 부족한 경우가 종종 발생합니다.&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;용량을&amp;nbsp;늘리기&amp;nbsp;위해서는&amp;nbsp;리눅스&amp;nbsp;커널에서&amp;nbsp;제공하는&amp;nbsp;스왑(SWAP)을&amp;nbsp;사용하면&amp;nbsp;됩니다. &lt;br /&gt;&lt;br /&gt;스왑은&amp;nbsp;RAM이&amp;nbsp;부족할&amp;nbsp;때,&amp;nbsp;디스크의&amp;nbsp;일부를&amp;nbsp;임시&amp;nbsp;메모리처럼&amp;nbsp;사용하는&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;h2 data-ke-size=&quot;size26&quot;&gt;스왑 공간 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0. 스왑 공간 확인하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xOR6A/btsM1ghBdTB/sgDxk4zVvLuLhbJQm4Wfy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xOR6A/btsM1ghBdTB/sgDxk4zVvLuLhbJQm4Wfy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xOR6A/btsM1ghBdTB/sgDxk4zVvLuLhbJQm4Wfy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxOR6A%2FbtsM1ghBdTB%2FsgDxk4zVvLuLhbJQm4Wfy1%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743226829605&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ free -h&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스왑을&amp;nbsp;위해서는&amp;nbsp;스왑&amp;nbsp;공간을&amp;nbsp;할당해야&amp;nbsp;합니다. &lt;br /&gt;&lt;br /&gt;현재&amp;nbsp;사용&amp;nbsp;중인&amp;nbsp;환경에&amp;nbsp;스왑&amp;nbsp;공간이&amp;nbsp;존재하는지&amp;nbsp;여부는&amp;nbsp;free&amp;nbsp;-h&amp;nbsp;명령을&amp;nbsp;통해서&amp;nbsp;확인할&amp;nbsp;수&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;&lt;b&gt;1. 스왑 파일 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZoXIn/btsM0vsw3Fr/noVqrvpSUXCc1khqFO6HJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZoXIn/btsM0vsw3Fr/noVqrvpSUXCc1khqFO6HJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZoXIn/btsM0vsw3Fr/noVqrvpSUXCc1khqFO6HJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZoXIn%2FbtsM0vsw3Fr%2FnoVqrvpSUXCc1khqFO6HJk%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743226901098&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 dd 명령을 통해 스왑 파일을 생성해야 합니다. &lt;br /&gt;&lt;br /&gt;bs는&amp;nbsp;블록&amp;nbsp;크기이고,&amp;nbsp;count는&amp;nbsp;블록&amp;nbsp;수&amp;nbsp;입니다. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;일반적으로&amp;nbsp;스왑&amp;nbsp;공간은&amp;nbsp;현재&amp;nbsp;메모리의&amp;nbsp;2배를&amp;nbsp;권장하는&amp;nbsp;편이며,&amp;nbsp;저는&amp;nbsp;1GB&amp;nbsp;RAM을&amp;nbsp;사용&amp;nbsp;중이기&amp;nbsp;때문에,&amp;nbsp;2GB의&amp;nbsp;스왑&amp;nbsp;공간을&amp;nbsp;할당할&amp;nbsp;것입니다. &lt;br /&gt;&lt;br /&gt;스왑&amp;nbsp;공간은&amp;nbsp;bs&amp;nbsp;*&amp;nbsp;count로&amp;nbsp;계산할&amp;nbsp;수&amp;nbsp;있으며,&amp;nbsp;보통&amp;nbsp;bs를&amp;nbsp;128MB로&amp;nbsp;고정하는&amp;nbsp;편이므로&amp;nbsp;count는&amp;nbsp;16이&amp;nbsp;도출되었습니다.&amp;nbsp;(128&amp;nbsp;*&amp;nbsp;16&amp;nbsp;=&amp;nbsp;2048) &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;&lt;b&gt;2. 스왑 파일의 읽기/쓰기 권한 부여&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qGlRP/btsM1KI0KSn/PU0v03JekHdknK3ZGlDlTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qGlRP/btsM1KI0KSn/PU0v03JekHdknK3ZGlDlTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qGlRP/btsM1KI0KSn/PU0v03JekHdknK3ZGlDlTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqGlRP%2FbtsM1KI0KSn%2FPU0v03JekHdknK3ZGlDlTK%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743227043777&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo chmod 600 /swapfile

$ ls -l&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;chmod 명령을 통해 스왑파일의 읽기 및 쓰기 권한을 업데이트합니다.&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;권한이 업데이트 된 것인 ls -l을 통해서 확인하실 수 있습니다.&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;&lt;b&gt;3. 스왑 영역 설정&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvnyCg/btsM2xh6Z2K/7HysiwGKSExGn2FoIjMI60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvnyCg/btsM2xh6Z2K/7HysiwGKSExGn2FoIjMI60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvnyCg/btsM2xh6Z2K/7HysiwGKSExGn2FoIjMI60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvnyCg%2FbtsM2xh6Z2K%2F7HysiwGKSExGn2FoIjMI60%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743227209992&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo mkswap /swapfile

$ sudo swapon /swapfile

$ sudo swapon -s&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mkswap, swapon을 통해 스왑 영역을 설정합니다.&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;swapon -s을 통해서 스왑 영역이 적용된 것을 확인할 수 있습니다.&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;&lt;b&gt;4. 재부팅시 자동으로 스왑 설정 유지하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zs0Ik/btsM1bNRQQM/GaLkEdIAbaTJ0ZHIw065l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zs0Ik/btsM1bNRQQM/GaLkEdIAbaTJ0ZHIw065l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zs0Ik/btsM1bNRQQM/GaLkEdIAbaTJ0ZHIw065l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzs0Ik%2FbtsM1bNRQQM%2FGaLkEdIAbaTJ0ZHIw065l0%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743227436209&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ sudo vi /etc/fstab

# 아래 라인을 작성
/swapfile swap swap defaults 0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번에 swapon /swapfile으로 스왑을 활성화했지만, 서버를 재부팅하면 사라지게 됩니다.&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;&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;/etc/fstab에는 다른 설정 라인이 있기 때문에 가장 마지막 줄에 /swapfile 을 작성해주시면 됩니다.&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;리눅스는 일반적으로 vi를 통해서 파일을 수정하는데, nano 등 다른 에디터를 사용하셔도 무방합니다.&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;저는 vi를 사용할 것이고, 아래에 간단히 순서대로 작성하는 방법을 작성해 놓겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(#의 뒤는 설명이므로 무시하셔도 됩니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1743227800192&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0) sudo vi /etc/fstab
1) Shift + G # 마지막 줄로 이동
2) o # 수정 모드로 변경
3) /swapfile swap swap defaults 0 0 # 작성
4) ESC
5) :wq # 저장 후 나가기&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;&lt;b&gt;5. 확인&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4d5Mm/btsM2LOcxQb/sKGOnENSu0wsBjKxgC54uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4d5Mm/btsM2LOcxQb/sKGOnENSu0wsBjKxgC54uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4d5Mm/btsM2LOcxQb/sKGOnENSu0wsBjKxgC54uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4d5Mm%2FbtsM2LOcxQb%2FsKGOnENSu0wsBjKxgC54uK%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;free -h로 다시 확인해보시면 Swap 영역이 2GB로 잘 적용된 모습을 확인하실 수 있습니다.&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>Programming/Infra</category>
      <category>aws</category>
      <category>EC2</category>
      <category>memory</category>
      <category>RAM</category>
      <category>vi</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/597</guid>
      <comments>https://angelplayer.tistory.com/597#entry597comment</comments>
      <pubDate>Mon, 31 Mar 2025 08:59:53 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] EC2 고정 IP(탄력적 IP) 설정하기</title>
      <link>https://angelplayer.tistory.com/596</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IgEKM/btsMSQRlRgM/YYWjLG9YIe09K9kOY7Ryvk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IgEKM/btsMSQRlRgM/YYWjLG9YIe09K9kOY7Ryvk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IgEKM/btsMSQRlRgM/YYWjLG9YIe09K9kOY7Ryvk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIgEKM%2FbtsMSQRlRgM%2FYYWjLG9YIe09K9kOY7Ryvk%2Fimg.jpg&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;500&quot; height=&quot;500&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&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;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;&lt;a href=&quot;https://angelplayer.tistory.com/595&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://angelplayer.tistory.com/595&lt;/a&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;p data-ke-size=&quot;size16&quot;&gt;지난&amp;nbsp;시간에는&amp;nbsp;AWS&amp;nbsp;EC2를&amp;nbsp;이용하여&amp;nbsp;클라우드&amp;nbsp;서버를&amp;nbsp;구축하였습니다. &lt;br /&gt;&lt;br /&gt;지금&amp;nbsp;서버에&amp;nbsp;접속하거나&amp;nbsp;서비스를&amp;nbsp;구축&amp;nbsp;후&amp;nbsp;배포해도&amp;nbsp;사용하는&amp;nbsp;데&amp;nbsp;문제가&amp;nbsp;없지만,&amp;nbsp;동적&amp;nbsp;IP&amp;nbsp;상태이므로&amp;nbsp;서버를&amp;nbsp;재기동하면&amp;nbsp;IP가&amp;nbsp;변경될&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&gt;따라서&amp;nbsp;고정&amp;nbsp;IP를&amp;nbsp;사용하도록&amp;nbsp;만들어야&amp;nbsp;하는데,&amp;nbsp;AWS에서는&amp;nbsp;탄력적&amp;nbsp;IP(Elastic&amp;nbsp;IP)를&amp;nbsp;통해&amp;nbsp;고정적인&amp;nbsp;IP를&amp;nbsp;사용할&amp;nbsp;수&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTZp4Z/btsMS49Am83/8XED7xkK8OgcmwuWkWEj2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTZp4Z/btsMS49Am83/8XED7xkK8OgcmwuWkWEj2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTZp4Z/btsMS49Am83/8XED7xkK8OgcmwuWkWEj2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTZp4Z%2FbtsMS49Am83%2F8XED7xkK8OgcmwuWkWEj2k%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2&amp;nbsp;대시보드로&amp;nbsp;접근하여,&amp;nbsp;'&lt;b&gt;네트워크&amp;nbsp;및&amp;nbsp;보안&amp;nbsp;-&amp;nbsp;탄력적&amp;nbsp;IP&lt;/b&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;'탄력적 IP 주소 할당'을&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YMnGv/btsMSwMmI9f/SR9N504vaiYfjXRk29mSPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YMnGv/btsMSwMmI9f/SR9N504vaiYfjXRk29mSPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YMnGv/btsMSwMmI9f/SR9N504vaiYfjXRk29mSPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYMnGv%2FbtsMSwMmI9f%2FSR9N504vaiYfjXRk29mSPK%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄력적 IP 주소의 경우 네트워크 경계 그룹을 EC2 리전과 맞춰주기만 하면 되는데, 기본적으로 계정에 설정된 리전을 따라서 적용됩니다.&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5k3eo/btsMUmgUOT2/817eMaqWCOiGf1CCJGXGMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5k3eo/btsMUmgUOT2/817eMaqWCOiGf1CCJGXGMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5k3eo/btsMUmgUOT2/817eMaqWCOiGf1CCJGXGMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5k3eo%2FbtsMUmgUOT2%2F817eMaqWCOiGf1CCJGXGMk%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄력적 IP가 잘 생성된 것을 볼 수 있습니다.&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;저는 헷갈리지 않도록 우선 Name을 수정했습니다.&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;Name 란에 마우스를 가져가면 수정 버튼이 생기며 클릭 시 Name 수정이 가능합니다.&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;다음으로 우측 상단의 &lt;b&gt;'작업 - 탄력적 IP 주소 연결'&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNqXA3/btsMSv0Y1Ip/MqW4BteuGX5XBUtYMJyVCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNqXA3/btsMSv0Y1Ip/MqW4BteuGX5XBUtYMJyVCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNqXA3/btsMSv0Y1Ip/MqW4BteuGX5XBUtYMJyVCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNqXA3%2FbtsMSv0Y1Ip%2FMqW4BteuGX5XBUtYMJyVCK%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탄력적 IP 주소에서 &lt;b&gt;리소스 유형&lt;/b&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;인스턴스 선택 후 &lt;b&gt;프라이빗 IP&lt;/b&gt;도 설정해야 하는데, 인스턴스를 정상적으로 선택했다면 해당 인스턴스의 IP 정보를 자동으로 받아올 수 있습니다.&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;인스턴스, 프라이빗 IP를 정상적으로 선택했다면 &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgp1Kl/btsMTFn0nsz/VbHV2imchXgyGPL8ytzgB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgp1Kl/btsMTFn0nsz/VbHV2imchXgyGPL8ytzgB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgp1Kl/btsMTFn0nsz/VbHV2imchXgyGPL8ytzgB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgp1Kl%2FbtsMTFn0nsz%2FVbHV2imchXgyGPL8ytzgB1%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로&amp;nbsp;적용되었다면&amp;nbsp;인스턴스&amp;nbsp;탭에서&amp;nbsp;연결한&amp;nbsp;인스턴스를&amp;nbsp;선택했을&amp;nbsp;때&amp;nbsp;탄력적&amp;nbsp;IP&amp;nbsp;주소로&amp;nbsp;방금&amp;nbsp;만든&amp;nbsp;angelplayer-eip가&amp;nbsp;잘&amp;nbsp;적용된&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btA7it/btsMSUFZ1OX/lvZtEvVfb4nt1dZdIAfuK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btA7it/btsMSUFZ1OX/lvZtEvVfb4nt1dZdIAfuK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btA7it/btsMSUFZ1OX/lvZtEvVfb4nt1dZdIAfuK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtA7it%2FbtsMSUFZ1OX%2FlvZtEvVfb4nt1dZdIAfuK1%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;452&quot; height=&quot;442&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 PuTTY에서 로그인을 진행해 봅시다.&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;Host Name&lt;/b&gt;에 &lt;b&gt;탄력적 IP의 주소&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;탄력적 IP 주소는 앞에 인스턴스 탭에서 인스턴스 선택 시 나오는 탄력적 주소란에 적혀 있습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbJirx/btsMTBePtHL/jD4x6kSZppkZkcn1b4kdaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbJirx/btsMTBePtHL/jD4x6kSZppkZkcn1b4kdaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbJirx/btsMTBePtHL/jD4x6kSZppkZkcn1b4kdaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbJirx%2FbtsMTBePtHL%2FjD4x6kSZppkZkcn1b4kdaK%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;661&quot; height=&quot;418&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;login as에 ubuntu를 작성하고 엔터를 눌러주시면 로그인 됩니다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;※ 탄력적 IP는 EC2 인스턴스에 연결되어 있을 경우 요금이 부과되지 않지만, 연결되지 않은 상태로 할당만 되어 있을 경우에는 비용이 발생할 수 있습니다.&lt;/blockquote&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>Programming/Infra</category>
      <category>aws</category>
      <category>EC2</category>
      <category>EIP</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/596</guid>
      <comments>https://angelplayer.tistory.com/596#entry596comment</comments>
      <pubDate>Tue, 25 Mar 2025 08:19:03 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] EC2 프리티어 환경 구축하기 (Feat. Ubuntu, PuTTY)</title>
      <link>https://angelplayer.tistory.com/595</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJqICW/btsMT3hBnZo/zpKx3LbKL2uDNROCL4qSPk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJqICW/btsMT3hBnZo/zpKx3LbKL2uDNROCL4qSPk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJqICW/btsMT3hBnZo/zpKx3LbKL2uDNROCL4qSPk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJqICW%2FbtsMT3hBnZo%2FzpKx3LbKL2uDNROCL4qSPk%2Fimg.jpg&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;500&quot; height=&quot;500&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;900&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;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;AWS에서 제공하는 기능들을 이용하여 클라우드 컴퓨팅 환경을 만들어 보겠습니다.&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;AWS는 EC2, Lightsail 등을 이용하여 클라우드 컴퓨팅 환경을 구축하며, AWS는 회원가입 후 EC2를 1년간 무료로 사용할 수 있는 프리티어 오퍼를 제공합니다.&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;&lt;b&gt;AWS&amp;nbsp;(Amazon&amp;nbsp;Web&amp;nbsp;Services)란?&lt;/b&gt; &lt;br /&gt;AWS(Amazon&amp;nbsp;Web&amp;nbsp;Services)는&amp;nbsp;아마존(Amazon)이&amp;nbsp;제공하는&amp;nbsp;클라우드&amp;nbsp;컴퓨팅&amp;nbsp;플랫폼으로,&amp;nbsp;인터넷을&amp;nbsp;통해&amp;nbsp;서버,&amp;nbsp;스토리지,&amp;nbsp;데이터베이스,&amp;nbsp;네트워크,&amp;nbsp;AI&amp;nbsp;등의&amp;nbsp;IT&amp;nbsp;인프라를&amp;nbsp;제공하는&amp;nbsp;서비스입니다. &lt;br /&gt;즉,&amp;nbsp;물리적인&amp;nbsp;서버&amp;nbsp;없이도&amp;nbsp;AWS를&amp;nbsp;통해&amp;nbsp;원하는&amp;nbsp;만큼&amp;nbsp;컴퓨팅&amp;nbsp;리소스를&amp;nbsp;빌려&amp;nbsp;사용할&amp;nbsp;수&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;&lt;b&gt;EC2&amp;nbsp;(Elastic&amp;nbsp;Compute&amp;nbsp;Cloud)란?&lt;/b&gt; &lt;br /&gt;EC2는&amp;nbsp;AWS에서&amp;nbsp;제공하는&amp;nbsp;가상&amp;nbsp;서버&amp;nbsp;서비스입니다. &lt;br /&gt;일반적으로&amp;nbsp;우리가&amp;nbsp;알고&amp;nbsp;있는&amp;nbsp;물리적&amp;nbsp;서버를&amp;nbsp;클라우드&amp;nbsp;환경에서&amp;nbsp;가상화된&amp;nbsp;형태로&amp;nbsp;제공하는&amp;nbsp;서비스라고&amp;nbsp;보면&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;figure id=&quot;og_1742300619988&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;무료 클라우드 컴퓨팅 서비스 - AWS 프리 티어&quot; data-og-description=&quot;이러한 프리 티어 혜택은 AWS 신규 고객에게만 제공되며 AWS 가입일로부터 12개월 동안 유효합니다. 12개월의 무료 사용 기간이 만료되거나 애플리케이션 사용량이 프리 티어 범위를 초과할 경우&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/free/?gclid=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB&amp;amp;trk=fa2d6ba3-df80-4d24-a453-bf30ad163af9&amp;amp;sc_channel=ps&amp;amp;ef_id=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB:G:s&amp;amp;s_kwcid=AL!4422!3!563761819834!e!!g!!aws!15286221779!129400439466&amp;amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all&quot; data-og-url=&quot;https://aws.amazon.com/ko/free/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sKMQ5/hyYqMpsWou/klOj9RGZKzALVAzk4Wwlz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eyQdx/hyYqREk0tP/C6iuWkmxVmzjkf2A9GAlS0/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/free/?gclid=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB&amp;amp;trk=fa2d6ba3-df80-4d24-a453-bf30ad163af9&amp;amp;sc_channel=ps&amp;amp;ef_id=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB:G:s&amp;amp;s_kwcid=AL!4422!3!563761819834!e!!g!!aws!15286221779!129400439466&amp;amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/free/?gclid=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB&amp;amp;trk=fa2d6ba3-df80-4d24-a453-bf30ad163af9&amp;amp;sc_channel=ps&amp;amp;ef_id=Cj0KCQjws-S-BhD2ARIsALssG0YrFVesSOB4tOkCVY0nPPbZwLO3fd_qWRYx_COqWcTZuTk14aBPpAoaArrDEALw_wcB:G:s&amp;amp;s_kwcid=AL!4422!3!563761819834!e!!g!!aws!15286221779!129400439466&amp;amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;amp;all-free-tier.sort-order=asc&amp;amp;awsf.Free%20Tier%20Types=*all&amp;amp;awsf.Free%20Tier%20Categories=*all&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sKMQ5/hyYqMpsWou/klOj9RGZKzALVAzk4Wwlz0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eyQdx/hyYqREk0tP/C6iuWkmxVmzjkf2A9GAlS0/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;무료 클라우드 컴퓨팅 서비스 - AWS 프리 티어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이러한 프리 티어 혜택은 AWS 신규 고객에게만 제공되며 AWS 가입일로부터 12개월 동안 유효합니다. 12개월의 무료 사용 기간이 만료되거나 애플리케이션 사용량이 프리 티어 범위를 초과할 경우&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlnvOg/btsMPnttOWE/qk8tb2aM7Nsx6Gg5pba6k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlnvOg/btsMPnttOWE/qk8tb2aM7Nsx6Gg5pba6k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlnvOg/btsMPnttOWE/qk8tb2aM7Nsx6Gg5pba6k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlnvOg%2FbtsMPnttOWE%2Fqk8tb2aM7Nsx6Gg5pba6k1%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;1399&quot; height=&quot;881&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 AWS에 접속하여 회원가입을 진행해줍니다.&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;1. EC2 인스턴스 생성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1-1. 리전 설정&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yWz3L/btsMOghNhYv/Ti4EHrFB3GHs6NRbVgg0v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yWz3L/btsMOghNhYv/Ti4EHrFB3GHs6NRbVgg0v1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yWz3L/btsMOghNhYv/Ti4EHrFB3GHs6NRbVgg0v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyWz3L%2FbtsMOghNhYv%2FTi4EHrFB3GHs6NRbVgg0v1%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;620&quot; height=&quot;343&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 AWS의 리전(Region)을 변경해줍니다.&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;&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;리전은 AWS 화면에서 우측 상단 설정 옆에서 변경할 수 있습니다.&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;&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;size18&quot;&gt;&lt;b&gt;1-2. EC2 인스턴스 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pAj1u/btsMOoz0s9n/LVtwwLwtq665kSk80fx510/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pAj1u/btsMOoz0s9n/LVtwwLwtq665kSk80fx510/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pAj1u/btsMOoz0s9n/LVtwwLwtq665kSk80fx510/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpAj1u%2FbtsMOoz0s9n%2FLVtwwLwtq665kSk80fx510%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;945&quot; height=&quot;570&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인화면 좌측 상단 검색창에 EC2를 작성해줍니다.&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;서비스 - EC2&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czOa3H/btsMNJLu4Du/AiMuPxTNCAilfkpU27uIi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czOa3H/btsMNJLu4Du/AiMuPxTNCAilfkpU27uIi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czOa3H/btsMNJLu4Du/AiMuPxTNCAilfkpU27uIi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczOa3H%2FbtsMNJLu4Du%2FAiMuPxTNCAilfkpU27uIi0%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;1399&quot; height=&quot;881&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2의 대시보드 화면에서 &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCKNaN/btsMUyarDfk/ch2WdmX3FCnmmMS90QJahk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCKNaN/btsMUyarDfk/ch2WdmX3FCnmmMS90QJahk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCKNaN/btsMUyarDfk/ch2WdmX3FCnmmMS90QJahk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCKNaN%2FbtsMUyarDfk%2Fch2WdmX3FCnmmMS90QJahk%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuGqCw/btsMUn7W8zM/jI10wBpR7kkLE39OJEUsI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuGqCw/btsMUn7W8zM/jI10wBpR7kkLE39OJEUsI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuGqCw/btsMUn7W8zM/jI10wBpR7kkLE39OJEUsI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuGqCw%2FbtsMUn7W8zM%2FjI10wBpR7kkLE39OJEUsI1%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어플리케이션 및 OS 이미지(AMI)&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;우분투 24.04&lt;/b&gt; 버전을 선택하였으며, 인스턴스 생성 시점에&lt;b&gt; LTS(Long&amp;nbsp;Term&amp;nbsp;Support)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r5PsV/btsMTIZbI4L/2xDurkfnQ1KkKwZdAVVu90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r5PsV/btsMTIZbI4L/2xDurkfnQ1KkKwZdAVVu90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r5PsV/btsMTIZbI4L/2xDurkfnQ1KkKwZdAVVu90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr5PsV%2FbtsMTIZbI4L%2F2xDurkfnQ1KkKwZdAVVu90%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인스턴스 유형&lt;/b&gt;은 프리티어를 지원하는 &lt;b&gt;t2.micro&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKbDuE/btsMT5TTZ34/CH2o4JRMTtTXSJ3sduQty1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKbDuE/btsMT5TTZ34/CH2o4JRMTtTXSJ3sduQty1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKbDuE/btsMT5TTZ34/CH2o4JRMTtTXSJ3sduQty1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKbDuE%2FbtsMT5TTZ34%2FCH2o4JRMTtTXSJ3sduQty1%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WdZU1/btsMUqcvqpG/iQk8jujaUiAkqapYwvpHl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WdZU1/btsMUqcvqpG/iQk8jujaUiAkqapYwvpHl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WdZU1/btsMUqcvqpG/iQk8jujaUiAkqapYwvpHl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWdZU1%2FbtsMUqcvqpG%2FiQk8jujaUiAkqapYwvpHl0%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&lt;b&gt;키 페어 유형&lt;/b&gt;은 RSA,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프라이빗 키 파일 형식&lt;/b&gt;은 .pem을 선택하신 후 키 페어 생성을 눌러주세요.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTfNXY/btsMT7Eb4ad/jzJYhJzqLTTtoVc0ZlZnoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTfNXY/btsMT7Eb4ad/jzJYhJzqLTTtoVc0ZlZnoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTfNXY/btsMT7Eb4ad/jzJYhJzqLTTtoVc0ZlZnoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTfNXY%2FbtsMT7Eb4ad%2FjzJYhJzqLTTtoVc0ZlZnoK%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;361&quot; height=&quot;144&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 페어 생성시 .pem 확장자인 파일이 다운로드 됩니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kySAS/btsMS67c4ua/FxMp6QdZL5aS2X8xFw9dKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kySAS/btsMS67c4ua/FxMp6QdZL5aS2X8xFw9dKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kySAS/btsMS67c4ua/FxMp6QdZL5aS2X8xFw9dKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkySAS%2FbtsMS67c4ua%2FFxMp6QdZL5aS2X8xFw9dKk%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;1004&quot; height=&quot;680&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&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;저는 C드라이브 - key 위치에 저장해두었습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7LEt/btsMSv7LIDJ/DE4iXZS1xGJlmNC7Ii5TU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7LEt/btsMSv7LIDJ/DE4iXZS1xGJlmNC7Ii5TU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7LEt/btsMSv7LIDJ/DE4iXZS1xGJlmNC7Ii5TU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7LEt%2FbtsMSv7LIDJ%2FDE4iXZS1xGJlmNC7Ii5TU0%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/INmop/btsMTLapgZX/kcK6xXSRmCmAJYlWQC5yW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/INmop/btsMTLapgZX/kcK6xXSRmCmAJYlWQC5yW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/INmop/btsMTLapgZX/kcK6xXSRmCmAJYlWQC5yW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FINmop%2FbtsMTLapgZX%2FkcK6xXSRmCmAJYlWQC5yW1%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리티어의 경우 스토리지를 월 최대 30GB까지 사용가능합니다.&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;만약 여러 인스턴스를 생성한다면 30GB를 적절히 나눠서 사용하면 되지만, 저희는 하나의 인스턴스를 생성할 것이기 때문에 최대 크기인 30GB를 지정했습니다.&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;스토리지 구성까지 끝났다면 화면 우측의 요약화면에 있는 &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;레이어 3.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chaXPA/btsMT1c5O8q/NakTgkWwj6IaSv3abGemE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chaXPA/btsMT1c5O8q/NakTgkWwj6IaSv3abGemE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chaXPA/btsMT1c5O8q/NakTgkWwj6IaSv3abGemE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchaXPA%2FbtsMT1c5O8q%2FNakTgkWwj6IaSv3abGemE1%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;1311&quot; height=&quot;843&quot; data-filename=&quot;레이어 3.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;2. 보안 그룹 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-1. 보안 그룹 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqXjWc/btsMSFI0Ipx/zSDNZkYEGxmWIaikF6h0KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqXjWc/btsMSFI0Ipx/zSDNZkYEGxmWIaikF6h0KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqXjWc/btsMSFI0Ipx/zSDNZkYEGxmWIaikF6h0KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqXjWc%2FbtsMSFI0Ipx%2FzSDNZkYEGxmWIaikF6h0KK%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;EC2 대시보드로 돌아가 왼쪽 탭에서 &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;그리고 &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcuJqo/btsMSunwcPO/Ej8bsGx9n9IlzKHyt75AH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcuJqo/btsMSunwcPO/Ej8bsGx9n9IlzKHyt75AH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcuJqo/btsMSunwcPO/Ej8bsGx9n9IlzKHyt75AH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcuJqo%2FbtsMSunwcPO%2FEj8bsGx9n9IlzKHyt75AH1%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;보안 그룹의 경우 &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beGBxB/btsMTa2OJ0g/suumARqn6v5BEf49rjQj40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beGBxB/btsMTa2OJ0g/suumARqn6v5BEf49rjQj40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beGBxB/btsMTa2OJ0g/suumARqn6v5BEf49rjQj40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeGBxB%2FbtsMTa2OJ0g%2FsuumARqn6v5BEf49rjQj40%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 putty 등으로 접속하기 위한 SSH 포트, 그리고 HTTP, HTTPS 포트를 열어두어야 합니다.&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;&lt;b&gt;인바운드 규칙&lt;/b&gt;에서 규칙 추가 버튼을 눌러서 각각의 유형을 &lt;b&gt;SSH, HTTP, HTTPS&lt;/b&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;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;/b&gt;는 &lt;b&gt;0.0.0.0/0&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;Anywhere-IPv4&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;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;blockquote data-ke-style=&quot;style3&quot;&gt;※ SSH(포트 22)는 보안상 내 IP로만 접근을 허용하는 것이 안전합니다.&amp;nbsp;&lt;br /&gt;개발/연습용이라면&amp;nbsp;0.0.0.0/0으로&amp;nbsp;설정해도&amp;nbsp;되지만,&amp;nbsp;실&amp;nbsp;서비스&amp;nbsp;환경에서는&amp;nbsp;제한을&amp;nbsp;권장합니다.&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/skY3F/btsMT0yuznx/Hvpf8xfkzp5p5drdqpR5zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/skY3F/btsMT0yuznx/Hvpf8xfkzp5p5drdqpR5zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/skY3F/btsMT0yuznx/Hvpf8xfkzp5p5drdqpR5zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FskY3F%2FbtsMT0yuznx%2FHvpf8xfkzp5p5drdqpR5zK%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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; 2-2. 보안 그룹 적용&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cArc5z/btsMUNrNliV/D4qcmuIcgYIjPqV0Yj3F2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cArc5z/btsMUNrNliV/D4qcmuIcgYIjPqV0Yj3F2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cArc5z/btsMUNrNliV/D4qcmuIcgYIjPqV0Yj3F2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcArc5z%2FbtsMUNrNliV%2FD4qcmuIcgYIjPqV0Yj3F2k%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 EC2 대시보드로 돌아가, &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;앞서 만들었던 인스턴스를 우클릭한 후, &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;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;레이어 7.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QZ15c/btsMSRbAUU0/rkIRtfnLvlTDXhC1aBpii0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QZ15c/btsMSRbAUU0/rkIRtfnLvlTDXhC1aBpii0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QZ15c/btsMSRbAUU0/rkIRtfnLvlTDXhC1aBpii0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQZ15c%2FbtsMSRbAUU0%2FrkIRtfnLvlTDXhC1aBpii0%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;1311&quot; height=&quot;843&quot; data-filename=&quot;레이어 7.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연결된 보안 그룹&lt;/b&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;/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;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;h2 data-ke-size=&quot;size26&quot;&gt;3. PuTTY로 서버 접속하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;레이어 8.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NWRB7/btsMT6ywkCd/ObpLoO1138WcSDrzL1kRWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NWRB7/btsMT6ywkCd/ObpLoO1138WcSDrzL1kRWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NWRB7/btsMT6ywkCd/ObpLoO1138WcSDrzL1kRWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNWRB7%2FbtsMT6ywkCd%2FObpLoO1138WcSDrzL1kRWk%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;1311&quot; height=&quot;843&quot; data-filename=&quot;레이어 8.png&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;EC2 - 인스턴스 - 인스턴스&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3vLa4/btsMTLapWvc/W0s6ydlP9XO3xwxQ66TAhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3vLa4/btsMTLapWvc/W0s6ydlP9XO3xwxQ66TAhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3vLa4/btsMTLapWvc/W0s6ydlP9XO3xwxQ66TAhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3vLa4%2FbtsMTLapWvc%2FW0s6ydlP9XO3xwxQ66TAhk%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;1311&quot; height=&quot;843&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 방법은 여러가지가 있는데, 저는&lt;b&gt; SSH 클라이언트&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;PuTTY&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;PuTTY를 통해서 EC2를 접속하는 자세한 방법은 모 블로거의 글에 자세히 나와있으니 아래 링크를 참고하시기 바랍니다.&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;위 화면에서 저희가 필요한 것은 마지막에 위치한 예 : ssh -i ~~~~~~~~ 에서 &lt;b&gt;ubuntu@ec2 ~~ compute.anazonaws.com&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;/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;&lt;a href=&quot;https://angelplayer.tistory.com/476&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://angelplayer.tistory.com/476&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1742709927929&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[PuTTY] EC2 ssh pk(ppk, pem)으로 접속하기&quot; data-og-description=&quot;https://www.putty.org/ Download PuTTY - a free SSH and telnet client for Windows Is Bitvise affiliated with PuTTY? Bitvise is not affiliated with PuTTY. We develop our SSH Server for Windows, which is compatible with PuTTY. Many PuTTY users are therefore o&quot; data-og-host=&quot;angelplayer.tistory.com&quot; data-og-source-url=&quot;https://angelplayer.tistory.com/476&quot; data-og-url=&quot;https://angelplayer.tistory.com/476&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fZT4W/hyYr26Xrff/D6oUyHO5D76u9vtn5cvPa0/img.png?width=618&amp;amp;height=551&amp;amp;face=0_0_618_551,https://scrap.kakaocdn.net/dn/zlSdj/hyYrTIWZy9/IcZYvmP7DAZ4mTIsQKLZo0/img.png?width=618&amp;amp;height=551&amp;amp;face=0_0_618_551,https://scrap.kakaocdn.net/dn/d8kH41/hyYuiBhnEP/RqVvUzllDLr86QGjkyVpmK/img.png?width=1446&amp;amp;height=853&amp;amp;face=0_0_1446_853&quot;&gt;&lt;a href=&quot;https://angelplayer.tistory.com/476&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://angelplayer.tistory.com/476&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fZT4W/hyYr26Xrff/D6oUyHO5D76u9vtn5cvPa0/img.png?width=618&amp;amp;height=551&amp;amp;face=0_0_618_551,https://scrap.kakaocdn.net/dn/zlSdj/hyYrTIWZy9/IcZYvmP7DAZ4mTIsQKLZo0/img.png?width=618&amp;amp;height=551&amp;amp;face=0_0_618_551,https://scrap.kakaocdn.net/dn/d8kH41/hyYuiBhnEP/RqVvUzllDLr86QGjkyVpmK/img.png?width=1446&amp;amp;height=853&amp;amp;face=0_0_1446_853');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[PuTTY] EC2 ssh pk(ppk, pem)으로 접속하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.putty.org/ Download PuTTY - a free SSH and telnet client for Windows Is Bitvise affiliated with PuTTY? Bitvise is not affiliated with PuTTY. We develop our SSH Server for Windows, which is compatible with PuTTY. Many PuTTY users are therefore o&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;angelplayer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;※ 위 블로그 내용을 요약하면 아래와 같습니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;PuTTY로&amp;nbsp;접속하려면&amp;nbsp;.pem&amp;nbsp;키를&amp;nbsp;.ppk로&amp;nbsp;변환해야&amp;nbsp;합니다.&lt;br /&gt;1.&amp;nbsp;PuTTYgen&amp;nbsp;실행&amp;nbsp;&amp;rarr;&amp;nbsp;Load&amp;nbsp;&amp;rarr;&amp;nbsp;.pem&amp;nbsp;파일&amp;nbsp;선택&lt;br /&gt;2.&amp;nbsp;&quot;Save&amp;nbsp;private&amp;nbsp;key&quot;&amp;nbsp;클릭&amp;nbsp;&amp;rarr;&amp;nbsp;.ppk&amp;nbsp;저장&lt;br /&gt;3.&amp;nbsp;PuTTY&amp;nbsp;실행&amp;nbsp;후,&amp;nbsp;SSH&amp;nbsp;&amp;rarr;&amp;nbsp;Auth&amp;nbsp;&amp;rarr;&amp;nbsp;Private&amp;nbsp;Key에&amp;nbsp;.ppk&amp;nbsp;경로&amp;nbsp;지정&lt;br /&gt;4.&amp;nbsp;Session&amp;nbsp;탭에서&amp;nbsp;Host&amp;nbsp;Name에&amp;nbsp;ubuntu@퍼블릭IP&amp;nbsp;입력&lt;br /&gt;5. Save 후 Open 클릭 &amp;rarr; 접속&amp;nbsp;&lt;/blockquote&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UEw0u/btsMTIET7Iy/Ki3NxUvwyKM5PMsbqHOkuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UEw0u/btsMTIET7Iy/Ki3NxUvwyKM5PMsbqHOkuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UEw0u/btsMTIET7Iy/Ki3NxUvwyKM5PMsbqHOkuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUEw0u%2FbtsMTIET7Iy%2FKi3NxUvwyKM5PMsbqHOkuK%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;452&quot; height=&quot;442&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 링크를 참조하여, &lt;b&gt;ppk 파일 생성,&lt;/b&gt;&amp;nbsp;&lt;b&gt;경로 설정&lt;/b&gt;이 끝났으면 PuTTY 메인 화면으로 돌아갑니다.&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;ssh -i ~~~~~~~~ 부분에서 ubuntu@ec2 ~~ compute.anazonaws.com&lt;/b&gt;를 &lt;b&gt;Host Name(or IP address)&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;p data-ke-size=&quot;size16&quot;&gt;이렇게 지정한 설정은 앞으로 자주 들어갈 확률이 높으므로 Saved Sessions에서 명칭을 지정하고 Save를 해두시는 것을 추천드립니다.&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;설정이 완료되면 Open을 눌러줍니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;레이어 1.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV6Agm/btsMS51E0eW/byQLSOlyXdO9iut0NdmCN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV6Agm/btsMS51E0eW/byQLSOlyXdO9iut0NdmCN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV6Agm/btsMS51E0eW/byQLSOlyXdO9iut0NdmCN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV6Agm%2FbtsMS51E0eW%2FbyQLSOlyXdO9iut0NdmCN0%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;661&quot; height=&quot;418&quot; data-filename=&quot;레이어 1.png&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 PuTTY로 AWS EC2에 접속한 것을 확인할 수 있습니다.&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>Programming/Infra</category>
      <category>aws</category>
      <category>cloud</category>
      <category>EC2</category>
      <category>PuTTY</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/595</guid>
      <comments>https://angelplayer.tistory.com/595#entry595comment</comments>
      <pubDate>Mon, 24 Mar 2025 08:31:25 +0900</pubDate>
    </item>
    <item>
      <title>다국어 기능 구현하기 (Feat. Next, App Router, TS, next-intl)</title>
      <link>https://angelplayer.tistory.com/594</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPsB8h/btsLAeZSzqN/xzWydq6gl3429ni0QNhhZ0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPsB8h/btsLAeZSzqN/xzWydq6gl3429ni0QNhhZ0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPsB8h/btsLAeZSzqN/xzWydq6gl3429ni0QNhhZ0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPsB8h%2FbtsLAeZSzqN%2FxzWydq6gl3429ni0QNhhZ0%2Fimg.webp&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;300&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발 환경&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- Windows&lt;br /&gt;- VSCode&lt;br /&gt;&lt;br /&gt;- Next.js 15 (App Router)&lt;br /&gt;- Typescript&lt;br /&gt;&lt;br /&gt;- next-intl ^3.26.3&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;다국어 (영어, 한국어)를 지원하는 웹 페이지 제작&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;h2 data-ke-size=&quot;size26&quot;&gt;다국어 및 i18n 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Next.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;span style=&quot;background-color: #ffffff; color: #1b1b1b; text-align: start;&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735351992312&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;국제화 (internationalization, I18N) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&quot; data-og-description=&quot;국제화 (i18n (&amp;quot;internationalization&amp;quot; 부터 유래, 20자로 이루어진 단어))는 제품이나 서비스를 모든 대상 문화에 쉽게 적용할 수 있도록 하는 모범 사례입니다.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckI4kP/hyXSyRVaT1/I4yR6M8IZKVgTWb6LAAe01/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Glossary/Internationalization&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckI4kP/hyXSyRVaT1/I4yR6M8IZKVgTWb6LAAe01/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;국제화 (internationalization, I18N) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;국제화 (i18n (&quot;internationalization&quot; 부터 유래, 20자로 이루어진 단어))는 제품이나 서비스를 모든 대상 문화에 쉽게 적용할 수 있도록 하는 모범 사례입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다국어를&amp;nbsp;지원하는&amp;nbsp;웹사이트는&amp;nbsp;국제화(Internationalization,&amp;nbsp;i18n)&amp;nbsp;과정을&amp;nbsp;통해&amp;nbsp;여러&amp;nbsp;국가의&amp;nbsp;사람들이&amp;nbsp;쉽게&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;개발됩니다. &lt;br /&gt;&lt;br /&gt;국제화(이하&amp;nbsp;i18n)는&amp;nbsp;단순히&amp;nbsp;언어를&amp;nbsp;번역하는&amp;nbsp;것뿐만&amp;nbsp;아니라,&amp;nbsp;지역별&amp;nbsp;날짜&amp;nbsp;및&amp;nbsp;시간&amp;nbsp;형식,&amp;nbsp;숫자&amp;nbsp;및&amp;nbsp;통화&amp;nbsp;단위,&amp;nbsp;측정&amp;nbsp;단위&amp;nbsp;등을&amp;nbsp;사용자의&amp;nbsp;환경에&amp;nbsp;맞게&amp;nbsp;출력하는&amp;nbsp;것을&amp;nbsp;목표로&amp;nbsp;합니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;이를&amp;nbsp;통해&amp;nbsp;사용자는&amp;nbsp;자신의&amp;nbsp;지역이나&amp;nbsp;문화적&amp;nbsp;맥락에&amp;nbsp;맞는&amp;nbsp;경험을&amp;nbsp;제공받을&amp;nbsp;수&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;국제화를 지원하기 위해서는 사용자의 언어 정보를 라우팅에 반영하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next 환경에서 i18n을 지원하기 위한 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대표적인&lt;span&gt; &lt;/span&gt;&lt;/span&gt;라우팅 방법으로는&lt;/p&gt;
&lt;p style=&quot;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: #334155; text-align: left;&quot;&gt;Prefix-based routing&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;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: #334155; text-align: left;&quot;&gt;Domain-based routing&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 가지 방식이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;Prefix-based routing는 URL 경로에 언어 정보를 접두어(Prefix)로 포함하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;- 절대 경로 뒤에 /en, /ko와 같이 언어 정보를 엔드 포인트로 전달합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;- ex) angelplayer.tistory.com/en&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;Domain-based routing는 언어별로 서로 다른 서브도메인 또는 도메인을 사용하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;- ex1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;en.&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;angelplayer.tistory.com (서브 도메인 사용 예제)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;- ex2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #334155; text-align: left;&quot;&gt;angelplayer.kr (국가별 상위 도메인 사용 예제)&lt;/span&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;&amp;nbsp;&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4zjhc/btsLzfryFEY/Ua20a8u5RHdbLTMKB8H4Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4zjhc/btsLzfryFEY/Ua20a8u5RHdbLTMKB8H4Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4zjhc/btsLzfryFEY/Ua20a8u5RHdbLTMKB8H4Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4zjhc%2FbtsLzfryFEY%2FUa20a8u5RHdbLTMKB8H4Uk%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;1227&quot; height=&quot;722&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3Qada/btsLz5hHd8k/3ZIs0OkchmWn5WndG94Pz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3Qada/btsLz5hHd8k/3ZIs0OkchmWn5WndG94Pz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3Qada/btsLz5hHd8k/3ZIs0OkchmWn5WndG94Pz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3Qada%2FbtsLz5hHd8k%2F3ZIs0OkchmWn5WndG94Pz0%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;1236&quot; height=&quot;654&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Next.js에서 다국어를 지원하는 라이브러리는&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;크게&lt;span&gt;&amp;nbsp;&lt;/span&gt;next-i18next와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;next-intl가 있습니다.&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;과거에는 next-i18next를 선호하는 사용자가 압도적으로 많았지만, 최근에는 next-intl을 채택하는 사례가 점점 증가하고 있다는 것을 패키지 다운로드 수 등으로 확인할 수 있습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fjbfd/btsLBjMBNCz/7mTaMIg5Kn4p9ydYdsxXJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fjbfd/btsLBjMBNCz/7mTaMIg5Kn4p9ydYdsxXJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fjbfd/btsLBjMBNCz/7mTaMIg5Kn4p9ydYdsxXJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFjbfd%2FbtsLBjMBNCz%2F7mTaMIg5Kn4p9ydYdsxXJ0%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;721&quot; height=&quot;255&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://locize.com/blog/next-app-dir-i18n/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://locize.com/blog/next-app-dir-i18n/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735354602057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;i18n with Next.js 13/14 and app directory / App Router (an i18next guide)&quot; data-og-description=&quot;Looking for a way to internationalize your Next.js 13/14 project with the new app directory / App Router paradigm? Then this guide is for you!&quot; data-og-host=&quot;locize.com&quot; data-og-source-url=&quot;https://locize.com/blog/next-app-dir-i18n/&quot; data-og-url=&quot;https://locize.com/blog/next-app-dir-i18n/index.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/drRZpJ/hyXSpU0866/betZ9rLKJmMVla11DKlhOk/img.jpg?width=1040&amp;amp;height=489&amp;amp;face=0_0_1040_489,https://scrap.kakaocdn.net/dn/bdyPcO/hyXSFjd817/J1OL7gcoOOBqzMUJS60rEk/img.jpg?width=1040&amp;amp;height=489&amp;amp;face=0_0_1040_489,https://scrap.kakaocdn.net/dn/bDtLMj/hyXSvt89Nr/W9QZwh7QCQ1hSk8hkcIKuk/img.jpg?width=2000&amp;amp;height=562&amp;amp;face=0_0_2000_562&quot;&gt;&lt;a href=&quot;https://locize.com/blog/next-app-dir-i18n/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://locize.com/blog/next-app-dir-i18n/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/drRZpJ/hyXSpU0866/betZ9rLKJmMVla11DKlhOk/img.jpg?width=1040&amp;amp;height=489&amp;amp;face=0_0_1040_489,https://scrap.kakaocdn.net/dn/bdyPcO/hyXSFjd817/J1OL7gcoOOBqzMUJS60rEk/img.jpg?width=1040&amp;amp;height=489&amp;amp;face=0_0_1040_489,https://scrap.kakaocdn.net/dn/bDtLMj/hyXSvt89Nr/W9QZwh7QCQ1hSk8hkcIKuk/img.jpg?width=2000&amp;amp;height=562&amp;amp;face=0_0_2000_562');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;i18n with Next.js 13/14 and app directory / App Router (an i18next guide)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Looking for a way to internationalize your Next.js 13/14 project with the new app directory / App Router paradigm? Then this guide is for you!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;locize.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;n&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;ext-i18next의 경우 i18next 라이브러리를 기반으로 국제화를 지원하기 때문에 플러그인 및 부가 기능이 굉장히 많습니다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 저는 Typescript 환경에서 개발을 원하였으나, 공식 블로그에서는 기본적으로 Javascript 환경의 개발 방법에 대해서 설명하고 있어, 전환이 쉽지 않았습니다.&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;&lt;a href=&quot;https://next-intl.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://next-intl.dev/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1735354612276&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;next-intl &amp;ndash; Internationalization (i18n) for Next.js&quot; data-og-description=&quot;Using next-intl too? Let us know!&quot; data-og-host=&quot;next-intl.dev&quot; data-og-source-url=&quot;https://next-intl.dev/&quot; data-og-url=&quot;https://next-intl.dev/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://next-intl.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://next-intl.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;next-intl &amp;ndash; Internationalization (i18n) for Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using next-intl too? Let us know!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;next-intl.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next-intl의 경우 공식 document를 제공하며, typescript 환경에서의 예제 코드도 제공하고 있기 때문에 매우 편리하였습니다.&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;및&amp;nbsp;설정&amp;nbsp;단계에서&amp;nbsp;필요한&amp;nbsp;패키지가&amp;nbsp;next-intl&amp;nbsp;하나뿐이라는&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;따라서 저는 next-intl을 사용하여 i18n을 지원하는 서비스를 만들기로 결정하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(앞으로 생성할 프로젝트는 상단에 링크로 제공된 next-intl의 Document에 기반으로 제작하였습니다.)&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;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 생성 및 환경 구축&lt;/h2&gt;
&lt;pre id=&quot;code_1735351294228&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install next-intl&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 next-intl 패키지를 설치해줍니다.&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;pre id=&quot;code_1735354846934&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Root
├── public
│   └── messages
│      ├── en.json (1)
│      └── ko.json
├── next.config.mjs (2)
└── src
    ├── i18n
    │   ├── routing.ts (3)
    │   └── request.ts (5)
    ├── middleware.ts (4)
    └── app
        └── [locale]
            ├── layout.tsx (6)
            └── page.tsx (7)&lt;/code&gt;&lt;/pre&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;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;(1) public/message/[locale].json&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735356756316&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;HomePage&quot;: {
        &quot;title&quot;: &quot;Hello world!&quot;,
        &quot;about&quot;: &quot;Go to the about page&quot;
    },
    &quot;AboutPage&quot;: {
        &quot;name&quot;: &quot;AngelPlayer&quot;
    }
}&lt;/code&gt;&lt;/pre&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;파일의 최상위 key는 각 페이지의 namespace로 사용합니다.&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;하위에 중접된 key-value는 각각 텍스트의 식별자와 번역 문자열을 저장하고 있습니다.&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;&lt;b&gt;(2) next.config.mjs&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735357029086&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin();

/** @type {import('next').NextConfig} */
const nextConfig = {};

export default withNextIntl(nextConfig);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next-intl 플러그인을 사용하여 Next.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;&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;(3) src/i18n/routing.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735357489441&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
    // A list of all locales that are supported
    locales: ['en', 'ko'],

    // Used when no locale matches
    defaultLocale: 'en',
});

// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;routing.ts는&amp;nbsp;next-intl&amp;nbsp;라이브러리에서&amp;nbsp;제공하는&amp;nbsp;라우팅&amp;nbsp;및&amp;nbsp;네비게이션&amp;nbsp;기능을&amp;nbsp;설정하는&amp;nbsp;파일입니다. &lt;br /&gt;&lt;br /&gt;이&amp;nbsp;파일에서는&amp;nbsp;지원&amp;nbsp;언어및&amp;nbsp;기본&amp;nbsp;언어를&amp;nbsp;정의하여&amp;nbsp;프로젝트에서&amp;nbsp;다국어를&amp;nbsp;지원할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;설정합니다. &lt;br /&gt;&lt;br /&gt;Link,&amp;nbsp;redirect,&amp;nbsp;usePathname,&amp;nbsp;useRouter,&amp;nbsp;getPathname과&amp;nbsp;같은&amp;nbsp;네비게이션&amp;nbsp;유틸리티를&amp;nbsp;생성하여,&amp;nbsp;프로젝트&amp;nbsp;내부에서&amp;nbsp;언어별&amp;nbsp;라우팅과&amp;nbsp;네비게이션을&amp;nbsp;쉽게&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있도록&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;&lt;b&gt;(4) src/middleware.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735357953723&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

export default createMiddleware(routing);

export const config = {
    // Match only internationalized pathnames
    matcher: ['/', '/(ko|en)/:path*'],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js의&amp;nbsp;Middleware&amp;nbsp;기능을&amp;nbsp;활용하여&amp;nbsp;애플리케이션&amp;nbsp;요청(request)을&amp;nbsp;가로채고&amp;nbsp;처리하는&amp;nbsp;설정&amp;nbsp;파일입니다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;createMiddleware &lt;br /&gt;createMiddleware는&amp;nbsp;요청이&amp;nbsp;들어올&amp;nbsp;때&amp;nbsp;라우팅&amp;nbsp;및&amp;nbsp;언어&amp;nbsp;관련&amp;nbsp;처리를&amp;nbsp;수행하는&amp;nbsp;Middleware를&amp;nbsp;생성합니다. &lt;br /&gt;&lt;br /&gt;routing.ts에서&amp;nbsp;정의한&amp;nbsp;라우팅&amp;nbsp;설정(locales,&amp;nbsp;defaultLocale)을&amp;nbsp;사용하여&amp;nbsp;언어&amp;nbsp;정보를&amp;nbsp;처리하며,&amp;nbsp;사용자의&amp;nbsp;언어&amp;nbsp;설정에&amp;nbsp;따라&amp;nbsp;적절한&amp;nbsp;경로로&amp;nbsp;리다이렉트합니다. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;config &lt;br /&gt;Next.js의&amp;nbsp;Middleware가&amp;nbsp;작동할&amp;nbsp;경로를&amp;nbsp;정의합니다. &lt;br /&gt;&lt;br /&gt;matcher를&amp;nbsp;통해&amp;nbsp;Middleware가&amp;nbsp;적용될&amp;nbsp;URL&amp;nbsp;패턴을&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;&lt;b&gt;(5) src/i18n/request.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735358047972&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
import path from 'path';
import fs from 'fs';

export default getRequestConfig(async ({ requestLocale }) =&amp;gt; {
    // This typically corresponds to the `[locale]` segment
    let locale = await requestLocale;

    // Ensure that a valid locale is used
    if (!locale || !routing.locales.includes(locale as any)) {
        locale = routing.defaultLocale;
    }

    const messagesPath = path.join(process.cwd(), 'public', 'messages', `${locale}.json`);
    const messages = JSON.parse(fs.readFileSync(messagesPath, 'utf8'));

    return {
        locale,
        messages,
    };
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request.ts는&amp;nbsp;요청에&amp;nbsp;따라&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;동적으로&amp;nbsp;로드하고&amp;nbsp;처리하는&amp;nbsp;설정&amp;nbsp;파일입니다. &lt;br /&gt;&lt;br /&gt;URL을&amp;nbsp;통해&amp;nbsp;언어&amp;nbsp;정보를&amp;nbsp;받아오고,&amp;nbsp;유효성&amp;nbsp;검증을&amp;nbsp;수행합니다. &lt;br /&gt;&lt;br /&gt;언어&amp;nbsp;정보를&amp;nbsp;기반으로&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;로드합니다. &lt;br /&gt;&lt;br /&gt;번역&amp;nbsp;데이터를&amp;nbsp;페이지에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;파싱을&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;&lt;b&gt;(6)&amp;nbsp;src/app/[locale]/layout.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735358526625&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { Metadata } from 'next';
import './../globals.css';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';

export const metadata: Metadata = {
    title: 'AngelPlayer`s i18n',
    description: 'Sample code that applies next-intl in Next.js (App Router) and TypeScript environments.',
};

export default async function LocaleLayout({
    children,
    params,
}: {
    children: React.ReactNode;
    params: { locale: string };
}) {
    // `params`에서 `locale`을 비동기로 가져옵니다.
    const { locale } = await params;

    // `locale`이 유효하지 않으면 404를 반환합니다.
    if (!routing.locales.includes(locale as any)) {
        notFound();
    }

    // 메시지를 비동기로 가져옵니다.
    const messages = await getMessages(locale as any);

    return (
        &amp;lt;html lang={locale}&amp;gt;
            &amp;lt;body&amp;gt;
                &amp;lt;NextIntlClientProvider messages={messages}&amp;gt;{children}&amp;lt;/NextIntlClientProvider&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;next-intl&amp;nbsp;라이브러리를&amp;nbsp;활용하여&amp;nbsp;i18n를&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;환경을&amp;nbsp;구축합니다. &lt;br /&gt;&lt;br /&gt;getMessages()를&amp;nbsp;통해&amp;nbsp;요청된&amp;nbsp;언어에&amp;nbsp;맞는&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;비동기적으로&amp;nbsp;로드합니다. &lt;br /&gt;&lt;br /&gt;NextIntlClientProvider&amp;nbsp;컨텍스트를&amp;nbsp;통해서&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;제공함으로써&amp;nbsp;하위&amp;nbsp;컴포넌트에서&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;읽고&amp;nbsp;출력할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&gt;&lt;br /&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;(7)&amp;nbsp;src/app/[locale]/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735358604928&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/routing';

export default function HomePage() {
    const t = useTranslations('HomePage');
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;{t('title')}&amp;lt;/h1&amp;gt;
            &amp;lt;Link href='/about'&amp;gt;{t('about')}&amp;lt;/Link&amp;gt;
        &amp;lt;/div&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;size16&quot;&gt;&lt;b&gt;(7-1) src/app/[locale]/about&lt;b&gt;/page.tsx&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735358656200&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useTranslations } from 'next-intl';
import React from 'react';

export default function AboutPage() {
    const t = useTranslations('AboutPage');
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;{t('name')}&amp;lt;/h1&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;page.tsx에서는&amp;nbsp;useTranslations()를&amp;nbsp;통해서&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;받아와&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있습니다. &lt;br /&gt;&lt;br /&gt;useTranslations('namespace')에서&amp;nbsp;namespace를&amp;nbsp;입력하면,&amp;nbsp;해당&amp;nbsp;네임스페이스의&amp;nbsp;번역&amp;nbsp;데이터를&amp;nbsp;가져옵니다. &lt;br /&gt;가져온&amp;nbsp;번역&amp;nbsp;데이터는&amp;nbsp;t&amp;nbsp;변수에&amp;nbsp;저장합니다. &lt;br /&gt;&lt;br /&gt;t('key')를&amp;nbsp;호출하면,&amp;nbsp;지정된&amp;nbsp;키에&amp;nbsp;해당하는&amp;nbsp;번역&amp;nbsp;문자열을&amp;nbsp;텍스트로&amp;nbsp;출력할&amp;nbsp;수&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mIcN4/btsLAh938qs/XoAGTPgRaw3qT0rxn6J4a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mIcN4/btsLAh938qs/XoAGTPgRaw3qT0rxn6J4a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mIcN4/btsLAh938qs/XoAGTPgRaw3qT0rxn6J4a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmIcN4%2FbtsLAh938qs%2FXoAGTPgRaw3qT0rxn6J4a0%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;391&quot; height=&quot;190&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBGfKY/btsLAtvCMVe/4CbmSE0cCO8OOemDOpMN11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBGfKY/btsLAtvCMVe/4CbmSE0cCO8OOemDOpMN11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBGfKY/btsLAtvCMVe/4CbmSE0cCO8OOemDOpMN11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBGfKY%2FbtsLAtvCMVe%2F4CbmSE0cCO8OOemDOpMN11%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;387&quot; height=&quot;212&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&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>Web/React.js</category>
      <category>I18N</category>
      <category>Next</category>
      <category>react</category>
      <category>typescript</category>
      <author>AngelPlayer</author>
      <guid isPermaLink="true">https://angelplayer.tistory.com/594</guid>
      <comments>https://angelplayer.tistory.com/594#entry594comment</comments>
      <pubDate>Sat, 28 Dec 2024 13:40:27 +0900</pubDate>
    </item>
  </channel>
</rss>