<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>YS's develop story</title>
    <link>https://yusang.tistory.com/</link>
    <description> &amp;zwj;  개발공부 기록 블로그입니다.  
</description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 00:46:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Yusang</managingEditor>
    <image>
      <title>YS's develop story</title>
      <url>https://tistory1.daumcdn.net/tistory/4200994/attach/2633dc4af88a4d61a5b1734ed089d609</url>
      <link>https://yusang.tistory.com</link>
    </image>
    <item>
      <title>싱글톤 vs HikariCP, Connection 관리 부분에서 어떤 차이점이 있을까</title>
      <link>https://yusang.tistory.com/151</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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746080577227&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package util;

import io.github.cdimascio.dotenv.Dotenv;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    private static final Dotenv dotenv = Dotenv.load();
    private static final String URL = dotenv.get(&quot;DB_HOST&quot;);
    private static final String DB_USER = dotenv.get(&quot;DB_NAME&quot;);
    private static final String DB_PASSWORD = dotenv.get(&quot;DB_PW&quot;);

    // DB 연결 메서드
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, DB_USER, DB_PASSWORD);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class LendingBookDao {

    public int insertLending(Long bookId, Long userId, LocalDateTime dueDate) {
        String sql = LendingBookSql.INSERT;

        try (Connection connection = DBUtil.getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(sql)) {

            preparedStatement.setLong(1, bookId);
            preparedStatement.setLong(2, userId);
            preparedStatement.setTimestamp(3, Timestamp.valueOf(dueDate));
            preparedStatement.setInt(4, LendingStatus.IS_BORROWED);

            //영향을 받은 행의 갯수를 출력하게 된다.
            return preparedStatement.executeUpdate();

        } catch (SQLException e) {
            e.printStackTrace();
            return ResultCode.IS_ERROR;
        }
    }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤, HikariCp을 모두 적용하지 않은 순수 JDBC 코드&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;h2 data-end=&quot;137&quot; data-start=&quot;86&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 커넥션 관리: 싱글톤 vs HikariCP 차이점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;282&quot; data-start=&quot;139&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JDBC만을 활용하는 프로젝트를 진행하다가, 매번 DB와 연결하는 Connection 부분을 &lt;b&gt;싱글톤 패턴&lt;/b&gt;으로 바꾸려고 하던 중,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예전에 사용했던 &lt;b&gt;Hikari 커넥션 풀&lt;/b&gt;이 떠올라 둘의 차이를 비교해 보기 위해 이 글을 작성하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;287&quot; data-start=&quot;284&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;314&quot; data-start=&quot;289&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  DB 커넥션 관리는 왜 중요한가?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-end=&quot;465&quot; data-start=&quot;316&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 연결을 매번 새로 만드는 것은 성능에 매우 안 좋습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DB 연결(Connection)은 리소스를 많이 소모하는 작업&lt;/b&gt;이기 때문에,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매번 새로 만들기보다는 &lt;span style=&quot;color: #ee2323;&quot;&gt;재사용 가능한 커넥션 풀(Connection Pool)&lt;/span&gt;을 활용하는 것이 효율적입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-end=&quot;470&quot; data-start=&quot;467&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;501&quot; data-start=&quot;472&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  HikariCP 적용 전후 코드&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;527&quot; data-start=&quot;503&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적용 전 (DB 커넥션)&lt;/span&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1746081018025&quot; class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package util;

import io.github.cdimascio.dotenv.Dotenv;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    private static final Dotenv dotenv = Dotenv.load();
    private static final String URL = dotenv.get(&quot;DB_HOST&quot;);
    private static final String DB_USER = dotenv.get(&quot;DB_NAME&quot;);
    private static final String DB_PASSWORD = dotenv.get(&quot;DB_PW&quot;);

    // DB 연결 메서드
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, DB_USER, DB_PASSWORD);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;503&quot; data-end=&quot;527&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤 적용 (DB 커넥션)&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1746081449225&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package util;

import io.github.cdimascio.dotenv.Dotenv;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    private static final DBUtil instance = new DBUtil();

    private final String URL;
    private final String DB_USER;
    private final String DB_PASSWORD;

    // private 생성자
    private DBUtil() {
        Dotenv dotenv = Dotenv.load();
        this.URL = dotenv.get(&quot;DB_HOST&quot;);
        this.DB_USER = dotenv.get(&quot;DB_NAME&quot;);
        this.DB_PASSWORD = dotenv.get(&quot;DB_PW&quot;);
    }

    // 인스턴스를 반환하는 메서드
    public static DBUtil getInstance() {
        return instance;
    }

    // DB 연결 메서드
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, DB_USER, DB_PASSWORD);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;698&quot; data-start=&quot;673&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HikariCP 커넥션 풀 적용 후&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1746081105010&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package util;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.github.cdimascio.dotenv.Dotenv;

import java.sql.Connection;
import java.sql.SQLException;

public class DBUtil {

    private static final HikariDataSource dataSource;
    private static final Dotenv dotenv = Dotenv.load();

    // HikariCP 설정
    static {
        // Dotenv 라이브러리로 환경 변수 로드
        Dotenv dotenv = Dotenv.load();

        // HikariCP 설정
        HikariConfig config = new HikariConfig();

        // .env 파일에서 DB 연결 정보 가져오기
        config.setJdbcUrl(dotenv.get(&quot;DB_HOST&quot;));
        config.setUsername(dotenv.get(&quot;DB_NAME&quot;));
        config.setPassword(dotenv.get(&quot;DB_PW&quot;));

        // 커넥션 풀 사이즈 설정
        config.setMaximumPoolSize(10);

        // 설정된 데이터소스를 이용하여 커넥션 풀 초기화
        dataSource = new HikariDataSource(config);
    }

    // DB 연결 메서드
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;1148&quot; data-start=&quot;1145&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1169&quot; data-start=&quot;1150&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  커넥션 풀의 개념과 장점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1254&quot; data-start=&quot;1171&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;커넥션 풀(Connection Pool)&lt;/b&gt;은 미리 여러 개의 DB 연결을 만들어 두고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요할 때마다 꺼내서 쓰고 다시 돌려주는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1254&quot; data-start=&quot;1171&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;CP.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d10Fj6/btsNHtzOrWO/UKKdE20PA5Zn9SeWjct2KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d10Fj6/btsNHtzOrWO/UKKdE20PA5Zn9SeWjct2KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d10Fj6/btsNHtzOrWO/UKKdE20PA5Zn9SeWjct2KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd10Fj6%2FbtsNHtzOrWO%2FUKKdE20PA5Zn9SeWjct2KK%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;627&quot; height=&quot;600&quot; data-filename=&quot;CP.png&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cp2.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BjvPc/btsNIcRWxqg/ouubfeHHrkVGXdGJm4gGH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BjvPc/btsNIcRWxqg/ouubfeHHrkVGXdGJm4gGH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BjvPc/btsNIcRWxqg/ouubfeHHrkVGXdGJm4gGH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBjvPc%2FbtsNIcRWxqg%2FouubfeHHrkVGXdGJm4gGH0%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;668&quot; height=&quot;673&quot; data-filename=&quot;cp2.png&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;673&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;1272&quot; data-start=&quot;1256&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;커넥션 풀을 사용하면?&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1376&quot; data-start=&quot;1274&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1293&quot; data-start=&quot;1274&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 커넥션 생성 비용 최소화&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1322&quot; data-start=&quot;1294&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 연결/해제 과정을 줄여 &lt;b&gt;성능 향상&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1345&quot; data-start=&quot;1323&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ &lt;b&gt;멀티스레드 환경&lt;/b&gt;에 안전&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1376&quot; data-start=&quot;1346&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 설정에 따라 유휴 연결, 타임아웃 등 관리 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1381&quot; data-start=&quot;1378&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1410&quot; data-start=&quot;1383&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  싱글톤 방식 vs HikariCP 비교&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 1. &lt;b&gt;싱글톤 패턴으로 Connection 관리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤은 애플리케이션 전체에서 단 하나의 Connection 객체만 사용하도록 만드는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구현이 매우 간단함.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리 낭비를 막고, 단일 객체를 통해 일관된 DB 접근 가능.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DB 연결을 계속 유지&lt;/b&gt;하므로, &lt;b&gt;연결이 끊기거나 유휴 상태가 오래되면 장애 가능성&lt;/b&gt;이 큼.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;멀티스레드 환경에 부적합&lt;/b&gt;: 여러 요청이 하나의 Connection을 공유하면 동시 처리 시 충돌 발생.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 분리가 어려움.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;적합한 상황&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단일 사용자 / 테스트 환경 / 간단한 애플리케이션.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 2. &lt;b&gt;HikariCP (Connection Pool)&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HikariCP는 JDBC 연결을 미리 여러 개 만들어 놓고, 요청마다 풀에서 빌려 쓰고 반납하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;고성능, 경량 커넥션 풀&lt;/b&gt;: 현재 가장 빠르고 널리 사용됨.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스레드 세이프&lt;/b&gt;: 다중 사용자 환경에 적합.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;풀링 된 커넥션 재사용&lt;/b&gt; &amp;rarr; 커넥션 생성/종료 오버헤드 감소.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연결 유휴 시간, 타임아웃, 최대 수 제한 등 &lt;b&gt;다양한 설정 가능&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설정이 약간 복잡할 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메모리를 약간 더 사용함 (여러 커넥션 유지하므로).&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;적합한 상황&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;웹 애플리케이션&lt;/b&gt;, &lt;b&gt;동시 접속이 많은 서비스&lt;/b&gt;, &lt;b&gt;프로덕션 환경&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2355&quot; data-start=&quot;2348&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ 결론&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cp3.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCfGt0/btsNG0rq0mU/MdrMAV6apbc5Go5uRV9Mm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCfGt0/btsNG0rq0mU/MdrMAV6apbc5Go5uRV9Mm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCfGt0/btsNG0rq0mU/MdrMAV6apbc5Go5uRV9Mm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCfGt0%2FbtsNG0rq0mU%2FMdrMAV6apbc5Go5uRV9Mm0%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;480&quot; height=&quot;334&quot; data-filename=&quot;cp3.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2531&quot; data-start=&quot;2357&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2397&quot; data-start=&quot;2357&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;테스트 용도&lt;/b&gt;나 &lt;b&gt;학습 목적&lt;/b&gt;이라면 싱글톤으로도 충분합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2463&quot; data-start=&quot;2398&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, &lt;b&gt;실제 운영 환경&lt;/b&gt;이라면 반드시 HikariCP 같은 &lt;b&gt;커넥션 풀 라이브러리 사용을 권장&lt;/b&gt;합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2531&quot; data-start=&quot;2464&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 &lt;b&gt;Spring Boot&lt;/b&gt;와 같이 프레임워크에서 기본 지원하기 때문에 설정만 해주면 쉽게 사용할 수 있습&lt;/span&gt;니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2536&quot; data-start=&quot;2533&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2546&quot; data-start=&quot;2538&quot; data-ke-size=&quot;size26&quot;&gt;  참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2696&quot; data-start=&quot;2548&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2612&quot; data-start=&quot;2548&quot;&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP&quot; data-end=&quot;2612&quot; data-start=&quot;2550&quot;&gt;HikariCP GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li data-end=&quot;2696&quot; data-start=&quot;2613&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%84%A5%EC%85%98_%ED%92%80&quot; data-end=&quot;2696&quot; data-start=&quot;2615&quot;&gt;커넥션 풀 위키백과&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2800&quot; data-start=&quot;2714&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2862&quot; data-start=&quot;2807&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <category>HikariCP</category>
      <category>jdbc</category>
      <category>Singleton</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/151</guid>
      <comments>https://yusang.tistory.com/151#entry151comment</comments>
      <pubDate>Thu, 1 May 2025 20:40:40 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot + Redis 캐시 적용 가이드 (@Cacheable 활용)</title>
      <link>https://yusang.tistory.com/150</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n2jzK/btsNHJQcEQR/fg76E1aOkK8CfZAmwcmN11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n2jzK/btsNHJQcEQR/fg76E1aOkK8CfZAmwcmN11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n2jzK/btsNHJQcEQR/fg76E1aOkK8CfZAmwcmN11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn2jzK%2FbtsNHJQcEQR%2Ffg76E1aOkK8CfZAmwcmN11%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;455&quot; height=&quot;303&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableJpaAuditing
@EnableCaching //캐싱 기능을 활성화 하겠다는 어노테이션 -&amp;gt; redis활용을 위해 추가
public class SpringJavaApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringJavaApplication.class, args);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;@EnableCaching 어노테이션 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;관련 gradle 추가&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//redis config 설정 클래스들
@Configuration
public class RedisConfig {

    @Bean
    LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate() {
        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(connectionFactory());
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(10L)); // 캐시의 TTL 설정 (10분)

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(factory)
                .cacheDefaults(cacheConfig)
                .build();
    }

    //Redis는 LocalDateTime과 같은 일부 타입을 직렬화할 수 없기 때문에
    //Redis의 직렬화 도구인 GenericJackson2JsonRedisSerializer를 구성하여 Java 8의 시간 관련 타입을 처리할 수 있도록 함.
    @Bean
    public RedisSerializer&amp;lt;Object&amp;gt; redisSerializer() {
        return new GenericJackson2JsonRedisSerializer(new ObjectMapper().registerModule(new JavaTimeModule()));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;Redis config 추가&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Cacheable(cacheNames = &quot;allProducts&quot;, key = &quot;#pageable&quot;)
//상품 전체 조회
public List&amp;lt;GetProductsResponseDTO&amp;gt; getAllProducts(Pageable pageable) {
    Page&amp;lt;Product&amp;gt; productList = productRepository.findAll(pageable);

    List&amp;lt;GetProductsResponseDTO&amp;gt; getProductsResponseDTOList = productList.getContent().stream()
            .map(product -&amp;gt; {
                List&amp;lt;String&amp;gt; imageList = product.getProductImages().stream()
                        .map(ProductImages::getImageUrl)
                        .collect(Collectors.toList());
                return GetProductsResponseDTO.fromEntity(product, imageList);
            })
            .collect(Collectors.toList());

    return getProductsResponseDTOList;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;@Cacheable 어노테이션을 메서드에 추가하면 해당 메서드의 리턴값이 캐시에 저장되며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이후 동일한 매개변수로 호출될 때 캐시된 값을 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring의 @Cacheable 어노테이션은 스프링 프레임워크가 제공하는 캐싱 추상화 기능을 이용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 기능을 사용하면 메서드의 결과를 캐시에 저장하고, 동일한 매개변수로 메서드가 호출될 때 캐시된 결과를 반환할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어노테이션 하나로 이렇게 동작할 수 있는 이유는 Spring이 프록시를 사용하여 메서드 호출을 감싸고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 메서드 호출이 발생할 때 캐시 관련 로직을 수행하는 프록시를 생성하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 프록시는 메서드 호출을 처리하기 전에 캐시에 저장된 결과를 확인하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장된 결과가 있는 경우에는 캐시 된 결과를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 따라서 개발자는 &lt;b&gt;어노테이션만으로 간편하게 캐싱 기능을 활성화할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 통해 반복적으로 동일한 요청이 들어올 때마다 매번 비싼 연산을 수행하지 않고, &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캐시된 결과를 효율적으로 반환함으로써 성능을 향상할 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;@Cacheable 어노테이션을 사용하여 캐싱을 적용하면 Spring은 해당 메서드의 호출을 감싸는 프록시 객체를 생성합니다. 이 프록시 객체는 메서드가 호출될 때 캐시 관련 로직을 수행하고, 캐시에 저장된 결과를 반환하거나 메서드의 실행 결과를 캐시에 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 객체는 다음과 같은 과정을 거쳐 생성됩니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Cacheable 어노테이션이 적용된 메서드가 호출됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring AOP가 이를 감싸는 프록시 객체를 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 객체는 메서드 호출 전에 캐시에 저장된 결과를 확인하고, 저장된 결과가 있는 경우 캐시된 결과를 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장된 결과가 없는 경우 메서드를 실행하고, 실행 결과를 캐시에 저장한 후 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 프록시 패턴을 통해 개발자는 @Cacheable 어노테이션만으로 간편하게 캐싱 기능을 활성화할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&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 data-ke-size=&quot;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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzTZko/btsHHbfbKlv/kvsImaZDR2kptO5KUXPXE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzTZko/btsHHbfbKlv/kvsImaZDR2kptO5KUXPXE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzTZko/btsHHbfbKlv/kvsImaZDR2kptO5KUXPXE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzTZko%2FbtsHHbfbKlv%2FkvsImaZDR2kptO5KUXPXE1%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;537&quot; height=&quot;98&quot; data-origin-width=&quot;537&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbbFF8/btsHG9hm7aG/RFBKBktOGfDvbFqHTCJxP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbbFF8/btsHG9hm7aG/RFBKBktOGfDvbFqHTCJxP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbbFF8/btsHG9hm7aG/RFBKBktOGfDvbFqHTCJxP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbbFF8%2FbtsHG9hm7aG%2FRFBKBktOGfDvbFqHTCJxP1%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;1091&quot; height=&quot;96&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;96&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: #000000;&quot;&gt;[&quot;java.util.ArrayList&quot;,[{&quot;@class&quot;:&quot;co&lt;a style=&quot;color: #000000;&quot; href=&quot;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&lt;/a&gt;&quot;,&quot;productId&quot;:12,&quot;name&quot;:&quot;도시락&quot;,&quot;price&quot;:3000,&quot;description&quot;:&quot;고기도시락&quot;,&quot;inventory&quot;:10,&quot;productImages&quot;:[&quot;java.util.ArrayList&quot;,[]],&quot;productType&quot;:&quot;SNACK&quot;},{&quot;@class&quot;:&quot;co&lt;a style=&quot;color: #000000;&quot; href=&quot;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&lt;/a&gt;&quot;,&quot;productId&quot;:11,&quot;name&quot;:&quot;주먹밥&quot;,&quot;price&quot;:2000,&quot;description&quot;:&quot;먹기편한&amp;nbsp;주먹밥&quot;,&quot;inventory&quot;:10,&quot;productImages&quot;:[&quot;java.util.ArrayList&quot;,[]],&quot;productType&quot;:&quot;SNACK&quot;},{&quot;@class&quot;:&quot;co&lt;a style=&quot;color: #000000;&quot; href=&quot;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO&lt;/a&gt;&quot;,&quot;productId&quot;:10,&quot;name&quot;:&quot;아메리카노&quot;,&quot;price&quot;:1500,&quot;description&quot;:&quot;시원한 아메리카노&quot;,&quot;inventory&quot;:3,&quot;productImages&quot;:[&quot;java.util.ArrayList&quot;,[]],&quot;productType&quot;:&quot;BEVERAGE&quot;},&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.....&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 해당 상품 조회 api를 실행한 후, Redis를 확인해 본다면 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 결괏값들이 redis에 저장되어 있는 것을 확인할 수 있습니다.&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;h3 id=&quot;cacheable&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Cacheable&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드에 붙여서 사용할 것을 권장하고, 캐시를 저장 또는 조회할 때 사용 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드에서 조회할 캐시 데이터가 캐시 서버에 있는지 확인하고, 있으면 반환하고 없으면 새로 저장한 후 반환&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;cacheput&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@CachePut&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메서드에 붙여서 사용할 것을 권장하고, 캐시를 저장 또는 수정하는 목적으로만 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 즉, 메서드에서 조회하고자 하는 데이터는 DB에서 직접 꺼내서 전달하고, 그 결과를 캐시 서버에 반영하기만 함&lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;cacheevict&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@CacheEvict&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캐시 제거를 위해서 사용합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역시 메서드에 붙여서 사용하는 것이 바람직하고, 삭제 기능을 갖고 있는 메서드에 붙여서 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/150</guid>
      <comments>https://yusang.tistory.com/150#entry150comment</comments>
      <pubDate>Thu, 30 May 2024 15:24:47 +0900</pubDate>
    </item>
    <item>
      <title>Redis를 이용해 JWT 활용하기 (Refresh Token 저장 시 Redis활용)</title>
      <link>https://yusang.tistory.com/149</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DbVDZ/btsNHwi0ycD/H17QgMNVqiPkiBHfT3WLFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DbVDZ/btsNHwi0ycD/H17QgMNVqiPkiBHfT3WLFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DbVDZ/btsNHwi0ycD/H17QgMNVqiPkiBHfT3WLFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbVDZ%2FbtsNHwi0ycD%2FH17QgMNVqiPkiBHfT3WLFk%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;400&quot; height=&quot;267&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;test-redis:
  container_name: test-redis
  image: redis:6
  hostname: redis
  command: redis-server --port 6379
  ports:
    - &quot;6379:6379&quot;
  volumes:
    - ./redis/data:/data
  environment:
    - TZ=Asia/Seoul
  labels:
    - &quot;name=redis&quot;
    - &quot;mode=standalone&quot;
  restart: always&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;Docker 위에서 Redis를 올려놓고 사용할 것이기 때문에 docker-compose.yml 파일 수정&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  config:
    activate:
      on-profile: dev

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3307/test?characterEncoding=UTF-8
    username: root
    password: root

  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.MySQLDialect

  data:
    redis:
      host: localhost
      port: 6379&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;application.yml 파일에 Redis 설정 추가&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//redis config 설정 클래스들
@Configuration
public class RedisConfig {

    @Bean
    LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate() {
        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        template.setConnectionFactory(connectionFactory());
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(10L)); // 캐시의 TTL 설정 (10분)

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(factory)
                .cacheDefaults(cacheConfig)
                .build();
    }

    //Redis는 LocalDateTime과 같은 일부 타입을 직렬화할 수 없기 때문에
    //Redis의 직렬화 도구인 GenericJackson2JsonRedisSerializer를 구성하여 Java 8의 시간 관련 타입을 처리할 수 있도록 함.
    @Bean
    public RedisSerializer&amp;lt;Object&amp;gt; redisSerializer() {
        return new GenericJackson2JsonRedisSerializer(new ObjectMapper().registerModule(new JavaTimeModule()));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;Redis config 설정&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciHkln/btsHIft04nv/V4KnRaW55oOUCMBu4pDTb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciHkln/btsHIft04nv/V4KnRaW55oOUCMBu4pDTb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciHkln/btsHIft04nv/V4KnRaW55oOUCMBu4pDTb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciHkln%2FbtsHIft04nv%2FV4KnRaW55oOUCMBu4pDTb0%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;680&quot; height=&quot;373&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #191919; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트가 로그인을 성공하면, 서버는 Access Token과 Refresh Token을 함께 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이후 클라이언트는 인가가 필요한 요청에 Access Token을 header에 담아서 요청하게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시간이 조금 흘러 Access Token이 만료되었다면, 클라이언트는 Refresh Token을 서버로 전달하여 새로운 Access Token을 발급받습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 Refresh Token 토큰을 DB에 저장하지 않고 Redis에 저장하는 방식을 구현해보고자 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 했을 때의 장점은&amp;nbsp;Redis는 메모리 내 데이터 구조 저장소로서 매우 빠른 응답 시간을 제공하는데, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇기에 인증 서버에서 자주 액세스하는 데이터를 Redis에 저장하면 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 액세스보다 더 빠른 응답 시간을 얻을 수 있습니다.&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;span style=&quot;color: #000000;&quot;&gt;Refresh Token은 인증 서버에 자주 요청되는 데이터 중 하나이므로, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 Redis에 저장함으로써 전반적인 성능을 향상할 수 있습니다.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

//Redis는 LocalDateTime과 같은 일부 타입을 직렬화할 수 없기 때문에
//Redis의 직렬화 도구인 GenericJackson2JsonRedisSerializer를 구성하여 Java 8의 시간 관련 타입을 처리할 수 있도록 함.
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Redis 관련 gradle 추가&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@RedisHash(value = &quot;refreshToken&quot;, timeToLive = 60 * 60 * 2)
//titmeToLive -&amp;gt; 만료시간 설정 초 단위
//timeToLive의 시간이 지나면 redis에 저장된 RefreshToken이 만료 됨

public class RefreshToken {

    //java.persistence.id가 아닌 opg.springframework.data.annotation.Id 를 import해야 함
    //Refresh Token은 Redis에 저장하기 때문에 JPA 의존성이 필요하지 않음. persistence로 하게되면 에러 발생
    @Id
    private String refreshToken;
    private Long userId;

    @Builder
    public RefreshToken(String refreshToken, Long userId) {
        this.refreshToken = refreshToken;
        this.userId = userId;
    }

    //역직렬화를 위한 기본 생성자
    public RefreshToken() {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;RefreshToken class 생성&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;span style=&quot;color: #000000;&quot;&gt;이때 java.persistence.id가 아닌 opg.springframework.data.annotation.Id 를 import 해야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 Refresh Token은 DB에 저장하는 것이 아닌 Redis에 저장하기 때문에 JPA 의존성이 필요하지 않습니다. -&amp;gt; persistence로 하게 되면 에러가 발생합니다.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;public interface RefreshTokenRepository extends CrudRepository&amp;lt;RefreshToken, String&amp;gt; {

    @Override
    Optional&amp;lt;RefreshToken&amp;gt; findById(String s);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;RefreshToken CrudRepository 생성&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public String createRefreshToken(User user, RefreshTokenRepository refreshTokenRepository) {
    String refreshToken = UUID.randomUUID().toString();
    RefreshToken redisRefreshToken = RefreshToken.builder().
            refreshToken(refreshToken).
            userId(user.getId()).
            build();

    refreshTokenRepository.save(redisRefreshToken);

    return refreshToken;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;jwtProvider에 RefreshToken을 생성하는 함수 createRefreshToken을 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 Refresh Token은 UUID을 통해서 생성&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
protected void successfulAuthentication(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain chain,
        Authentication authResult
) throws IOException {
    User user = ((PrincipalDetails) authResult.getPrincipal()).getUser();
    String token = jwtProvider.createToken(user);
    String refreshToken = jwtProvider.createRefreshToken(user, refreshTokenRepository);

    LoginResponseDTO loginResponseDto = LoginResponseDTO.fromEntity(user, token, refreshToken);
    ResponseDTO&amp;lt;LoginResponseDTO&amp;gt; loginResponse = ResponseDTO.okWithData(loginResponseDto);

    sendJsonResponse(response, loginResponse, HttpStatus.OK);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;로그인 성공 시 Access Token과 Refresh Token을 생성하여 보낼 수 있도록 코드 작성&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI27kt/btsHHJJarvA/yv4UqKdaQHfRIotlXMmP3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI27kt/btsHHJJarvA/yv4UqKdaQHfRIotlXMmP3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI27kt/btsHHJJarvA/yv4UqKdaQHfRIotlXMmP3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI27kt%2FbtsHHJJarvA%2Fyv4UqKdaQHfRIotlXMmP3k%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;786&quot; height=&quot;316&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DNWpy/btsHIOipImb/fmqgpkEBSmETem2TXZf601/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DNWpy/btsHIOipImb/fmqgpkEBSmETem2TXZf601/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DNWpy/btsHIOipImb/fmqgpkEBSmETem2TXZf601/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDNWpy%2FbtsHIOipImb%2FfmqgpkEBSmETem2TXZf601%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;444&quot; height=&quot;156&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;156&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;span style=&quot;color: #000000;&quot;&gt;이후 확인을 하면 로그인 시 Access Token과 Refresh Token을 모두 발급하고 successfulAuthentication에서 작성한 대로 Refresh Token은 Redis에 저장되고 있습니다.&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;span style=&quot;color: #000000;&quot;&gt;Refresh Token의 만료시간은 Refresh Token 클래스의 TTL 시간이고 이 시간이 지나게 되면 redis에 저장된 값이 사라지기 때문에 Refresh Token이 만료되게 됩니다.&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;span style=&quot;color: #000000;&quot;&gt;클라이언트는 로그인 성공 시 짧은 만료시간을 가진 Access Token과 약간 긴 만료시간을 가진 Refresh Token을 발급받게 되는데, 시간이 흘러&amp;nbsp; Access Token이 만료된다면 서버로 Refresh Token을 보내서 Access Token을 재발급받을 수 있습니다.&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;span style=&quot;color: #000000;&quot;&gt;Access Token이 짧은 만료시간을 갖게 되는 건 Access Token이 탈취될 수 있기 때문에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰의 만료시간을 짧게 하는 보안성의 이점이 있다고 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 이제 redis에 Refresh Token이 존재한다면 서버에서 올바른 Refresh Token을 받았을 때, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 유저의 Access Token을 재 발급하는 코드를 작성해야 합니다.&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@RestController
public class TokenController {

    private final TokenService tokenService;

    @PostMapping(&quot;/access-token&quot;)
    public ResponseEntity&amp;lt;ResponseDTO&amp;lt;GetNewAccessTokenResponseDTO&amp;gt;&amp;gt;
    getNewAccessToken(@RequestBody RefreshToken refreshToken) {

        GetNewAccessTokenResponseDTO getNewAccessTokenResponseDTO =
                tokenService.generateAccessToken(refreshToken);

        ResponseDTO&amp;lt;GetNewAccessTokenResponseDTO&amp;gt; response
                = ResponseDTO.okWithData(getNewAccessTokenResponseDTO);

        return ResponseEntity
                .status(response.getCode())
                .body(response);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;Token controller&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
@RequiredArgsConstructor
@Transactional
@Service
public class TokenService {

    private final RefreshTokenRepository refreshTokenRepository;
    private final UserRepository userRepository;
    private final JwtProvider jwtProvider;

    public GetNewAccessTokenResponseDTO generateAccessToken(final RefreshToken refreshToken) {

        //redis의 refresh token의 유효기간이 지나지 않았는지
        RefreshToken refreshTokenCheck = refreshTokenRepository.findById(refreshToken.getRefreshToken())
                .orElseThrow(ExpiredRefreshTokenException::new);

        User user = userRepository.findById(refreshTokenCheck.getUserId())
                .orElseThrow(UserNotFoundException::new);

        return GetNewAccessTokenResponseDTO.fromEntity(jwtProvider.createToken(user));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TokenService&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;span style=&quot;color: #000000;&quot;&gt;Redis에 Refresh Token이 존재하는지 체크하고, 존재한다면 저장된 userId를 기반으로 jwt토큰을 재발급합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record GetNewAccessTokenResponseDTO(
        String token
) {

    public static GetNewAccessTokenResponseDTO fromEntity(String token) {
        return new GetNewAccessTokenResponseDTO(
                token
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;GetNewTokenResponseDTO&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6pILD/btsHHgncESl/tEoosJoXVfoGVtO9rrhZX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6pILD/btsHHgncESl/tEoosJoXVfoGVtO9rrhZX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6pILD/btsHHgncESl/tEoosJoXVfoGVtO9rrhZX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6pILD%2FbtsHHgncESl%2FtEoosJoXVfoGVtO9rrhZX0%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;504&quot; height=&quot;183&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EMl6w/btsHI1IBvFW/LDcOIKupIWHm1UzoZUY26k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EMl6w/btsHI1IBvFW/LDcOIKupIWHm1UzoZUY26k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EMl6w/btsHI1IBvFW/LDcOIKupIWHm1UzoZUY26k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEMl6w%2FbtsHI1IBvFW%2FLDcOIKupIWHm1UzoZUY26k%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;818&quot; height=&quot;302&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;302&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;&lt;span style=&quot;color: #000000;&quot;&gt;POST&amp;nbsp; &amp;nbsp; /access-token&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 통해서 Access Token을 성공적으로 재발급받을 수 있습니다.&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>access token</category>
      <category>Redis</category>
      <category>refresh token</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/149</guid>
      <comments>https://yusang.tistory.com/149#entry149comment</comments>
      <pubDate>Thu, 30 May 2024 14:51:23 +0900</pubDate>
    </item>
    <item>
      <title>야놀자테크스쿨 미니프로젝트 정리 및 회고</title>
      <link>https://yusang.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b73F6X/btsCuPhqA5J/W4hCZ290tYuzlIcyMTwXRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b73F6X/btsCuPhqA5J/W4hCZ290tYuzlIcyMTwXRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b73F6X/btsCuPhqA5J/W4hCZ290tYuzlIcyMTwXRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb73F6X%2FbtsCuPhqA5J%2FW4hCZ290tYuzlIcyMTwXRK%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;800&quot; height=&quot;420&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckmpwy/btsCzKSBerY/JKwyTgGEJjbJchTgtl2Fw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckmpwy/btsCzKSBerY/JKwyTgGEJjbJchTgtl2Fw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckmpwy/btsCzKSBerY/JKwyTgGEJjbJchTgtl2Fw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckmpwy%2FbtsCzKSBerY%2FJKwyTgGEJjbJchTgtl2Fw1%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;328&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패키지 구조&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1703238240160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; com.example.yanolja  
 ├── domain  
 │   ├── user  
 │   ├── accommodation  
 │   ├── review  
 │   ├── reservation  
 │   ├── accommodationLikes  
 │   ├── basket  
 │   ├── wishlist  
 │   ...  
 └── global  
      ├── springsecurity  
      ├── entity  
      ├── config  
      ├── exception  
      ├── jwt  
      └── util&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ERD&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1923&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pnnBC/btsCxCagEgo/9oFJ1N6iEAeQK47MIT5no1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pnnBC/btsCxCagEgo/9oFJ1N6iEAeQK47MIT5no1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pnnBC/btsCxCagEgo/9oFJ1N6iEAeQK47MIT5no1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpnnBC%2FbtsCxCagEgo%2F9oFJ1N6iEAeQK47MIT5no1%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;1280&quot; height=&quot;1923&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1923&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐ FlowChart&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0cOap/btsCuL0mRCD/j52JfLi7L29p1Z2P7ROAok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0cOap/btsCuL0mRCD/j52JfLi7L29p1Z2P7ROAok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0cOap/btsCuL0mRCD/j52JfLi7L29p1Z2P7ROAok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0cOap%2FbtsCuL0mRCD%2Fj52JfLi7L29p1Z2P7ROAok%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;1280&quot; height=&quot;740&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐Project Architecture&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DO10K/btsCyaLi2KO/77Gl4DKdWBePZZRdkgfVI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DO10K/btsCyaLi2KO/77Gl4DKdWBePZZRdkgfVI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DO10K/btsCyaLi2KO/77Gl4DKdWBePZZRdkgfVI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDO10K%2FbtsCyaLi2KO%2F77Gl4DKdWBePZZRdkgfVI1%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;1280&quot; height=&quot;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐담당 역할&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;spring security&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 배포&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장바구니, 결제&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 예약조회&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메인페이지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그인(OAuth2)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Querydsl&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐ &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 구현 내용과 관련하여 작성한 블로그 글정리&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 진행하면서 생겼었던 문제들을 그때그때 기록하고 정리해 놨었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 글들은 해당 프로젝트를 진행하면서 사용했었던 기술들을 기록하고자 블로그에 작성했었던 글들입니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703238663365&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;Spring, Docker에서 MySQL 데이터베이스 컨테이너 설정하기&quot; data-og-description=&quot;docker-compose.yml 설정 파일 version: '3' services: travel-db: container_name: test build: context: ./database dockerfile: Dockerfile ports: - &amp;quot;3307:3306&amp;quot; restart: always healthcheck: test: [ &amp;quot;CMD&amp;quot;, &amp;quot;mysqladmin&amp;quot; ,&amp;quot;ping&amp;quot;, &amp;quot;-h&amp;quot;, &amp;quot;localhost&amp;quot; ] timeout: 30&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/139&quot; data-og-url=&quot;https://yusang.tistory.com/139&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cxOUAo/hyUPyVhrXn/751ad4BYg65GKlT3xlkaRk/img.png?width=279&amp;amp;height=131&amp;amp;face=0_0_279_131,https://scrap.kakaocdn.net/dn/7g6vg/hyUPDhYVk5/YxcpREQLzxL2Vy8nRm4JGk/img.png?width=279&amp;amp;height=131&amp;amp;face=0_0_279_131,https://scrap.kakaocdn.net/dn/bID6Cr/hyUPCpSiWI/wTrhm3a6Vh2hORGQZ14Aok/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/139&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/139&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cxOUAo/hyUPyVhrXn/751ad4BYg65GKlT3xlkaRk/img.png?width=279&amp;amp;height=131&amp;amp;face=0_0_279_131,https://scrap.kakaocdn.net/dn/7g6vg/hyUPDhYVk5/YxcpREQLzxL2Vy8nRm4JGk/img.png?width=279&amp;amp;height=131&amp;amp;face=0_0_279_131,https://scrap.kakaocdn.net/dn/bID6Cr/hyUPCpSiWI/wTrhm3a6Vh2hORGQZ14Aok/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000');&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;Spring, Docker에서 MySQL 데이터베이스 컨테이너 설정하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose.yml 설정 파일 version: '3' services: travel-db: container_name: test build: context: ./database dockerfile: Dockerfile ports: - &quot;3307:3306&quot; restart: always healthcheck: test: [ &quot;CMD&quot;, &quot;mysqladmin&quot; ,&quot;ping&quot;, &quot;-h&quot;, &quot;localhost&quot; ] timeout: 30&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703238515518&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;GCP 에서 springboot 프로젝트 docker로 배포하기&quot; data-og-description=&quot;GCP로 이동합니다. Google 클라우드 플랫폼 로그인 Google 클라우드 플랫폼으로 이동 accounts.google.com vm인스턴스 -&amp;gt; 인스턴스 생성 아래와 같이 설정했습니다. 가장 저렴한 비용으로 할 수 있습니다. OS&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/144&quot; data-og-url=&quot;https://yusang.tistory.com/144&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwWkWg/hyUPCciP44/SL9K2Wo4f2bIllJchqb9P0/img.png?width=694&amp;amp;height=604&amp;amp;face=0_0_694_604,https://scrap.kakaocdn.net/dn/ddjxEV/hyUPGTlcjv/8QGITYLmuj94j6UKnMLVy1/img.png?width=694&amp;amp;height=604&amp;amp;face=0_0_694_604,https://scrap.kakaocdn.net/dn/bUEWk9/hyUPGeH7IL/mWY6jlFKNYlpDu93xRrP8K/img.png?width=1346&amp;amp;height=2214&amp;amp;face=0_0_1346_2214&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/144&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwWkWg/hyUPCciP44/SL9K2Wo4f2bIllJchqb9P0/img.png?width=694&amp;amp;height=604&amp;amp;face=0_0_694_604,https://scrap.kakaocdn.net/dn/ddjxEV/hyUPGTlcjv/8QGITYLmuj94j6UKnMLVy1/img.png?width=694&amp;amp;height=604&amp;amp;face=0_0_694_604,https://scrap.kakaocdn.net/dn/bUEWk9/hyUPGeH7IL/mWY6jlFKNYlpDu93xRrP8K/img.png?width=1346&amp;amp;height=2214&amp;amp;face=0_0_1346_2214');&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;GCP 에서 springboot 프로젝트 docker로 배포하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GCP로 이동합니다. Google 클라우드 플랫폼 로그인 Google 클라우드 플랫폼으로 이동 accounts.google.com vm인스턴스 -&amp;gt; 인스턴스 생성 아래와 같이 설정했습니다. 가장 저렴한 비용으로 할 수 있습니다. OS&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703238522910&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;springboot certbot으로 ssl인증서 받아서 https로 배포하기&quot; data-og-description=&quot;ssl 인증서를 받기 위해서는 등록할 도메인이 필요합니다. 그래서 저는 가비아에서 1900짜리 제일 싼 도메인을 1년 구매했습니다. 그리고 배포한 인스턴스의 주소를 등록해 주었습니다. https://certb&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/145&quot; data-og-url=&quot;https://yusang.tistory.com/145&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHfEcm/hyUPK9e1Xi/Xi8qbD1xwakgMVxKmbih21/img.png?width=224&amp;amp;height=224&amp;amp;face=0_0_224_224,https://scrap.kakaocdn.net/dn/cS9OOc/hyUPLmL1PE/fpBDH4LXTcN5OQCQrSoVu0/img.png?width=224&amp;amp;height=224&amp;amp;face=0_0_224_224,https://scrap.kakaocdn.net/dn/WY6hO/hyUPIQ78C8/WMfvkFrnrkR3Jk7M7YxT0K/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/145&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/145&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHfEcm/hyUPK9e1Xi/Xi8qbD1xwakgMVxKmbih21/img.png?width=224&amp;amp;height=224&amp;amp;face=0_0_224_224,https://scrap.kakaocdn.net/dn/cS9OOc/hyUPLmL1PE/fpBDH4LXTcN5OQCQrSoVu0/img.png?width=224&amp;amp;height=224&amp;amp;face=0_0_224_224,https://scrap.kakaocdn.net/dn/WY6hO/hyUPIQ78C8/WMfvkFrnrkR3Jk7M7YxT0K/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000');&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;springboot certbot으로 ssl인증서 받아서 https로 배포하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ssl 인증서를 받기 위해서는 등록할 도메인이 필요합니다. 그래서 저는 가비아에서 1900짜리 제일 싼 도메인을 1년 구매했습니다. 그리고 배포한 인스턴스의 주소를 등록해 주었습니다. https://certb&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703238528606&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;spring jpa에 Querydsl 적용하기 [spring 3.1.5, java 17]&quot; data-og-description=&quot;개발하고 있는 프로젝트에 Querydsl을 적용해 보려고 합니다. Querydsl의 장점 유연성 및 강력한 검색 기능 : Query DSL을 사용하면 복잡한 검색 조건을 표현할 수 있습니다. 진행하고 있는 프로젝트 메&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/146&quot; data-og-url=&quot;https://yusang.tistory.com/146&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bXnm9l/hyUPEgSJtL/U413tsMTa4UbyK0S4AvdY1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bNL06Z/hyUPA6DLeX/1ykHW2tkVxrChstSMDDXr0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/iF0oj/hyUPzNpZwi/N5QEhN0AfeRhhe2gPuNusK/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/146&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/146&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bXnm9l/hyUPEgSJtL/U413tsMTa4UbyK0S4AvdY1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/bNL06Z/hyUPA6DLeX/1ykHW2tkVxrChstSMDDXr0/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/iF0oj/hyUPzNpZwi/N5QEhN0AfeRhhe2gPuNusK/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000');&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;spring jpa에 Querydsl 적용하기 [spring 3.1.5, java 17]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개발하고 있는 프로젝트에 Querydsl을 적용해 보려고 합니다. Querydsl의 장점 유연성 및 강력한 검색 기능 : Query DSL을 사용하면 복잡한 검색 조건을 표현할 수 있습니다. 진행하고 있는 프로젝트 메&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703238532408&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;Spring, OAuth2  + JWT 를 활용하여 소셜 로그인 구현하기 1편 (구글 및 네이버) [Spring 3.1.5, java 17]&quot; data-og-description=&quot;글 작성하기에 앞서 ppt로 정리한 전체적인 동작 흐름입니다. 현재 제 프로젝트에서는 아래와 같이 OAuth를 통한 구글, 네이버 로그인을 구현했고 그 과정을 정리하려고 글을 작성하게 되었습니다&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/140&quot; data-og-url=&quot;https://yusang.tistory.com/140&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cnWm0P/hyUPBdqd2Z/pw4hDQGyti30LmYc3G06jk/img.png?width=800&amp;amp;height=600&amp;amp;face=497_76_532_113,https://scrap.kakaocdn.net/dn/bhBUeR/hyUPL1o7zJ/qfCuUjwVm770cRQRoBR1Ek/img.png?width=800&amp;amp;height=600&amp;amp;face=497_76_532_113,https://scrap.kakaocdn.net/dn/lwmS9/hyUPDCg6NO/o0j4UpDOOUKcKK6eMDv2Nk/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/140&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cnWm0P/hyUPBdqd2Z/pw4hDQGyti30LmYc3G06jk/img.png?width=800&amp;amp;height=600&amp;amp;face=497_76_532_113,https://scrap.kakaocdn.net/dn/bhBUeR/hyUPL1o7zJ/qfCuUjwVm770cRQRoBR1Ek/img.png?width=800&amp;amp;height=600&amp;amp;face=497_76_532_113,https://scrap.kakaocdn.net/dn/lwmS9/hyUPDCg6NO/o0j4UpDOOUKcKK6eMDv2Nk/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000');&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;Spring, OAuth2 + JWT 를 활용하여 소셜 로그인 구현하기 1편 (구글 및 네이버) [Spring 3.1.5, java 17]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글 작성하기에 앞서 ppt로 정리한 전체적인 동작 흐름입니다. 현재 제 프로젝트에서는 아래와 같이 OAuth를 통한 구글, 네이버 로그인을 구현했고 그 과정을 정리하려고 글을 작성하게 되었습니다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1703238535581&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;Spring, OAuth2  + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]&quot; data-og-description=&quot;이 글은 전글과 이어집니다.! https://yusang.tistory.com/140 Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17] http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google &quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/147&quot; data-og-url=&quot;https://yusang.tistory.com/147&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qvHuN/hyUPy12hPG/KPZi64W5JVepxSqSahL2wk/img.png?width=771&amp;amp;height=640&amp;amp;face=599_26_636_65,https://scrap.kakaocdn.net/dn/0OhqJ/hyUPEnG2Un/3OdmzO1aU4YmtDYYAxmv11/img.png?width=771&amp;amp;height=640&amp;amp;face=599_26_636_65,https://scrap.kakaocdn.net/dn/bnDlrO/hyUPAevb0q/dlrFrv8UPQTEZmDcGzM2A1/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/147&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qvHuN/hyUPy12hPG/KPZi64W5JVepxSqSahL2wk/img.png?width=771&amp;amp;height=640&amp;amp;face=599_26_636_65,https://scrap.kakaocdn.net/dn/0OhqJ/hyUPEnG2Un/3OdmzO1aU4YmtDYYAxmv11/img.png?width=771&amp;amp;height=640&amp;amp;face=599_26_636_65,https://scrap.kakaocdn.net/dn/bnDlrO/hyUPAevb0q/dlrFrv8UPQTEZmDcGzM2A1/img.jpg?width=972&amp;amp;height=2000&amp;amp;face=0_0_972_2000');&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;Spring, OAuth2 + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 전글과 이어집니다.! https://yusang.tistory.com/140 Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17] http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시연영상&lt;/span&gt;&lt;/h4&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Fdb-5SaWlCE&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cnag8y/hyUPL8aP12/mUBR1ii6XfAqXrc9AVHAlk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Fdb-5SaWlCE&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포페이지&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://dashing-tiramisu-cbdade.netlify.app&quot;&gt;https://dashing-tiramisu-cbdade.netlify.app&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⭐&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;회고&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;❗&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;짧은 기간 동안 빠르게 기능구현을 위주로 개발을 하려고 하다 보니 다시 돌아와서 코드를 보았을 때 로직이 복잡하고 좋지 않은 코드들이 많이 보였던 거 같습니다. 다음에 비즈니스 로직을 구현할 때 있어서 좀 더 생각을 하고 좋은 설계를 바탕으로 코드를 짜면 더 좋을 거 같습니다. 이번경험을 바탕으로 코드작성에 있어서 더 생각을 해 볼 수 있을 거 같습니다. 좋은 경험이었습니다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;❗ 도커 위에서 작업을 하고, 도커이미지를 통해 배포를 해보았는데 이 경험이 매우 좋았습니다. 도커이미지로 배포를 했기 때문에 배포환경에 구애받지 않고 필요한 프로그램들 설치 없이 언제 어디서든 똑같은 배포환경을 유지할 수 있다는 점이 매우 편리했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이 과정은 기존 jar로 배포하던 것보다 훨씬 간편했던 거 같고 유용한 기술이었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Docker에 대해 좀 더 공부해 보고 더 다양한 기술들을 써보고 싶은 계기가 되었던 거 같습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;❗ querydsl을 활용해 메인페이지 JPQL을 개선했으며 OAuth2 client를 활용해 소셜로그인을 구현해 본 것, &lt;br /&gt;redis를 활용한 캐시 DB를 추후 리팩토링 시간에 추가적으로 적용해 볼 수 있어서 좋았던 것 같습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;❗ 처음 써보는 새로운 기술들을 적용하면서 여러 가지 문제점이 있었지만 팀원들과 같이 의논하고 같이 개발하면서 문제를 해결했었던 것 같습니다. 덕분에 실력이 많이 늘어서 좋았습니다. &lt;/span&gt;&lt;/p&gt;</description>
      <category>기타/패캠X야놀자 테크스쿨</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/148</guid>
      <comments>https://yusang.tistory.com/148#entry148comment</comments>
      <pubDate>Fri, 22 Dec 2023 18:57:24 +0900</pubDate>
    </item>
    <item>
      <title>Spring, OAuth2  + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]</title>
      <link>https://yusang.tistory.com/147</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUOfdA/btsBBl1svoB/DoJ9ptf4rDSX9olHKExSTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUOfdA/btsBBl1svoB/DoJ9ptf4rDSX9olHKExSTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUOfdA/btsBBl1svoB/DoJ9ptf4rDSX9olHKExSTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUOfdA%2FbtsBBl1svoB%2FDoJ9ptf4rDSX9olHKExSTk%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;280&quot; height=&quot;184&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;184&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 글은 전글과 이어집니다.!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yusang.tistory.com/140&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1702297815573&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;Spring, OAuth2  + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17]&quot; data-og-description=&quot;http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google 클라우드 플랫폼으로 이동 accounts.google.com 프로젝트 만들기 -&amp;gt; API 및 서비스 -&amp;gt; 사용자 인증정보 -&amp;gt; 사용자 인증 정보 만들기 -&amp;gt; OAu&quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/140&quot; data-og-url=&quot;https://yusang.tistory.com/140&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dhkoxI/hyULYSSnDw/M506IMSHFg4dfyvgKeWPvk/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/rhlSi/hyUIr3v070/cbMEeZIytxdcwvJ4l0hDK0/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/dKDz4X/hyUIGfi1OB/fDN8RXT9vJ0zpkVMvZtkf0/img.png?width=1517&amp;amp;height=576&amp;amp;face=0_0_1517_576&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/140&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/140&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dhkoxI/hyULYSSnDw/M506IMSHFg4dfyvgKeWPvk/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/rhlSi/hyUIr3v070/cbMEeZIytxdcwvJ4l0hDK0/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/dKDz4X/hyUIGfi1OB/fDN8RXT9vJ0zpkVMvZtkf0/img.png?width=1517&amp;amp;height=576&amp;amp;face=0_0_1517_576');&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;Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google 클라우드 플랫폼으로 이동 accounts.google.com 프로젝트 만들기 -&amp;gt; API 및 서비스 -&amp;gt; 사용자 인증정보 -&amp;gt; 사용자 인증 정보 만들기 -&amp;gt; OAu&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;과정 설명에 앞서서 전체적인 동작방식을 ppt로 만들어 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;전체적인 동작 방식을 그림으로 정리하자면 아래와 같습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_OAuth2 동작방식.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgMqGj/btsBQxVdppX/KgQqAkackKRXMYd9Vbp0nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgMqGj/btsBQxVdppX/KgQqAkackKRXMYd9Vbp0nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgMqGj/btsBQxVdppX/KgQqAkackKRXMYd9Vbp0nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgMqGj%2FbtsBQxVdppX%2FKgQqAkackKRXMYd9Vbp0nk%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;776&quot; height=&quot;656&quot; data-filename=&quot;edited_OAuth2 동작방식.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;656&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;&lt;span style=&quot;color: #000000;&quot;&gt;네이버 개발자 사이트에 접속합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 후 아래와 같이 애플리케이션 등록을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 네이버로그인 시 제공받을 정보를 선택합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 저는 ssl인증서를 받아서 등록했기에 https로 요청을 했습니다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;2077&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx9svB/btsBBmS2KCF/ETWoJSdgbkuwegWKRzcKn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx9svB/btsBBmS2KCF/ETWoJSdgbkuwegWKRzcKn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx9svB/btsBBmS2KCF/ETWoJSdgbkuwegWKRzcKn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx9svB%2FbtsBBmS2KCF%2FETWoJSdgbkuwegWKRzcKn0%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;1169&quot; height=&quot;2077&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;2077&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;span style=&quot;color: #000000;&quot;&gt;등록 후 발급받은 id와 key를 기억해야 합니다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;아래의 네이버 로그인 개발가이드 공식문서를 참고하여 yml 파일에 코드를 추가합시다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701936691823&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;네이버 로그인 개발가이드 - LOGIN&quot; data-og-description=&quot;네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디 &quot; data-og-host=&quot;developers.naver.com&quot; data-og-source-url=&quot;https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; data-og-url=&quot;https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qQZCN/hyUIyN8x5Q/OvC4k7GMhUYb5DeWKkaKdK/img.png?width=880&amp;amp;height=1376&amp;amp;face=0_0_880_1376,https://scrap.kakaocdn.net/dn/bt2jsR/hyUIE8DQnR/8mV3kRIMWkt7QyfFWKMBmK/img.png?width=880&amp;amp;height=619&amp;amp;face=0_0_880_619,https://scrap.kakaocdn.net/dn/YbT6r/hyUIzzuIiZ/Xved9w6XpFvl0KWG5oDkkk/img.png?width=880&amp;amp;height=591&amp;amp;face=0_0_880_591&quot;&gt;&lt;a href=&quot;https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.naver.com/docs/login/devguide/devguide.md#2-2-1-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qQZCN/hyUIyN8x5Q/OvC4k7GMhUYb5DeWKkaKdK/img.png?width=880&amp;amp;height=1376&amp;amp;face=0_0_880_1376,https://scrap.kakaocdn.net/dn/bt2jsR/hyUIE8DQnR/8mV3kRIMWkt7QyfFWKMBmK/img.png?width=880&amp;amp;height=619&amp;amp;face=0_0_880_619,https://scrap.kakaocdn.net/dn/YbT6r/hyUIzzuIiZ/Xved9w6XpFvl0KWG5oDkkk/img.png?width=880&amp;amp;height=591&amp;amp;face=0_0_880_591');&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;네이버 로그인 개발가이드 - LOGIN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.naver.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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmOCqc/btsBBPBiyk3/ZJYAkKTZLyT1DHB5POpnPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmOCqc/btsBBPBiyk3/ZJYAkKTZLyT1DHB5POpnPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmOCqc/btsBBPBiyk3/ZJYAkKTZLyT1DHB5POpnPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmOCqc%2FbtsBBPBiyk3%2FZJYAkKTZLyT1DHB5POpnPk%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;740&quot; height=&quot;246&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ezByLn/btsBCui6XyR/D3Xm7h67PjNhJcRuwpdet1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ezByLn/btsBCui6XyR/D3Xm7h67PjNhJcRuwpdet1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ezByLn/btsBCui6XyR/D3Xm7h67PjNhJcRuwpdet1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FezByLn%2FbtsBCui6XyR%2FD3Xm7h67PjNhJcRuwpdet1%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;751&quot; height=&quot;235&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;284&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;네이버 소셜 로그인은 OAuth2.0 공식 지원 대상이 아니기에 provider가 기본적으로 제공되고 있지 않습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;그렇기에 provider 설정이 필요하고, google로그인과 달리 직접 지정해야 하는 값들이 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전편에서 추가했었던 google 아래에 naver를 아래와 같이 추가해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 들여 쓰기에 유의해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IDE에서 인식해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;security:
  oauth2:
    client:
      registration:
        google:
          client-id: ${GOOGLE_ID}
          client-secret: ${GOOGLE_KEY}
          scope:
            - email
            - profile

        naver:
          client-id: key
          client-secret: value
          scope:
            - name
            - email
          client-name: Naver
          authorization-grant-type: authorization_code
          redirect-uri: https://localhost:8080/login/oauth2/code/naver


      provider:
        naver:
          authorization-uri: https://nid.naver.com/oauth2.0/authorize
          token-uri: https://nid.naver.com/oauth2.0/token
          user-info-uri: https://openapi.naver.com/v1/nid/me
          user-name-attribute: response&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;663&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GA9PG/btsBzJuFMOO/2z3kw1Aq2QnmpUXtpdaWNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GA9PG/btsBzJuFMOO/2z3kw1Aq2QnmpUXtpdaWNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GA9PG/btsBzJuFMOO/2z3kw1Aq2QnmpUXtpdaWNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGA9PG%2FbtsBzJuFMOO%2F2z3kw1Aq2QnmpUXtpdaWNk%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;613&quot; height=&quot;616&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;663&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;&lt;span style=&quot;color: #000000;&quot;&gt;구글 로그인때와 마찬가지로 아래에 엔드포인트로 보내면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네이버 로그인이 나오게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;http://localhost:8080/oauth2/authorization/naver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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: #000000;&quot;&gt;그 후 System.out으로 정보를 출력하면 아래와 같이 로그인정보가 출력됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701941376089&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;getAttributes: {resultcode=00, 
message=success, 
response={id=nsTFmLViERwRxCE_M03ep0uk6qXv148cUp9ppSQWpsk, 
email=개인정보, 
mobile=개인정보, 
mobile_e164=+개인정보,
name=이유상}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이때 로그인 페이지에서 로그인을 수행하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;승인하면 Spring Security가 제공한 콜백 엔드포인트로 다시 리다이렉트 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;여기서는 아까 설정 한&amp;nbsp; http://localhost:8080/login/oauth2/code/naver&lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;로 리다이렉트 됩니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포된 환경에서 OAuth 로그인을 하려고 한다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포된 도메인도 같이 네이버 로그인 애플리케이션을 등록할 때 같이 입력해야지 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포환경에서도 OAuth 로그인을 할 수 있습니다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;승인된 리디렉션 URI를 등록하지 않는다면&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;400 오류 redirect_uri_mismatch 에러가 발생하게 됩니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저번 포스팅에 google 소셜 로그인을 추가한 상황에서 naver 소셜 로그인을 추가하고자 했기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구글 로그인인지, naver로그인인지에 따라 상황을 분기하여야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 아래처럼 코드를 수정해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리는 userRequest.getClientRegistration().getRegistrationId()를 통해서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;google 로그인인지, naver 로그인인지 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 OAuth2UserInfo interface을 생성하여&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2.0 제공자들마다 응답해 주는 공통의 속성을 만들어 줍니다.&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;span style=&quot;color: #000000;&quot;&gt;그리고 그 후 이를 implement 한 NaverUserInfo, GoogleUserInfo을 각각 생성해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇게 되면 아래와 같이 상황에 따라 맞는 정보를 생성하고 처리를 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        //System.out.println(&quot;getClientRegistration: &quot; + userRequest.getClientRegistration());
        //System.out.println(&quot;getAccessToken: &quot; + userRequest.getAccessToken());
        //System.out.println(&quot;getAttributes: &quot; + super.loadUser(userRequest).getAttributes());

        OAuth2User oauth2User = super.loadUser(userRequest);

        OAuth2UserInfo oauth2Userinfo = null;
        String provider = userRequest.getClientRegistration().getRegistrationId(); //google kakao facebook...

        if(provider.equals(&quot;google&quot;)){
            oauth2Userinfo = new GoogleUserInfo(oauth2User.getAttributes());
        } else if(provider.equals(&quot;naver&quot;)){
            oauth2Userinfo = new NaverUserInfo((Map)oauth2User.getAttributes().get(&quot;response&quot;));
        }

        Optional&amp;lt;User&amp;gt; user = userRepository.findByEmailAndProvider(
            oauth2Userinfo.getEmail(),oauth2Userinfo.getProvider());

        //이미 소셜로그인을 한적이 있는지 없는지
        if (user.isEmpty()) {
            User newUser = User.builder()
                .email(oauth2Userinfo.getEmail())
                .username(oauth2Userinfo.getName())
                .password(&quot;OAuth2&quot;)  //Oauth2로 로그인을 해서 패스워드는 의미없음.
                .phonenumber(oauth2Userinfo.getPhoneNumber())
                .authority(&quot;ROLE_USER&quot;)
                .provider(provider)
                .build();

            userRepository.save(newUser);
            return new PrincipalDetails(newUser, oauth2User.getAttributes());
        } else {
            return new PrincipalDetails(user.get(), oauth2User.getAttributes());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public interface OAuth2UserInfo {
    String getProviderId();
    String getProvider();
    String getEmail();
    String getName();
    String getPhoneNumber();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class NaverUserInfo implements OAuth2UserInfo {

    private Map&amp;lt;String, Object&amp;gt; attributes;

    public NaverUserInfo(Map&amp;lt;String, Object&amp;gt; attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get(&quot;id&quot;);
    }

    @Override
    public String getName() {
        return (String) attributes.get(&quot;name&quot;);
    }

    @Override
    public String getPhoneNumber() {
        return (String) attributes.get(&quot;mobile&quot;);
    }

    @Override
    public String getEmail() {
        return (String) attributes.get(&quot;email&quot;);
    }

    @Override
    public String getProvider() {
        return &quot;naver&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class GoogleUserInfo implements OAuth2UserInfo{

    private Map&amp;lt;String, Object&amp;gt; attributes;

    public GoogleUserInfo(Map&amp;lt;String, Object&amp;gt; attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get(&quot;sub&quot;);
    }

    @Override
    public String getName() {
        return (String) attributes.get(&quot;name&quot;);
    }

    @Override
    public String getPhoneNumber() {
        return null;
    }

    @Override
    public String getEmail() {
        return (String) attributes.get(&quot;email&quot;);
    }

    @Override
    public String getProvider() {
        return &quot;google&quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;이제 아래 엔드포인트로 네이버 로그인을 요청해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;localhost:8080/oauth2/authorization/naver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7cYNQ/btsByppT8Ly/SyaJab2616F2TN6I0B3VX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7cYNQ/btsByppT8Ly/SyaJab2616F2TN6I0B3VX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7cYNQ/btsByppT8Ly/SyaJab2616F2TN6I0B3VX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7cYNQ%2FbtsByppT8Ly%2FSyaJab2616F2TN6I0B3VX1%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;550&quot; height=&quot;549&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;724&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;span style=&quot;color: #000000;&quot;&gt;로그인 성공 시 발급받은 JWT를 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0mv1Y/btsBzwoALGP/fp0usFk7txhTw0mNLwgUC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0mv1Y/btsBzwoALGP/fp0usFk7txhTw0mNLwgUC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0mv1Y/btsBzwoALGP/fp0usFk7txhTw0mNLwgUC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0mv1Y%2FbtsBzwoALGP%2Ffp0usFk7txhTw0mNLwgUC1%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;618&quot; height=&quot;110&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;110&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: #000000;&quot;&gt;JWT을 통한 사용자 인증도 잘되네요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wNpzW/btsBCwH8EQr/nFNeUz0rg0e2mkMQoRvSek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wNpzW/btsBCwH8EQr/nFNeUz0rg0e2mkMQoRvSek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wNpzW/btsBCwH8EQr/nFNeUz0rg0e2mkMQoRvSek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwNpzW%2FbtsBCwH8EQr%2FnFNeUz0rg0e2mkMQoRvSek%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;539&quot; height=&quot;548&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;548&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;&lt;span style=&quot;color: #000000;&quot;&gt;네이버 로그인을 서비스에서 제대로 사용하고자 한다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네이버 검수요청을 진행해서 받아야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그전까지는 멤버관리에서 등록한 아이디만 등록할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DMtzk/btsBvVbTb6K/PPZKlzRzkvPbU2MHpmg4eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DMtzk/btsBvVbTb6K/PPZKlzRzkvPbU2MHpmg4eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DMtzk/btsBvVbTb6K/PPZKlzRzkvPbU2MHpmg4eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDMtzk%2FbtsBvVbTb6K%2FPPZKlzRzkvPbU2MHpmg4eK%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;936&quot; height=&quot;187&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;187&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;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 현재 네이버 로그인 과정을 정확히 캡처하여 검수 요청을 했습니다.!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GMCjp/btsBF3GPxmz/7BiLb3yilgrE8DMlp471z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GMCjp/btsBF3GPxmz/7BiLb3yilgrE8DMlp471z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GMCjp/btsBF3GPxmz/7BiLb3yilgrE8DMlp471z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGMCjp%2FbtsBF3GPxmz%2F7BiLb3yilgrE8DMlp471z1%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;738&quot; height=&quot;549&quot; data-origin-width=&quot;914&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;&lt;b&gt;또한 주의해야 할 점이 로컬에서 테스트 후&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;배포 시에는 &lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;OAuth2로 네이버 로그인을 할 경우&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;redirect-uri를 배포 주소로 변경해야 합니다!&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfKLpi/btsBBNX4q5C/BtitQhZ8qketTN5iMLi170/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfKLpi/btsBBNX4q5C/BtitQhZ8qketTN5iMLi170/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfKLpi/btsBBNX4q5C/BtitQhZ8qketTN5iMLi170/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfKLpi%2FbtsBBNX4q5C%2FBtitQhZ8qketTN5iMLi170%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;665&quot; height=&quot;245&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;245&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;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이후 업데이트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2LoginSuccessHandler를 다음과 같이 변경하여 &lt;b&gt;프론트단으로 JWT토큰을 쿼리스트링에 담아&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리디렉트 되도록 하고 로그인 처리를 하도록 변경했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유는 이렇게 발급한 JWT토큰은 프론트단에서 추출하여 사용을 하지 못하기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702297944093&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

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

        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        String token = jwtProvider.createToken(principalDetails.getUser());
        String email = principalDetails.getEmail();
        String name = principalDetails.getUsername();

        //코드 내로 리디렉트 설정
        //String redirectUrl = &quot;/user/oauth-success?token=&quot;+token;

        //한국어 인코딩 설정
        String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8.toString());
        
        String redirectUrl = &quot;프론트도메인?token=&quot; + token
        +&quot;&amp;amp;email=&quot;+email+&quot;&amp;amp;name=&quot;+encodedName;
        
        getRedirectStrategy().sendRedirect(request, response, redirectUrl);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;callback url에 프론트 도메인을 추가하여 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트단으로 리디렉트 돼서 로그인을 처리할 것이기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oNkd9/btsBG6X0af1/7S7mRmwkS9afy9xpseHCzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oNkd9/btsBG6X0af1/7S7mRmwkS9afy9xpseHCzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oNkd9/btsBG6X0af1/7S7mRmwkS9afy9xpseHCzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoNkd9%2FbtsBG6X0af1%2F7S7mRmwkS9afy9xpseHCzk%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;540&quot; height=&quot;240&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;367&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;span style=&quot;color: #000000;&quot;&gt;로그인 화면에서 네이버로 시작하기를 클릭 후&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SJLCP/btsBFGscfna/kBICNbK5Y5HjJMwknB1OKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SJLCP/btsBFGscfna/kBICNbK5Y5HjJMwknB1OKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SJLCP/btsBFGscfna/kBICNbK5Y5HjJMwknB1OKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSJLCP%2FbtsBFGscfna%2FkBICNbK5Y5HjJMwknB1OKK%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;555&quot; height=&quot;460&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;622&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;span style=&quot;color: #000000; text-align: start;&quot;&gt;로그인 창이 뜨면 로그인을 하면&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvpCsk/btsBGukuTtI/xzKf5us1my0iXPNoJkGVD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvpCsk/btsBGukuTtI/xzKf5us1my0iXPNoJkGVD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvpCsk/btsBGukuTtI/xzKf5us1my0iXPNoJkGVD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvpCsk%2FbtsBGukuTtI%2FxzKf5us1my0iXPNoJkGVD0%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;448&quot; height=&quot;590&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;590&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;최초 로그인시 정보 제공을 동의하는지 나옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;동의하면 정보가 제공되어 DB에 정보가 저장되게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsIeI/btsBMWfZjxq/MvZRKCwK3dvKq7c75kerf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsIeI/btsBMWfZjxq/MvZRKCwK3dvKq7c75kerf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsIeI/btsBMWfZjxq/MvZRKCwK3dvKq7c75kerf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnsIeI%2FbtsBMWfZjxq%2FMvZRKCwK3dvKq7c75kerf0%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;523&quot; height=&quot;487&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;614&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;span style=&quot;color: #000000;&quot;&gt;그 후 로그인 처리가 되어 메인페이지로 온 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;945&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbc865/btsBQW7BVOV/Ki0bp2IDAUrSinOZBoMfCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbc865/btsBQW7BVOV/Ki0bp2IDAUrSinOZBoMfCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbc865/btsBQW7BVOV/Ki0bp2IDAUrSinOZBoMfCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbc865%2FbtsBQW7BVOV%2FKi0bp2IDAUrSinOZBoMfCk%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;543&quot; height=&quot;659&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;945&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;span style=&quot;color: #000000;&quot;&gt;내 정보 조회를 하면 네이버 OAuth로 로그인한 정보가 확인 가능하네요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;935&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkzeRv/btsBQnxBwct/th5cTgW2VB9axTWdJLFKHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkzeRv/btsBQnxBwct/th5cTgW2VB9axTWdJLFKHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkzeRv/btsBQnxBwct/th5cTgW2VB9axTWdJLFKHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkzeRv%2FbtsBQnxBwct%2Fth5cTgW2VB9axTWdJLFKHK%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;528&quot; height=&quot;631&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;782&quot; data-origin-height=&quot;935&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;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체적인 과정을 정리하면 아래 이미지와 같습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b34UJG/btsBG3fVVYk/F7ry1VpMgAboMNXn7NU4Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b34UJG/btsBG3fVVYk/F7ry1VpMgAboMNXn7NU4Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b34UJG/btsBG3fVVYk/F7ry1VpMgAboMNXn7NU4Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb34UJG%2FbtsBG3fVVYk%2FF7ry1VpMgAboMNXn7NU4Kk%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;557&quot; height=&quot;844&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring</category>
      <category>OAuth</category>
      <category>Spring</category>
      <category>소셜로그인</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/147</guid>
      <comments>https://yusang.tistory.com/147#entry147comment</comments>
      <pubDate>Mon, 11 Dec 2023 22:01:24 +0900</pubDate>
    </item>
    <item>
      <title>Spring, OAuth2  + JWT 를 활용하여 소셜 로그인 구현하기 1편 (구글 및 네이버) [Spring 3.1.5, java 17]</title>
      <link>https://yusang.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ycDMv/btsByr8dstB/HxYgCKkiAz660kvfkOeXwk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ycDMv/btsByr8dstB/HxYgCKkiAz660kvfkOeXwk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ycDMv/btsByr8dstB/HxYgCKkiAz660kvfkOeXwk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FycDMv%2FbtsByr8dstB%2FHxYgCKkiAz660kvfkOeXwk%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;280&quot; height=&quot;184&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;184&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: #000000;&quot;&gt;글 작성하기에 앞서 ppt로 정리한 전체적인 동작 흐름입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 제 프로젝트에서는 아래와 같이 OAuth를 통한 구글, 네이버 로그인을 구현했고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 과정을 정리하려고 글을 작성하게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_OAuth2 동작방식.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltFap/btsBHstvygh/6VTeWYf2ce28rYHW7FhSI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltFap/btsBHstvygh/6VTeWYf2ce28rYHW7FhSI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltFap/btsBHstvygh/6VTeWYf2ce28rYHW7FhSI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FltFap%2FbtsBHstvygh%2F6VTeWYf2ce28rYHW7FhSI1%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;699&quot; height=&quot;591&quot; data-filename=&quot;edited_OAuth2 동작방식.png&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;656&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;http://console.cloud.google.com/project&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;http://console.cloud.google.com/project&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700323128484&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 클라우드 플랫폼&quot; data-og-description=&quot;로그인 Google 클라우드 플랫폼으로 이동&quot; data-og-host=&quot;accounts.google.com&quot; data-og-source-url=&quot;http://console.cloud.google.com/project&quot; data-og-url=&quot;https://accounts.google.com/v3/signin/identifier?continue=https%3A%2F%2Fconsole.cloud.google.com%2Fproject&amp;amp;followup=https%3A%2F%2Fconsole.cloud.google.com%2Fproject&amp;amp;ifkv=AVQVeywtrOmM4wV4fCzusDjk6zZF7ZdFxxr1izSvpJEXkVTluKpzvk-yta_Yb5X4sGGkuJgaYBPnpA&amp;amp;osid=1&amp;amp;passive=1209600&amp;amp;service=cloudconsole&amp;amp;flowName=WebLiteSignIn&amp;amp;flowEntry=ServiceLogin&amp;amp;dsh=S-1241431778%3A1700323126926132&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://console.cloud.google.com/project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://console.cloud.google.com/project&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;Google 클라우드 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로그인 Google 클라우드 플랫폼으로 이동&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;accounts.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 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: #000000;&quot;&gt;프로젝트 만들기 -&amp;gt; API 및 서비스 -&amp;gt; 사용자 인증정보 -&amp;gt; 사용자 인증 정보 만들기 -&amp;gt; OAuth 클라이언트 ID&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/liqsY/btsAwrPYkok/vGXEYeBpGMhLXlKyrmwTd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/liqsY/btsAwrPYkok/vGXEYeBpGMhLXlKyrmwTd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/liqsY/btsAwrPYkok/vGXEYeBpGMhLXlKyrmwTd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FliqsY%2FbtsAwrPYkok%2FvGXEYeBpGMhLXlKyrmwTd1%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;347&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;347&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H8fOD/btsBxdikFrc/BOZRQqx4dERSDmtwzKacO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H8fOD/btsBxdikFrc/BOZRQqx4dERSDmtwzKacO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H8fOD/btsBxdikFrc/BOZRQqx4dERSDmtwzKacO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH8fOD%2FbtsBxdikFrc%2FBOZRQqx4dERSDmtwzKacO1%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;552&quot; height=&quot;557&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqFfDm/btsBtF712vq/1EKORXhKXcyltKdXETeWS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqFfDm/btsBtF712vq/1EKORXhKXcyltKdXETeWS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqFfDm/btsBtF712vq/1EKORXhKXcyltKdXETeWS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqFfDm%2FbtsBtF712vq%2F1EKORXhKXcyltKdXETeWS1%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;503&quot; height=&quot;356&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;승인할 리디렉션 URI를 설정 후&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 후 사용자 인증 정보에서 클라이언트 ID 및 클라이언트 보안 비밀번호 확인 가능&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것을 복사해 놓습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zvdFw/btsAzBRiROg/DL7CIB6w8T3NsCE5j8G3RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zvdFw/btsAzBRiROg/DL7CIB6w8T3NsCE5j8G3RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zvdFw/btsAzBRiROg/DL7CIB6w8T3NsCE5j8G3RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzvdFw%2FbtsAzBRiROg%2FDL7CIB6w8T3NsCE5j8G3RK%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;798&quot; height=&quot;403&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;403&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;&lt;span style=&quot;color: #000000;&quot;&gt;gradle에 추가&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;dependencies {
    //oauth
    implementation(&quot;org.springframework.boot:spring-boot-starter-oauth2-client&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;들여 쓰기 유이하여 yml파일에 아래와 같이 코드 작성&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX9XCZ/btsBtIX1obN/rZ5Owc031QsxVnK9uNKpMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX9XCZ/btsBtIX1obN/rZ5Owc031QsxVnK9uNKpMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX9XCZ/btsBtIX1obN/rZ5Owc031QsxVnK9uNKpMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX9XCZ%2FbtsBtIX1obN%2FrZ5Owc031QsxVnK9uNKpMK%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;1517&quot; height=&quot;576&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 발급받은 id
            client-secret: 발급받은 비밀번호
            scope:
              - email
              - profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;securityConfig에 추가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 OAuth 로그인 후 후처리는 principalOauth2UserService 여기서 진행하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  http
           .oauth2Login((oauth2) -&amp;gt; oauth2
            .userInfoEndpoint(userInfoEndpoint -&amp;gt; userInfoEndpoint
                .userService(principalOauth2UserService))
            .defaultSuccessUrl(&quot;/&quot;, true));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;DefaultOauth2 UserService을 상속받은 principalOauth2UserService 클래스를 만들고,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구글 로그인 후 값을 잘 가져오는지 콘솔로 찍어 확인해 보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        System.out.println(&quot;getClientRegistration: &quot;+userRequest.getClientRegistration());
        System.out.println(&quot;getAccessToken: &quot;+userRequest.getAccessToken());
        System.out.println(&quot;getAttributes: &quot;+super.loadUser(userRequest).getAttributes());

        return super.loadUser(userRequest);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&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 class=&quot;awk&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;http://localhost:8080/oauth2/authorization/google&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 같은 엔드 포인트로 요청을 보내면 구글 로그인을 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;따로 엔드포인트를 설정한게 없는데 어떻게 아래 엔드포인트로 Oauth 로그인이 될까요?&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Security OAuth2의 기본 동작 중 하나는 각 OAuth2 제공자에 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;대한 &lt;span style=&quot;color: #ee2323;&quot;&gt;로그인 엔드포인트를 자동으로 생성하는 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그렇기 때문에 &lt;span style=&quot;text-align: start;&quot;&gt;사용자가 해당 엔드포인트로 이동하면 OAuth2 로그인 프로세스가 시작됩니다.&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 사용자는 해당 제공자의 로그인 페이지에서 로그인을 수행하고, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;승인하면 Spring Security가 제공한 콜백 엔드포인트로 다시 리다이렉트 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;Google의 경우 &lt;/span&gt;/login/oauth2/code/google&lt;span style=&quot;text-align: start;&quot;&gt;로 리다이렉트 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그래서 위에서 승인된 리디렉션 URI로 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;localhost:8080/login/oauth2/code/google 을 입력한 것입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇기에 배포된 환경에서 OAuth 로그인을 하려고 한다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포된 도메인도 같이 입력해야지 배포환경에서도 OAuth 로그인을 할 수 있습니다.&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;실제로 &lt;span style=&quot;text-align: start;&quot;&gt;승인된 리디렉션 URI를 등록하지 않는다면&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;아래와 같이 400 오류 redirect_uri_mismatch 에러가 발생하게 됩니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCBf4G/btsBr3Bc7oO/CNdQUGblkWEMXyOKiHv3KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCBf4G/btsBr3Bc7oO/CNdQUGblkWEMXyOKiHv3KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCBf4G/btsBr3Bc7oO/CNdQUGblkWEMXyOKiHv3KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCBf4G%2FbtsBr3Bc7oO%2FCNdQUGblkWEMXyOKiHv3KK%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;568&quot; height=&quot;464&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;464&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;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 구글 로그인을 진행해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhyviD/btsBzzSdg3n/EJHP0hjbgyUMOyNiGMdQWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhyviD/btsBzzSdg3n/EJHP0hjbgyUMOyNiGMdQWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhyviD/btsBzzSdg3n/EJHP0hjbgyUMOyNiGMdQWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhyviD%2FbtsBzzSdg3n%2FEJHP0hjbgyUMOyNiGMdQWk%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;584&quot; height=&quot;638&quot; data-origin-width=&quot;584&quot; data-origin-height=&quot;638&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;&lt;span style=&quot;color: #000000;&quot;&gt;그러면 아래와 같이 콘솔에 출력되는 로그인 정보들을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 자세히 보면 구글로그인을 통해 가져온 정보들임을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 이렇게 가져온 정보들을 통해 로그인을 하고 DB에 저장하여 user로 생성해야 할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQBsl/btsBx73ay7z/oD6p0stZwrVHOcdz86RkZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQBsl/btsBx73ay7z/oD6p0stZwrVHOcdz86RkZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQBsl/btsBx73ay7z/oD6p0stZwrVHOcdz86RkZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQBsl%2FbtsBx73ay7z%2FoD6p0stZwrVHOcdz86RkZk%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;1807&quot; height=&quot;69&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;69&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1701894868642&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;getAccessToken: org.springframework.security.oauth2.core.OAuth2AccessToken@171dce06
getAttributes: {sub=109646515849671741701, 
name=이유상, 
given_name=이유상, 
picture=https://lh3.googleusercontent.com/a/ACg8ocIDIAJdY7rkevOPxHTNSErvDt_nHubf3Omnk83p6DW38FM=s96-c, 
email=liyusang799@gmail.com, 
email_verified=true, 
locale=ko}&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9ewML/btsBvSrN4HR/cSzLjKDULj7nKETHcmO110/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9ewML/btsBvSrN4HR/cSzLjKDULj7nKETHcmO110/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9ewML/btsBvSrN4HR/cSzLjKDULj7nKETHcmO110/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9ewML%2FbtsBvSrN4HR%2FcSzLjKDULj7nKETHcmO110%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;342&quot; height=&quot;312&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;814&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;span style=&quot;color: #000000;&quot;&gt;스프링 시큐리티 세션에 들어갈 수 있는 타입은 Authentication 객체 밖에 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Authentication객체 안에 들어갈 수 있는 두 개의 타입은 UserDetails (일반적인 로그인), &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Oauth2 User(Oauth를 통한 로그인.. 소셜 로그인)입니다.&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;span style=&quot;color: #000000;&quot;&gt;controller에서 로그인 상태를 확인할 때 oauth2를 통해 로그인을 한 경우와&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 로그인을 한 경우에 필요한 타입이 다릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 그 다른 두경우를 나눠서 각각 코드를 작성하는 것은 매우 복잡하고 어려울 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 공통의 하나의 클래스를 만들어 &lt;span style=&quot;text-align: start;&quot;&gt;UserDetails, Oauth2 User를 implement 해서 사용해야 합니다&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;기존에 만들었던 PrincipalDetails에 UserDetails만 implement 했지만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;OAuth2 User 또한 같이 implement 해주고 필요한 메서드들을 아래와 같이 구현해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Getter
@AllArgsConstructor
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails, OAuth2User {

    @Getter
    private User user;
    private String username;
    private String password;
    private Map&amp;lt;String, Object&amp;gt; attributes;

    //일반 로그인
    public PrincipalDetails(User user) {
        this.user = user;
    }

    //OAuth 로그인
    public PrincipalDetails(User user,Map&amp;lt;String,Object&amp;gt; attributes) {
        this.user = user;
    }

    @Override
    public &amp;lt;A&amp;gt; A getAttribute(String name) {
        return OAuth2User.super.getAttribute(name);
    }

    @Override
    public Map&amp;lt;String, Object&amp;gt; getAttributes() {
        return attributes;
    }

    @Override
    public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
        return List.of(new SimpleGrantedAuthority(user.getAuthority()));
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public String getEmail() {
        return user.getEmail();
    }

    @Override
    public String getName() {
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;OAuth 로그인 후처리를 진행하는 principalOauth2UserService 클래스를 아래와 같이 바꿔봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구글 로그인 후 필요한 정보들을 가져와&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB를 체크하여 DB에 소셜로그인을 한 기록이 없다면 DB에 필요한 정보들을 저장하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇지 않다면 저장하지 않는 로직입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아까 콘솔로 찍어서 정보를 확인해본 코드들은 이제 필요 없는 코드니 지우시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        //System.out.println(&quot;getClientRegistration: &quot; + userRequest.getClientRegistration());
        //System.out.println(&quot;getAccessToken: &quot; + userRequest.getAccessToken());
        //System.out.println(&quot;getAttributes: &quot; + super.loadUser(userRequest).getAttributes());

        OAuth2User oauth2User = super.loadUser(userRequest);

       String provider = userRequest.getClientRegistration()
       .getRegistrationId(); //google kakao facebook...
        String provideId = oauth2User.getAttribute(&quot;sub&quot;);
        String email = oauth2User.getAttribute(&quot;email&quot;);
        String username = oauth2User.getAttribute(&quot;name&quot;);
        String password = &quot;OAuth2&quot;; //Oauth2로 로그인을 해서 패스워드는 의미없음.
        String role = &quot;ROLE_USER&quot;;

        Optional&amp;lt;User&amp;gt; user = userRepository.findByEmail(email);

        //이미 소셜로그인을 한적이 있는지 없는지
        if (user.isEmpty()) {
            User newUser = User.builder()
                .email(email)
                .username(username)
                .password(password)
                .phonenumber(null)
                .authority(role)
                .provider(provider)
                .build();

            userRepository.save(newUser);
            return new PrincipalDetails(newUser, oauth2User.getAttributes());
        } else {
            return new PrincipalDetails(user.get(), oauth2User.getAttributes());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;그리고 SimpleUrlAuthenticationSuccessHandler을 상속받아 OAuth로그인 성공을 처리할 커스텀 클래스를 만들어 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로그인 성공시 실행되는 onAuthenticationSuccess을 override 하여&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth 로그인이 성공적으로 실행될 시 수행할 작업을 코딩해 줍니다.&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;span style=&quot;color: #000000;&quot;&gt;저는 기존에 만들어둔 ResponseDTO에 JWT토큰을 생성하여 담아서 프론트쪽에 보낼 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주석 부분처럼 처리해서 전달하셔도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
        Authentication authentication) throws IOException, ServletException {
        // OAuth2 로그인이 성공했을 때의 추가 작업을 수행
        // 여기에서는 JWT 토큰을 발급하고 형식에 맞게 return

        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        String token = jwtProvider.createToken(principalDetails.getUser());

/*        // 응답 헤더에 JWT 토큰 추가
        response.addHeader(&quot;Authorization&quot;, &quot;Bearer &quot; + token);

        // JWT 토큰을 response에 담아서 전송
        response.setContentType(&quot;application/json&quot;);
        response.getWriter().write(&quot;{\&quot;token\&quot;: \&quot;&quot; + token + &quot;\&quot;}&quot;);
        System.out.println(&quot;TOKEN : &quot;+token);
*/

        ResponseDTO&amp;lt;Object&amp;gt; loginResponse = ResponseDTO.res(
            HttpStatus.valueOf(HttpServletResponse.SC_OK),
            &quot;Login successful&quot;,
            new LoginResponse(principalDetails.getUser().getEmail(),
                principalDetails.getUser().getUsername(), token));

        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResponse = objectMapper.writeValueAsString(loginResponse);

        response.setContentType(&quot;application/json&quot;);
        response.setCharacterEncoding(&quot;UTF-8&quot;);
        response.getWriter().write(jsonResponse);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;그리고 spring security 설정에 아래와 같이 성공 시 실행할 클래스를 추가해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;방금 만든 커스텀 성공 핸들러를 추가하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;http.oauth2Login(oauth2 -&amp;gt; oauth2
        .userInfoEndpoint(
            userInfoEndpoint -&amp;gt; userInfoEndpoint.userService(principalOauth2UserService))
        .successHandler(oAuth2LoginSuccessHandler)
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;이제 다시 스프링을 실행하여 아래의 엔드포인트로 구글 로그인을 진행해 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;http://localhost:8080/oauth2/authorization/google&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: #000000;&quot;&gt;구글 로그인을 진행하여...&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HMm6p/btsByzE9VGl/RmkvK9vi12zrKFEbUAobQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HMm6p/btsByzE9VGl/RmkvK9vi12zrKFEbUAobQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HMm6p/btsByzE9VGl/RmkvK9vi12zrKFEbUAobQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHMm6p%2FbtsByzE9VGl%2FRmkvK9vi12zrKFEbUAobQk%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;446&quot; height=&quot;510&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;510&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: #000000;&quot;&gt;구글 로그인을 성공적으로 한다면 아래와 같이 JWT토큰을 발급받을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wgrSW/btsBvhrNzp9/DGJsYx1eIN3o07LPQu2aXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wgrSW/btsBvhrNzp9/DGJsYx1eIN3o07LPQu2aXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wgrSW/btsBvhrNzp9/DGJsYx1eIN3o07LPQu2aXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwgrSW%2FbtsBvhrNzp9%2FDGJsYx1eIN3o07LPQu2aXk%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;616&quot; height=&quot;107&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;107&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;span style=&quot;color: #000000;&quot;&gt;발급받은 JWT토큰을 헤더에 넣어 유저 정보를 가져오는 API도 잘 실행이 됩니다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LtMfG/btsBBXyYl3S/a2DdlGiqikBpPCvQ7mIe50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LtMfG/btsBBXyYl3S/a2DdlGiqikBpPCvQ7mIe50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LtMfG/btsBBXyYl3S/a2DdlGiqikBpPCvQ7mIe50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLtMfG%2FbtsBBXyYl3S%2Fa2DdlGiqikBpPCvQ7mIe50%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;1195&quot; height=&quot;521&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;이후 업데이트&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2LoginSuccessHandler를 다음과 같이 변경하여 &lt;b&gt;프론트단으로 JWT토큰을 쿼리스트링에 담아&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리디렉트 되도록 하고 로그인 처리를 하도록 변경했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유는 이렇게 발급한 JWT토큰은 프론트단에서 추출하여 사용을 하지 못하기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtProvider jwtProvider;

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

        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
        String token = jwtProvider.createToken(principalDetails.getUser());
        String email = principalDetails.getEmail();
        String name = principalDetails.getUsername();

        //코드 내로 리디렉트 설정
        //String redirectUrl = &quot;/user/oauth-success?token=&quot;+token;

        //한국어 인코딩 설정
        String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8.toString());
        
        String redirectUrl = &quot;프론트도메인?token=&quot; + token
        +&quot;&amp;amp;email=&quot;+email+&quot;&amp;amp;name=&quot;+encodedName;
        
        getRedirectStrategy().sendRedirect(request, response, redirectUrl);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;프론트단 코드 - 구글 로그인 버튼 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 버튼으로 누르면 oauth2/authorization/google로 요청을 하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702296064129&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import styled from '@emotion/styled';
import { Button } from '@chakra-ui/react';
import NaverIcon from '../../assets/icons/btn_naver.svg';
import GoogleIcon from '../../assets/icons/btn_google.svg';

const SocialLoginContainer = () =&amp;gt; {
  const handleSocialLogin = (service: string) =&amp;gt; {
    window.location.href = `https://yanoljaschool.site:8080/oauth2/authorization/${service}`;
  };

  return (
    &amp;lt;StyledContainer&amp;gt;
      &amp;lt;StyledButton
        backgroundColor=&quot;#03C759&quot;
        variant=&quot;none&quot;
        onClick={() =&amp;gt; {
          handleSocialLogin('naver');
        }}
      &amp;gt;
        &amp;lt;img src={NaverIcon} alt=&quot;naver&quot; /&amp;gt;
        네이버로 시작하기
      &amp;lt;/StyledButton&amp;gt;
      &amp;lt;StyledButton
        style={{ color: 'black' }}
        onClick={() =&amp;gt; {
          handleSocialLogin('google');
        }}
      &amp;gt;
        &amp;lt;img src={GoogleIcon} alt=&quot;google&quot; style={{ paddingRight: '10px' }} /&amp;gt;
        구글로 시작하기
      &amp;lt;/StyledButton&amp;gt;
    &amp;lt;/StyledContainer&amp;gt;
  );
};

export default SocialLoginContainer;

const StyledContainer = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  margin: 5px;
`;

const StyledButton = styled(Button)`
  margin: 5px;
  color: white;
  &amp;amp;:hover {
    opacity: 0.9;
  }
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트단으로 리디렉트 되면 아래의 코드처럼 쿼리스트링으로 JWT토큰을 받을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 그렇게 받은 JWT토큰을 쿠키에 저장하게 되고 로그인처리를 프론트단에서 진행하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1702296160544&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { setCookies } from '../../../utils/utils';
import LoadingCircle from '../../../components/Loading';

const SocialLoginLoading = () =&amp;gt; {
  const navigate = useNavigate();

  useEffect(() =&amp;gt; {
    const handleLogin = async () =&amp;gt; {
      /* eslint-disable */
      const urlParams = new URLSearchParams(location.search);
      const token = urlParams.get('token');
      const userName = urlParams.get('name');
      const userEmail = urlParams.get('email');
      
      if (token &amp;amp;&amp;amp; userEmail &amp;amp;&amp;amp; userName) {
        await setCookies(userEmail, userName, token);
        navigate('/');
      } else {
        alert('로그인에 실패하셨습니다.');
        navigate('/login');
      }
    };

    handleLogin();
    /* eslint-enable */
  }, []);
  return &amp;lt;LoadingCircle /&amp;gt;;
};

export default SocialLoginLoading;&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;&lt;span style=&quot;color: #000000;&quot;&gt;실제 프론트 배포 페이지에서 버튼을 클릭하면..&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xWxZT/btsBG6DG29P/K4Uvo7HfMS0lUL5CWQBeaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xWxZT/btsBG6DG29P/K4Uvo7HfMS0lUL5CWQBeaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xWxZT/btsBG6DG29P/K4Uvo7HfMS0lUL5CWQBeaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxWxZT%2FbtsBG6DG29P%2FK4Uvo7HfMS0lUL5CWQBeaK%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;602&quot; height=&quot;470&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;576&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;span style=&quot;color: #000000;&quot;&gt;아래처럼 구글 로그인창이 뜨게 되고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구글 로그인을 성공하게 되면&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brdYfp/btsBG3G02i5/46qBWDwBbszf8BVzUou7Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brdYfp/btsBG3G02i5/46qBWDwBbszf8BVzUou7Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brdYfp/btsBG3G02i5/46qBWDwBbszf8BVzUou7Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrdYfp%2FbtsBG3G02i5%2F46qBWDwBbszf8BVzUou7Ok%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;451&quot; height=&quot;515&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;515&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;span style=&quot;color: #000000;&quot;&gt;JWT토큰을 쿠키에 저장한 채 메인페이지로 가게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth 로그인이 성공적으로 된 것이죠.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRacIl/btsBGoZbYVb/Ylqp8Xd05aeDbQCtFthEL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRacIl/btsBGoZbYVb/Ylqp8Xd05aeDbQCtFthEL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRacIl/btsBGoZbYVb/Ylqp8Xd05aeDbQCtFthEL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRacIl%2FbtsBGoZbYVb%2FYlqp8Xd05aeDbQCtFthEL1%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;553&quot; height=&quot;662&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;942&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;&lt;span style=&quot;color: #000000;&quot;&gt;내 정보를 확인해 보면 구글 이메일을 통해 로그인이 된 것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca04Nu/btsBQmyFi0U/1G5FgeS6Y4vboqF0Jr0111/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca04Nu/btsBQmyFi0U/1G5FgeS6Y4vboqF0Jr0111/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca04Nu/btsBQmyFi0U/1G5FgeS6Y4vboqF0Jr0111/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca04Nu%2FbtsBQmyFi0U%2F1G5FgeS6Y4vboqF0Jr0111%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;529&quot; height=&quot;651&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;942&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;&lt;span style=&quot;color: #000000;&quot;&gt;전체적인 과정을 정리하자면 아래 사진과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VItw8/btsBHrgwIbE/DKI2H2mu3V3k2RfbmkGy7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VItw8/btsBHrgwIbE/DKI2H2mu3V3k2RfbmkGy7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VItw8/btsBHrgwIbE/DKI2H2mu3V3k2RfbmkGy7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVItw8%2FbtsBHrgwIbE%2FDKI2H2mu3V3k2RfbmkGy7k%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;644&quot; height=&quot;620&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1386&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;&lt;span style=&quot;color: #000000;&quot;&gt;네이버 OAuth 로그인 까지 추가하는 과정을&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 글에 이어서 작성하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yusang.tistory.com/147&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1702299707095&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;Spring, OAuth2  + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]&quot; data-og-description=&quot;이 글은 전글과 이어집니다.! https://yusang.tistory.com/140 Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17] http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google &quot; data-og-host=&quot;yusang.tistory.com&quot; data-og-source-url=&quot;https://yusang.tistory.com/147&quot; data-og-url=&quot;https://yusang.tistory.com/147&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qb8kD/hyUIyhebMN/uok5liX4dQ6BB3BY7O2vxK/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/jUUuE/hyULUbSFvv/uyO2aEwNWFfnXLIr7VkDZK/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/bChAhQ/hyUL1vh8Hh/T4aCNuxtU6kYOgU1kPWkH1/img.png?width=1169&amp;amp;height=2077&amp;amp;face=0_0_1169_2077&quot;&gt;&lt;a href=&quot;https://yusang.tistory.com/147&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yusang.tistory.com/147&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qb8kD/hyUIyhebMN/uok5liX4dQ6BB3BY7O2vxK/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/jUUuE/hyULUbSFvv/uyO2aEwNWFfnXLIr7VkDZK/img.jpg?width=280&amp;amp;height=184&amp;amp;face=0_0_280_184,https://scrap.kakaocdn.net/dn/bChAhQ/hyUL1vh8Hh/T4aCNuxtU6kYOgU1kPWkH1/img.png?width=1169&amp;amp;height=2077&amp;amp;face=0_0_1169_2077');&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;Spring, OAuth2 + JWT 를 활용하여 소셜 로그인 구현하기 2편 (구글 및 네이버) [Spring 3.1.5, java 17]&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 전글과 이어집니다.! https://yusang.tistory.com/140 Spring, OAuth2 + JWT 를 활용하여 구글 소셜 로그인 구현하기 [Spring 3.1.5, java 17] http://console.cloud.google.com/project Google 클라우드 플랫폼 로그인 Google&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yusang.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;</description>
      <category>Spring</category>
      <category>oauth2</category>
      <category>spring 구글 소셜 로그인</category>
      <category>spring 소셜로그인</category>
      <category>구글 소셜로그인</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/140</guid>
      <comments>https://yusang.tistory.com/140#entry140comment</comments>
      <pubDate>Fri, 8 Dec 2023 07:26:01 +0900</pubDate>
    </item>
    <item>
      <title>spring jpa에 Querydsl 적용하기 [spring 3.1.5, java 17]</title>
      <link>https://yusang.tistory.com/146</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Er1qH/btsBvRMZ9a5/CFFAkndVp4MeMhYDf8yBV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Er1qH/btsBvRMZ9a5/CFFAkndVp4MeMhYDf8yBV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Er1qH/btsBvRMZ9a5/CFFAkndVp4MeMhYDf8yBV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEr1qH%2FbtsBvRMZ9a5%2FCFFAkndVp4MeMhYDf8yBV0%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;515&quot; height=&quot;335&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&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;span style=&quot;color: #000000;&quot;&gt;개발하고 있는 프로젝트에 Querydsl을 적용해 보려고 합니다.&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Querydsl의 장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;유연성 및 강력한 검색 기능&lt;/b&gt;&amp;nbsp;:&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Query DSL을 사용하면 복잡한 검색 조건을 표현할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;진행하고 있는 프로젝트 메인페이지 및 예약 검사 시&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;jpa repository를 통해 여러 복잡한 조건을 거쳐 DB에 여러 번 접근하게 되는데&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;QueryDSL을 활용하게 된다면 그럴 필요 없이 검색 조건과 필터를 조합해 정확한 결과를 얻을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;가독성 및 이해도 향상&lt;/b&gt;&amp;nbsp;:&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Query DSL은 일반적으로 사람이 이해하기 쉬운 구조를 가지고 있기에 이는 쿼리를 작성하고 이해하는 데 도움이 되며,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 코드의 가독성을 높여 유지보수를 쉽게 만들 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;성능 향상&lt;/b&gt;&amp;nbsp;:&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적절한 쿼리를 작성함으로써 성능을 향상할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 그냥 JPQL 쓰면 안 되나? QueryDSL을 사용하는 이유는 뭘까요?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 차이&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;JPQL&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JPQL은 JPA를 사용하여 작성된 객체 지향 쿼리 언어입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JPQL 쿼리는 **런타임(애플리케이션 실행 중)**에 문자열로 해석되어 SQL로 변환되는데, &lt;br /&gt;이 과정에서 쿼리의 오타나 잘못된 필드명으로 인한 오류가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;런타임&lt;/b&gt;&lt;/span&gt;에만 발견됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복잡한 쿼리의 경우 &lt;b&gt;JPQL로 작성하기 어려울 수 있고,&lt;/b&gt; 유지보수에 어려움이 있을 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Querydsl&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Querydsl은 타입 안전한 쿼리를 작성할 수 있는 프레임워크입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;컴파일 시점에 쿼리의 문법 오류를 발견할 수 있어, &lt;span style=&quot;color: #ee2323;&quot;&gt;안정성이 높습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동적 쿼리 작성이 용이하며, 복잡한 쿼리도 더 가독성 있게 작성할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JPQL에 비해 약간 더 빠른 성능을 제공할 수 있지만, &lt;br /&gt;이는 상황에 따라 다를 수 있으며 대체로 차이는 미미합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실무에서는?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;JPQL&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단한 쿼리나, 프로젝트에서 추가적인 의존성을 피하고자 할 때 선호됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;표준 JPA만을 사용하고 싶은 경우에 적합합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Querydsl&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복잡한 쿼리나 동적 쿼리가 필요한 경우에 선호됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;타입 안전성과 가독성이 중요한 프로젝트에서 유리합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;초기 설정 필요하고, 학습 곡선이 높지만,&lt;br /&gt;장기적으로 보았을 때 유지보수와 개발 효율성 측면에서 이점을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;color: #000000;&quot;&gt;결국 성능적인 측면에서 JPQL과 Querydsl 사이에는 큰 차이가 없습니다.&lt;br /&gt;&lt;b&gt;주된 차이점은 가독성, 유지보수성, 그리고 타입 안전성에 있습니다.&lt;/b&gt; &lt;br /&gt;실무에서는 복잡한 쿼리를 다루어야 하고 유지보수성, 런타임시 에러를 회피하기 위해 &lt;br /&gt;&lt;b&gt;Querydsl&lt;/b&gt;을 선호하는 경향이 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #374151; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;장단점 비교:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #374151; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;JPQL:&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점: JPA 표준의 일부이므로 JPA 구현체에서 지원되는 범위 내에서 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점: &lt;b&gt;동적 쿼리 작성이 어렵고, 문자열 기반으로 작성되어 오타가 발생할 가능성이 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;QueryDSL:&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점: 정적으로 쿼리를 작성하므로 컴파일 시간에 오류를 확인할 수 있고, 동적 쿼리 작성이 편리합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 코드 자동 완성과 IDE 지원이 우수합니다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점: JPQL이나 SQL과 달리 학습 곡선이 존재하며, 프로젝트에 라이브러리를 추가해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇기에 복잡한 쿼리를 추가하여 사용하는 경우에는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유지 보수 측면에서 용이한 QueryDsl을 추가해서 사용해 보려고 합니다.&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;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;gradle 추가&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;dependencies {
    // Querydsl 추가
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
    annotationProcessor &quot;jakarta.annotation:jakarta.annotation-api&quot;
    annotationProcessor &quot;jakarta.persistence:jakarta.persistence-api&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;//querydsl
def querydslDir = &quot;src/main/generated&quot;

sourceSets {
    main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile) {
    options.getGeneratedSourceOutputDirectory().set(file(querydslDir))
}

clean.doLast {
    file(querydslDir).deleteDir()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가 후 gradle - other - compileJava 실행&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2U6Ov/btsByzx9vGB/M7MNKnefXIe5WwOWTWdGp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2U6Ov/btsByzx9vGB/M7MNKnefXIe5WwOWTWdGp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2U6Ov/btsByzx9vGB/M7MNKnefXIe5WwOWTWdGp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2U6Ov%2FbtsByzx9vGB%2FM7MNKnefXIe5WwOWTWdGp1%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;355&quot; height=&quot;463&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;463&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;&lt;span style=&quot;color: #000000;&quot;&gt;실행하고 나면 아래와 같이 Qclass가 생성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 통해 Querydsl을 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KhyoN/btsByseQREJ/OxCvS03pSpnX1J10yZb9Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KhyoN/btsByseQREJ/OxCvS03pSpnX1J10yZb9Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KhyoN/btsByseQREJ/OxCvS03pSpnX1J10yZb9Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKhyoN%2FbtsByseQREJ%2FOxCvS03pSpnX1J10yZb9Q0%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;637&quot; height=&quot;657&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;657&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;&lt;span style=&quot;color: #000000;&quot;&gt;customRepository interface 생성&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Repository
public interface AccommodationRepositoryCustom {

    Page&amp;lt;Long&amp;gt; findAccommodationIds(AccommodationCategory category, boolean isDomestic,
        Pageable pageable, LocalDate startDate, LocalDate endDate,int numberOfPeople);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;메인페이지에서 업체를 조회하는데 아래와 같은 쿼리로 조건을 필터링해서 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조건에 맞는 업체만을 response 하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT DISTINCT a.accommodation_id
FROM accommodation_rooms
JOIN accommodation a ON a.accommodation_id = accommodation_rooms.accommodation_id
LEFT JOIN reservations r ON accommodation_rooms.room_id = r.room_id AND (r.start_date &amp;gt; NOW() OR r.start_date IS NULL)
WHERE a.category = :category
AND (
    NOT (
        (:startDate &amp;lt;= r.end_date AND :endDate &amp;gt;= r.start_date)
        OR (:startDate &amp;gt;= r.start_date AND :endDate &amp;lt;= r.end_date)
    )
    OR (r.start_date IS NULL AND r.end_date IS NULL)
    OR (r.payment_completed IS FALSE OR r.payment_completed IS NULL)
    OR (r.deleted_at is not null)
)
and a.is_domestic = :isDomestic
and (:numberOfPerson&amp;gt;=accommodation_rooms.min_capacity and :numberOfPerson&amp;lt;= accommodation_rooms.max_capacity)
limit :page,:size&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; customRepository을&amp;nbsp; implement 한 클래스 작성 후&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 쿼리를 Querydsl로 아래와 같이 반영해서 작성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;public class AccommodationRepositoryImpl implements AccommodationRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public AccommodationRepositoryImpl(EntityManager entityManager) {
        this.queryFactory = new JPAQueryFactory(entityManager);
    }

    @Override
    public Page&amp;lt;Long&amp;gt; findAccommodationIds(AccommodationCategory category, boolean isDomestic,
        Pageable pageable,LocalDate startDate, LocalDate endDate,int numberOfPeople) {
        QAccommodation a = QAccommodation.accommodation;
        QAccommodationRooms ar = QAccommodationRooms.accommodationRooms;
        QReservations r = QReservations.reservations;

        BooleanExpression reservationCondition = r.startDate.after(LocalDate.now())
            .or(r.startDate.isNull());

        BooleanExpression conflictingCondition = r.deletedAt.isNotNull()
            .or(r.paymentCompleted.isFalse().or(r.paymentCompleted.isNull()))
            .or(r.endDate.before(startDate))
                .or(r.startDate.after(endDate));

        BooleanExpression capacityCondition =
            ar.minCapacity.loe(numberOfPeople).and(ar.maxCapacity.goe(numberOfPeople));

        List&amp;lt;Long&amp;gt; result = queryFactory.selectDistinct(a.accommodationId)
            .from(ar)
            .join(a).on(ar.accommodation.accommodationId.eq(a.accommodationId))
            .leftJoin(r).on(ar.roomId.eq(r.room.roomId).and(reservationCondition))
            .where(a.category.eq(category)
                .and(a.isDomestic.eq(isDomestic))
                .and(conflictingCondition)
                .and(capacityCondition))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

        return new PageImpl&amp;lt;&amp;gt;(result, pageable, result.size());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Querydsl 적용 후 service단의 코드가 매우 간소화되었습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;적용 전&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701854127760&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
    public List&amp;lt;AccommodationFindResponse&amp;gt; getAccommodationsInMainPage(
        PrincipalDetails principalDetails, String categoryStr,
        boolean isDomestic, Pageable pageable, LocalDate startDate, LocalDate endDate,
        int numberOfPeople) {
        AccommodationCategory category = AccommodationCategory.valueOf(categoryStr.toUpperCase());

        Page&amp;lt;Accommodation&amp;gt; accommodations = accommodationRepository.findByCategoryAndIsDomestic(
            category, isDomestic, pageable);
        List&amp;lt;Accommodation&amp;gt; accommodationList = new ArrayList&amp;lt;&amp;gt;();

        boolean capacityAvailable = false;
        for (Accommodation accommodationContents : accommodations) {
            List&amp;lt;AccommodationRooms&amp;gt; roomlist = accommodationContents.getRoomlist();
            int fullRoomCount = 0;

            for (AccommodationRooms accommodationRoomContents : roomlist) {
                if (accommodationRoomContents.getMaxCapacity() &amp;gt;= numberOfPeople &amp;amp;&amp;amp;
                    accommodationRoomContents.getMinCapacity() &amp;lt;= numberOfPeople) {
                    capacityAvailable = true;
                }
                //예약 충돌 검사
                if (reservationRepository.findConflictingReservations(
                    accommodationRoomContents.getRoomId(), startDate, endDate).isPresent()) {
                    fullRoomCount++;
                }
            }
            if (fullRoomCount &amp;lt; roomlist.size() &amp;amp;&amp;amp; capacityAvailable) {
                accommodationList.add(accommodationContents);
            }
        }
        
        ...
        ...
        ...
        
        
        return accommodationFindResponses;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적용 후&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Transactional
public List&amp;lt;AccommodationFindResponse&amp;gt; getAccommodationsInMainPage(
    PrincipalDetails principalDetails, AccommodationCategory category,
    boolean isDomestic, Pageable pageable, LocalDate startDate, LocalDate endDate,
    int numberOfPeople) {
    Page&amp;lt;Long&amp;gt; acIds = accommodationRepositoryCustom.findAccommodationIds(
        category, isDomestic, pageable, startDate, endDate, numberOfPeople);

    List&amp;lt;Accommodation&amp;gt; accommodationList =
        acIds.stream().map(accommodationRepository::findByAccommodationId).toList();
   ...
   ...
   ...

    return accommodationFindResponses;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;진행하고 있는 프로젝트에서 &lt;span style=&quot;background-color: #ffffff; letter-spacing: 0px;&quot;&gt;메인페이지에서 클라이언트의 요구사항에 맞게 업체를 여러 가지 조건으로 필터링해서&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;업체정보를 전달해야 했었는데 &lt;b&gt;Querydsl 적용으로 인해 검색 조건과 필터를 조합하여 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;&lt;b&gt;정확한 결과를 얻을 수 있었고 service 로직이 매우 간소화되었습니다!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;또한 Querydsl은&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt; 일반적으로 사람이 이해하기 쉬운 구조를 가지고 있어&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt; 쿼리를 작성하고 이해하는 데 도움이 되며 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;IDE에서도 체크가 가능하기 때문에 유지보수가 매우 용이합니다!&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;필요한 데이터에 접근할 때 어쩔 수 없이 복잡한 쿼리를 사용해야 하는 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Querydsl을 적극적으로 활용하면 좋을 것 같습니다.&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;</description>
      <category>Spring</category>
      <category>querydsl</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/146</guid>
      <comments>https://yusang.tistory.com/146#entry146comment</comments>
      <pubDate>Wed, 6 Dec 2023 22:23:07 +0900</pubDate>
    </item>
    <item>
      <title>springboot certbot으로 ssl인증서 받아서 https로 배포하기</title>
      <link>https://yusang.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNs4V3/btsA8LGsSHJ/mvt3uslppxImxQCfNH0pb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNs4V3/btsA8LGsSHJ/mvt3uslppxImxQCfNH0pb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNs4V3/btsA8LGsSHJ/mvt3uslppxImxQCfNH0pb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNs4V3%2FbtsA8LGsSHJ%2Fmvt3uslppxImxQCfNH0pb0%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;1200&quot; height=&quot;631&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;631&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;&lt;span style=&quot;color: #000000;&quot;&gt;ssl 인증서를 받기 위해서는 등록할 도메인이 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 저는 가비아에서 1900짜리 제일 싼 도메인을 1년 구매했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 배포한 인스턴스의 주소를 등록해 주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1653&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DNwFN/btsA4ORNU2v/HGVEY0epJ0nRa53lvrmA6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DNwFN/btsA4ORNU2v/HGVEY0epJ0nRa53lvrmA6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DNwFN/btsA4ORNU2v/HGVEY0epJ0nRa53lvrmA6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDNwFN%2FbtsA4ORNU2v%2FHGVEY0epJ0nRa53lvrmA6k%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;1653&quot; height=&quot;627&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1653&quot; data-origin-height=&quot;627&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://certbot.eff.org/instructions?ws=other&amp;amp;os=ubuntufocal&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://certbot.eff.org/instructions?ws=other&amp;amp;os=ubuntufocal&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1701258579123&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;Certbot Instructions&quot; data-og-description=&quot;Tagline&quot; data-og-host=&quot;certbot.eff.org&quot; data-og-source-url=&quot;https://certbot.eff.org/instructions?ws=other&amp;amp;os=ubuntufocal&quot; data-og-url=&quot;https://certbot.eff.org/instructions?os=ubuntufocal&amp;amp;ws=other&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://certbot.eff.org/instructions?ws=other&amp;amp;os=ubuntufocal&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://certbot.eff.org/instructions?ws=other&amp;amp;os=ubuntufocal&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;Certbot Instructions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Tagline&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;certbot.eff.org&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;&lt;span style=&quot;color: #000000;&quot;&gt;저는 gcp 인스턴스에서 spring 프로젝트를 배포하고 있었고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운영체제는 ubuntu 20이었기에 아래처럼 선택했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;올바르게 위 사이트에서 선택을 해서 따라 하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2klTs/btsA7ynib4a/Ru7yOh0hFghd6n4MhH656K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2klTs/btsA7ynib4a/Ru7yOh0hFghd6n4MhH656K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2klTs/btsA7ynib4a/Ru7yOh0hFghd6n4MhH656K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2klTs%2FbtsA7ynib4a%2FRu7yOh0hFghd6n4MhH656K%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;1285&quot; height=&quot;159&quot; data-origin-width=&quot;1285&quot; data-origin-height=&quot;159&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;pre id=&quot;code_1701258785046&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo snap install --classic certbot&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1701258789178&quot; class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;sudo ln -s /snap/bin/certbot /usr/bin/certbot&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1701258807073&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo certbot certonly --standalone&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: #000000;&quot;&gt;사이트에서 알려주는 방법대로 쭉 따라 하고 저는 --standalone 방법을 선택했습니다.&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;span style=&quot;color: #000000;&quot;&gt;이메일을 입력하고 도메인을 입력하라는 메시지가 나오면 입력하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 후 파일이 저장된 경로가 나오고 그 경로로 이동해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 후 아래 명령어를 통해 keystore.p12 파일을 얻어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 입력하라고 한 비밀번호를 기억하고 있어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701259007362&quot; class=&quot;shell&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt; openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name name -CAfile chain.pem -caname root&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;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 아래처럼 keystore.p12 파일이 생성된 걸 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 이 파일을 파일 다운로드를 통해 내 컴퓨터로 가져와야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 주의 해야 할 점이 있습니다. 파일과 파일의 하위 폴더 모두 읽기 권한이 없기 때문에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;chmod 777 명령어를 통해 설정을 바꿔야 합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;저는 이때 구글에서 따로 메시지를 안 알려주고 다운로드를 할 수 없다고&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; 에러만 나서 원인을 찾는데 시간이 걸렸습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;원인은 다운로드하려는 파일만 읽기 권한을 주고 하위 폴더에 읽기 권한을 주지 않아서였습니다.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q0H7w/btsA9jpDkqJ/FI1g72QK71OmrBDBatxjOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q0H7w/btsA9jpDkqJ/FI1g72QK71OmrBDBatxjOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q0H7w/btsA9jpDkqJ/FI1g72QK71OmrBDBatxjOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq0H7w%2FbtsA9jpDkqJ%2FFI1g72QK71OmrBDBatxjOK%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;786&quot; height=&quot;669&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&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;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 가져온 인증서를 아래와 같이 spring프로젝트 resoureces 하위에 넣고&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n4SRA/btsA8tlKiGS/1Qg1P6k4bXqND1Dp2JuIm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n4SRA/btsA8tlKiGS/1Qg1P6k4bXqND1Dp2JuIm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n4SRA/btsA8tlKiGS/1Qg1P6k4bXqND1Dp2JuIm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn4SRA%2FbtsA8tlKiGS%2F1Qg1P6k4bXqND1Dp2JuIm0%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;286&quot; height=&quot;176&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;176&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;&lt;span style=&quot;color: #000000;&quot;&gt;yml파일에 아래와 같이 추가하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 비밀번호는 위에서 입력한 비밀번호입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 same-site : None 이 코드는 쿠키 관련 코드니 필요 없어도 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 사용하고 있는 프로젝트에서 필요하기에 저는 추가했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1701259459869&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  servlet:
    session:
      cookie:
        same-site: None
  ssl:
    key-store: classpath:keystore.p12
    key-store-type: PKCS12
    key-store-password: 123456&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 도커 이미지로 배포하고 있었기에 아래와 같이 도커파일을 수정해서&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커 이미지를 생성해서 도커허브에 다시 올렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk-slim-buster
COPY build/libs/yanolja-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
# 작업 디렉토리 생성
WORKDIR /app

# 소스 코드 및 .env 파일 복사
COPY . .

# .env 파일을 WORKDIR로 복사
COPY .env .env

# keystore.p12 파일을 WORKDIR로 복사
COPY src/main/resources/keystore.p12 keystore.p12

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;그리고 gcp에서 도커를 다시 실행시키고 api를 확인해 보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;https 인증이 아주 잘 되고 있는 걸 확인할 수 있습니다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MxVuw/btsA5g8jeyk/Bjm37TQgQkecYEVyeUG870/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MxVuw/btsA5g8jeyk/Bjm37TQgQkecYEVyeUG870/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MxVuw/btsA5g8jeyk/Bjm37TQgQkecYEVyeUG870/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxVuw%2FbtsA5g8jeyk%2FBjm37TQgQkecYEVyeUG870%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;327&quot; height=&quot;267&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring</category>
      <category>https</category>
      <category>spring으로 https 적용</category>
      <category>SSL</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/145</guid>
      <comments>https://yusang.tistory.com/145#entry145comment</comments>
      <pubDate>Thu, 30 Nov 2023 11:12:58 +0900</pubDate>
    </item>
    <item>
      <title>GCP 에서 springboot 프로젝트 docker로 배포하기</title>
      <link>https://yusang.tistory.com/144</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XvoC2/btsAM4z5AJS/Xf2N2U2nOrVnnmKEikMTD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XvoC2/btsAM4z5AJS/Xf2N2U2nOrVnnmKEikMTD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XvoC2/btsAM4z5AJS/Xf2N2U2nOrVnnmKEikMTD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXvoC2%2FbtsAM4z5AJS%2FXf2N2U2nOrVnnmKEikMTD1%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;646&quot; height=&quot;562&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;604&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;span style=&quot;color: #000000;&quot;&gt;GCP로 이동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700742185766&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 클라우드 플랫폼&quot; data-og-description=&quot;로그인 Google 클라우드 플랫폼으로 이동&quot; data-og-host=&quot;accounts.google.com&quot; data-og-source-url=&quot;http://console.cloud.google.com/?hl=ko&amp;amp;project=second-impact-406010&quot; data-og-url=&quot;https://accounts.google.com/v3/signin/identifier?continue=https%3A%2F%2Fconsole.cloud.google.com%2F%3Fhl%3Dko%26project%3Dsecond-impact-406010&amp;amp;followup=https%3A%2F%2Fconsole.cloud.google.com%2F%3Fhl%3Dko%26project%3Dsecond-impact-406010&amp;amp;hl=ko&amp;amp;ifkv=ASKXGp0AUAGidRv6NkqAajAl-Qm7envG8ithP4pGZDuX0DXDlD6KZ8aJJSkHqoqVOrZbf6uqjWFaJQ&amp;amp;osid=1&amp;amp;passive=1209600&amp;amp;service=cloudconsole&amp;amp;flowName=WebLiteSignIn&amp;amp;flowEntry=ServiceLogin&amp;amp;dsh=S-1559268296%3A1700742184308103&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://console.cloud.google.com/?hl=ko&amp;amp;project=second-impact-406010&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://console.cloud.google.com/?hl=ko&amp;amp;project=second-impact-406010&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;Google 클라우드 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로그인 Google 클라우드 플랫폼으로 이동&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;accounts.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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;vm인스턴스 -&amp;gt; 인스턴스 생성&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deXazR/btsAM402Pq1/qZvr5S2yH531HphZSaABLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deXazR/btsAM402Pq1/qZvr5S2yH531HphZSaABLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deXazR/btsAM402Pq1/qZvr5S2yH531HphZSaABLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeXazR%2FbtsAM402Pq1%2FqZvr5S2yH531HphZSaABLK%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;1470&quot; height=&quot;549&quot; data-origin-width=&quot;1470&quot; data-origin-height=&quot;549&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;&lt;span style=&quot;color: #000000;&quot;&gt;아래와 같이 설정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 저렴한 비용으로 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OS는 ubuntu로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;2214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTNFIV/btsAQARORiZ/d8ybYKkw7nMm5hEQyYX8F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTNFIV/btsAQARORiZ/d8ybYKkw7nMm5hEQyYX8F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTNFIV/btsAQARORiZ/d8ybYKkw7nMm5hEQyYX8F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTNFIV%2FbtsAQARORiZ%2Fd8ybYKkw7nMm5hEQyYX8F0%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;725&quot; height=&quot;1193&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;2214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3cC4/btsALJXD9no/EeLTJ3gHsb30KFUQsheoY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3cC4/btsALJXD9no/EeLTJ3gHsb30KFUQsheoY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3cC4/btsALJXD9no/EeLTJ3gHsb30KFUQsheoY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3cC4%2FbtsALJXD9no%2FEeLTJ3gHsb30KFUQsheoY0%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;653&quot; height=&quot;746&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;816&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;&lt;span style=&quot;color: #000000;&quot;&gt;vpc 네트워크 -&amp;gt; 외부 고정 ip 주소 예약을 클릭해 고정 ip주소를 할당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DQnMR/btsAQAYz0P1/aLMLc7A2QJidlGh8cDCDrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DQnMR/btsAQAYz0P1/aLMLc7A2QJidlGh8cDCDrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DQnMR/btsAQAYz0P1/aLMLc7A2QJidlGh8cDCDrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDQnMR%2FbtsAQAYz0P1%2FaLMLc7A2QJidlGh8cDCDrk%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;1504&quot; height=&quot;493&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;493&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;&lt;span style=&quot;color: #000000;&quot;&gt;생성된 인스턴스에 ssh를 통해 연결하고 도커를 설치합니다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래는 도커공식문서에서 가져온 설치 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700741176351&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;Install Docker Engine on Ubuntu&quot; data-og-description=&quot;Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bcICjz/hyUB2hiVor/MPYA0YZHnNWeuKQWxKYuS0/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bcICjz/hyUB2hiVor/MPYA0YZHnNWeuKQWxKYuS0/img.png?width=129&amp;amp;height=128&amp;amp;face=0_0_129_128');&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;Install Docker Engine on Ubuntu&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jumpstart your client-side server applications with Docker Engine on Ubuntu. This guide details prerequisites and multiple methods to install Docker Engine on Ubuntu.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;$ sudo apt-get update
$ sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

$ sudo mkdir -m 0755 -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo \
  &quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable&quot; | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
$ sudo docker --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0JTHN/btsAPKAlYEf/iHsqqwcDc6HAGRSfDu2zsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0JTHN/btsAPKAlYEf/iHsqqwcDc6HAGRSfDu2zsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0JTHN/btsAPKAlYEf/iHsqqwcDc6HAGRSfDu2zsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0JTHN%2FbtsAPKAlYEf%2FiHsqqwcDc6HAGRSfDu2zsK%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;746&quot; height=&quot;635&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot; data-testid=&quot;conversation-turn-16&quot;&gt;
&lt;div style=&quot;color: #000000;&quot;&gt;
&lt;div&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;docker compose 또한 설치합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;$ sudo curl -L &quot;https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
$ docker-compose -v&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커 이미지를 생성하기 위한 dockerfile을 생성해야 합니다. &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: #000000;&quot;&gt;인텔리제이에서 프로젝트 최상단에 Dockerfile을 생성하면 됩니다.&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: #000000;&quot;&gt;저는 env파일을 사용해 key값을 저장했기에 아래와 같은 docker file을 생성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk-slim-buster
COPY build/libs/yanolja-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
# 작업 디렉토리 생성
WORKDIR /app

# 소스 코드 및 .env 파일 복사
COPY . .

# .env 파일을 WORKDIR로 복사
COPY .env .env

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mRj6M/btsAO4F7tRx/s64EOp9bycYpRHeJe6kqKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mRj6M/btsAO4F7tRx/s64EOp9bycYpRHeJe6kqKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mRj6M/btsAO4F7tRx/s64EOp9bycYpRHeJe6kqKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmRj6M%2FbtsAO4F7tRx%2Fs64EOp9bycYpRHeJe6kqKK%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;1074&quot; height=&quot;792&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;792&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;&lt;span style=&quot;color: #000000;&quot;&gt;yml파일에서 datasource부분을 env파일에 있는 값을 사용하는 방식으로 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB는 GCP에 Cloud SQL를 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  config:
    activate:
      on-profile: docker

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DBURL}
    username: ${DBUSER}
    password: ${DBUSER}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000; text-align: start;&quot;&gt;bootJar을 통해 jar파일을 빌드해 줍니다&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pu740/btsAOQH6172/HFYo6FEb9HCopq4TWP1zB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pu740/btsAOQH6172/HFYo6FEb9HCopq4TWP1zB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pu740/btsAOQH6172/HFYo6FEb9HCopq4TWP1zB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpu740%2FbtsAOQH6172%2FHFYo6FEb9HCopq4TWP1zB1%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;442&quot; height=&quot;649&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;649&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;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;docker hub에서 repositories를 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1700770675809&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;Docker Hub Container Image Library | App Containerization&quot; data-og-description=&quot;Build and Ship any Application Anywhere Docker Hub is the world's easiest way to create, manage, and deliver your team's container applications. Create your account Signing up for Docker is fast and free. Continue with GoogleContinue with GitHubContinue wi&quot; data-og-host=&quot;hub.docker.com&quot; data-og-source-url=&quot;https://hub.docker.com/&quot; data-og-url=&quot;https://hub.docker.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cfxLbT/hyUCasW1N2/lS7jlXtrb5PnM3pkvM7vi1/img.png?width=416&amp;amp;height=250&amp;amp;face=0_0_416_250&quot;&gt;&lt;a href=&quot;https://hub.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hub.docker.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cfxLbT/hyUCasW1N2/lS7jlXtrb5PnM3pkvM7vi1/img.png?width=416&amp;amp;height=250&amp;amp;face=0_0_416_250');&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;Docker Hub Container Image Library | App Containerization&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build and Ship any Application Anywhere Docker Hub is the world's easiest way to create, manage, and deliver your team's container applications. Create your account Signing up for Docker is fast and free. Continue with GoogleContinue with GitHubContinue wi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hub.docker.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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J7JQi/btsAOTLu8JI/7qjjUQ8EOkEqoxwteIdkkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J7JQi/btsAOTLu8JI/7qjjUQ8EOkEqoxwteIdkkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J7JQi/btsAOTLu8JI/7qjjUQ8EOkEqoxwteIdkkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ7JQi%2FbtsAOTLu8JI%2F7qjjUQ8EOkEqoxwteIdkkK%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;629&quot; height=&quot;623&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;905&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: #000000;&quot;&gt;이제 도커 이미지를 만들어 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; docker build -t liyusang799/test . -&amp;gt; .을 붙어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; docker build -t (본인닉네임)/(레포이름) . &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;span style=&quot;color: #000000;&quot;&gt;그리고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; docker push liyusang799/test&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;docker hub로 push합니다.&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아까 생성한 vm 인스턴스를 ssh를 통해 연결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sudo vim docker-compose.yml을 통해 docker-compose.yml파일을 생성합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;version: '3'
services:
  application :
    image : test
    env_file: .env
    environment:
      SPRING_DATASOURECE_URL: 
      SPRING_DATESOURECE_USERNAME: 
      SPRING_DATASOURCE_PASSWORD: 
      SPRING_PROFILES_ACTIVE: dev
    restart: always
    container_name: test
    ports:
      - &quot;8080:8080&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 저는 env파일에서 값을 가져오는 코드들이 있어 env파일을 같은 경로에 넣어주어야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;gcp에서 지원하는 파일업로드를 통해 같은 경로에 env파일을 넣었습니다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbjcj/btsAL2Qq1Tm/KXeM0K2yaXkDaQoAp6DYv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbjcj/btsAL2Qq1Tm/KXeM0K2yaXkDaQoAp6DYv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbjcj/btsAL2Qq1Tm/KXeM0K2yaXkDaQoAp6DYv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flbjcj%2FbtsAL2Qq1Tm%2FKXeM0K2yaXkDaQoAp6DYv1%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;1385&quot; height=&quot;507&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE4YG6/btsAMYmfZHk/GfkqDTkKrIjcglozCnHdJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE4YG6/btsAMYmfZHk/GfkqDTkKrIjcglozCnHdJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE4YG6/btsAMYmfZHk/GfkqDTkKrIjcglozCnHdJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE4YG6%2FbtsAMYmfZHk%2FGfkqDTkKrIjcglozCnHdJk%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;701&quot; height=&quot;188&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 docker hub에서 accesstoken을 발급받고 sudo docker login -u (이름)을 통해 로그인해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;password는 토큰을 발급받아서 확인할 수 있습니다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2FocP/btsANMsmSdC/qDYl1jGxheCS91Cg66prKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2FocP/btsANMsmSdC/qDYl1jGxheCS91Cg66prKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2FocP/btsANMsmSdC/qDYl1jGxheCS91Cg66prKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2FocP%2FbtsANMsmSdC%2FqDYl1jGxheCS91Cg66prKk%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;722&quot; height=&quot;944&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;944&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: #000000;&quot;&gt;sudo&amp;nbsp;docker&amp;nbsp;pull&amp;nbsp;liyusang799/test&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; sudo docker pull (이름)/(레포이름)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;을 통해서 생성된 도커 이미지를 가져와서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아까 만들어둔 docker-compose.yml을 실행시키면 됩니다.&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;span style=&quot;color: #000000;&quot;&gt;sudo docker compose up이 명령어입니다.&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;관련 명령어들입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;현재 실행 중인 도커 이미지들을 확인합니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt; docker ps &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caVP9b/btsAMmOuPmp/SzZ9A3tHYcAOiJMSSGiL6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caVP9b/btsAMmOuPmp/SzZ9A3tHYcAOiJMSSGiL6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caVP9b/btsAMmOuPmp/SzZ9A3tHYcAOiJMSSGiL6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaVP9b%2FbtsAMmOuPmp%2FSzZ9A3tHYcAOiJMSSGiL6K%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;902&quot; height=&quot;768&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;768&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;실행중단&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt; docker stop my_container &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7YKHC/btsAMnNnYKv/ycYh1ANlrQBuj7ZYAJKCuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7YKHC/btsAMnNnYKv/ycYh1ANlrQBuj7ZYAJKCuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7YKHC/btsAMnNnYKv/ycYh1ANlrQBuj7ZYAJKCuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7YKHC%2FbtsAMnNnYKv%2FycYh1ANlrQBuj7ZYAJKCuk%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;600&quot; height=&quot;63&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;63&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;도커 이미지 삭제&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;sudo docker rmi -f &lt;span style=&quot;text-align: left;&quot;&gt;my_container&lt;/span&gt; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1EEJ6/btsAOOpUR11/o0xM0kF2v8K1sfzwoWr11k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1EEJ6/btsAOOpUR11/o0xM0kF2v8K1sfzwoWr11k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1EEJ6/btsAOOpUR11/o0xM0kF2v8K1sfzwoWr11k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1EEJ6%2FbtsAOOpUR11%2Fo0xM0kF2v8K1sfzwoWr11k%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;790&quot; height=&quot;152&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;152&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;&lt;span style=&quot;color: #000000;&quot;&gt;배포된 api를 이제 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHBJF/btsANJ3v017/gU9ov5OoG7CC04Tnzix8NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHBJF/btsANJ3v017/gU9ov5OoG7CC04Tnzix8NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHBJF/btsANJ3v017/gU9ov5OoG7CC04Tnzix8NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHBJF%2FbtsANJ3v017%2FgU9ov5OoG7CC04Tnzix8NK%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;644&quot; height=&quot;538&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;538&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;</description>
      <category>Spring</category>
      <category>docker</category>
      <category>GCP</category>
      <category>Spring</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/144</guid>
      <comments>https://yusang.tistory.com/144#entry144comment</comments>
      <pubDate>Sat, 25 Nov 2023 12:43:48 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] request시 notnull값 controllerAdvice로 처리해서 response보내기</title>
      <link>https://yusang.tistory.com/142</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baC2dM/btsAIGtaQ3A/cFKISCoUSo5lPWW4wU4zP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baC2dM/btsAIGtaQ3A/cFKISCoUSo5lPWW4wU4zP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baC2dM/btsAIGtaQ3A/cFKISCoUSo5lPWW4wU4zP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaC2dM%2FbtsAIGtaQ3A%2FcFKISCoUSo5lPWW4wU4zP0%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;750&quot; height=&quot;354&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;public record CreateReservationRequest(
    @NotNull(message = &quot;startDate cannot be null&quot;)
    LocalDate startDate,

    @NotNull(message = &quot;endDate cannot be null&quot;)
    LocalDate endDate,

    @NotNull(message = &quot;numberOfPerson cannot be null&quot;)
    Integer numberOfPerson
) {

    public Reservations toEntity(
        User user, AccommodationRooms rooms, boolean paymentCompleted) {
        return Reservations.builder()
            .user(user)
            .rooms(rooms)
            .startDate(startDate)
            .endDate(endDate)
            .numberOfPerson(numberOfPerson)
            .paymentCompleted(paymentCompleted)
            .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@NotNull&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;어노테이션을 사용하면 해당 필드에 null 값이 들어오지 않도록 강제할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같은 코드에서 request로 notnull값에 null이 들어오는 경우 &lt;span style=&quot;text-align: start;&quot;&gt;Spring은 자동으로&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MethodArgumentNotValidException&lt;span style=&quot;text-align: start;&quot;&gt;을 던지게 됩니다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하지만 이 예외는 핸들링되지 않기 때문에 직접 핸들링해서 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;알맞은 형식으로 response를 보내 주어야 합니다. 그렇게 해야 프론트개발자 입장에서 처리할 수 있습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@ControllerAdvice 에서 아래와 같이&amp;nbsp;MethodArgumentNotValidException을 핸들링할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;

@RestControllerAdvice
public class GlobalExceptionRestAdvice {

    //notnull값을 넣지 않은 경우 에러 핸들링
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity&amp;lt;ResponseDTO&amp;lt;Object&amp;gt;&amp;gt; handleValidationExceptions(
        MethodArgumentNotValidException e) {

        BindingResult bindingResult = e.getBindingResult();

        //Notnull어노테이션에 작성한 에러메시지가 뜨도록 메시지 바인딩
        Map&amp;lt;String, String&amp;gt; fieldErrors = bindingResult.getFieldErrors()
            .stream()
            .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));

        log.error(e.getMessage(), e);
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(
                ResponseDTO.res(HttpStatus.BAD_REQUEST, fieldErrors));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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: #000000;&quot;&gt;실제로 null일 수 없는 값을 null로 하여 요청하면 커스텀 핸들러가 에러를 처리해서 response를 보내게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bysMPU/btsAJGlT260/rpslXcE9630Vf2hQ1f5hA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bysMPU/btsAJGlT260/rpslXcE9630Vf2hQ1f5hA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bysMPU/btsAJGlT260/rpslXcE9630Vf2hQ1f5hA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbysMPU%2FbtsAJGlT260%2FrpslXcE9630Vf2hQ1f5hA0%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;435&quot; height=&quot;236&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring</category>
      <category>ControllerAdvice</category>
      <category>MethodArgumentNotValidException</category>
      <author>Yusang</author>
      <guid isPermaLink="true">https://yusang.tistory.com/142</guid>
      <comments>https://yusang.tistory.com/142#entry142comment</comments>
      <pubDate>Fri, 24 Nov 2023 23:13:00 +0900</pubDate>
    </item>
  </channel>
</rss>