<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>정시화의 공부기록</title>
    <link>https://im-diary.tistory.com/</link>
    <description>좋은 개발자가 되기 위해 공부중입니다. </description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 22:37:19 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>나는시화</managingEditor>
    <image>
      <title>정시화의 공부기록</title>
      <url>https://tistory1.daumcdn.net/tistory/6405147/attach/d0c9c8e2c6d84659af9daa93df42750f</url>
      <link>https://im-diary.tistory.com</link>
    </image>
    <item>
      <title>HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported</title>
      <link>https://im-diary.tistory.com/126</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인.&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ajax로 requestBody를 서버에 넘겨주려고 할 때 발생한 오류.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jackson 라이브러리는 이미 추가했는데 계속 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 &lt;b&gt;jackson&lt;/b&gt;은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;get&lt;/span&gt;&lt;/b&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_1742995362959&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Getter
@Setter
public class RecommendSearchRequest extends BookSearchRequest {
    private String rcmdTnY; // 추천년월
    private String rcmdTnM; // 추천월
    private Integer rcmdTnYm; // 추천연월
    private Long homepageSn;
    private String homepageSns;

    public void initRcmdTnYm() {
        LocalDate now = LocalDate.now();
        String nowStr = StringUtil.convertLocalDateToString(now, &quot;yyyyMMdd&quot;);
        this.rcmdTnM = nowStr.substring(4, 6);
        this.rcmdTnY = nowStr.substring(0, 4);
        this.rcmdTnYm = Integer.parseInt(nowStr.substring(0, 6));
    }


// 이 아래 메서드가 문제!! 
    public MultiValueMap&amp;lt;String, String&amp;gt; getNationLibSearchQueryParams() {

        MultiValueMap&amp;lt;String, String&amp;gt; queryParams = new LinkedMultiValueMap&amp;lt;&amp;gt;();

        // API 요청을 위한 startRowNumApi, endRowNumApi 계산
        if (StringUtils.hasText(this.rcmdTnY) &amp;amp;&amp;amp; StringUtils.hasText(this.rcmdTnM)) {
            YearMonth ym = YearMonth.of(Integer.parseInt(this.rcmdTnY), Integer.parseInt(this.rcmdTnM));
            LocalDate startDate = ym.atDay(1);
            LocalDate endDate = ym.atEndOfMonth();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;);

            queryParams.add(&quot;start_date&quot;, startDate.format(formatter));
            queryParams.add(&quot;end_date&quot;, endDate.format(formatter));
        }
        queryParams.add(&quot;Key&quot;, getKey());
        return queryParams;
    }

    public RecommendSearchRequest() {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get&lt;/b&gt;으로 시작하는 메서드가 있으면 &lt;b&gt;jackson&lt;/b&gt;이 &lt;b&gt;getter&lt;/b&gt;로 인식하기 때문에 &lt;b&gt;nationLibSearchQueryParams&amp;nbsp;&lt;/b&gt;프로퍼티에 대응하는 &lt;b&gt;setter&lt;/b&gt;를 찾는 과정에 생기는 오류이다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 메서드 이름을 변경하여 해결 완료함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 json 타입의 데이터를 requestBody로 매핑할 때 순서가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기본 생성자로 객체 생성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. setter로 필드 값 매핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음에는 상속 관계인 부모 클래스와 조상 클래스에 문제가 아닐까 생각함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 생성자가 잘 만들어지는 지도 확인이 필요.&lt;/p&gt;</description>
      <category>기타</category>
      <category>ajax</category>
      <category>httpmediatypenotsupportedexception</category>
      <category>requestbody</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/126</guid>
      <comments>https://im-diary.tistory.com/126#entry126comment</comments>
      <pubDate>Wed, 26 Mar 2025 22:30:51 +0900</pubDate>
    </item>
    <item>
      <title>티베로 java.lang.AbstractMethodError</title>
      <link>https://im-diary.tistory.com/125</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 에러 내용 및 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 gpt한테 물어봤다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-end=&quot;118&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;java.lang.AbstractMethodError는 Java 애플리케이션이 &lt;b&gt;추상 메소드&lt;/b&gt;를 호출하려 할 때 해당 메소드의 구현을 찾지 못하면 발생하는 에러입니다. 주로 다음과 같은 경우에 나타납니다:&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;371&quot; data-start=&quot;120&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;218&quot; data-start=&quot;120&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;버전 불일치:&lt;/b&gt; 컴파일 시점에 사용된 클래스와 런타임에 로드되는 클래스가 서로 다른 버전일 때, 인터페이스나 추상 클래스의 메소드 구현이 누락되어 발생할 수 있습니다.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li data-end=&quot;371&quot; data-start=&quot;219&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;JDBC 드라이버 문제:&lt;/b&gt; Tibero DB와 같이 특정 데이터베이스를 사용할 때, 해당 DB의 JDBC 드라이버가 Java에서 기대하는 인터페이스를 완전히 구현하지 않았거나, 드라이버 버전이 현재 사용 중인 Java 버전과 호환되지 않을 때 발생할 수 있습니다.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;383&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;해결 방법:&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;605&quot; data-start=&quot;384&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;448&quot; data-start=&quot;384&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;JDBC 드라이버 확인:&lt;/b&gt; Tibero DB에 맞는 최신 JDBC 드라이버를 사용하고 있는지 확인하세요.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li data-end=&quot;539&quot; data-start=&quot;449&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;라이브러리 버전 일치:&lt;/b&gt; 애플리케이션에서 참조하는 라이브러리(예: JDBC API)와 실제 런타임에 사용되는 라이브러리의 버전이 일치하는지 검토하세요.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li data-end=&quot;605&quot; data-start=&quot;540&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;클린 빌드:&lt;/b&gt; 가끔은 빌드 캐시 문제일 수 있으므로, 클린 빌드를 진행해보고 문제가 해결되는지 확인합니다.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffffff;&quot;&gt;&lt;b&gt;JDBC 드라이버&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;문제라고 함.&amp;nbsp;&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; background-color: #ffffff;&quot;&gt;현재 사용하고 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;jdbc&lt;/b&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;tibero6.jar&lt;/b&gt;인데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;티베로6&lt;/b&gt;은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자바 1.6&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;버전 기반으로 알고 있다.&amp;nbsp;&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;background-color: #ffffff;&quot;&gt;위 티베로가 자바 1.7인가? 1.8 부터 도입한 기능을 사용하려고 해서 발생한 문제이기 때문에 발생한 문제!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;2. 해결방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;jdbc 자바 1.8 버전 기반으로 만든 &lt;b&gt;tibero6-jdbc-18.jar&lt;/b&gt;을 설치해주면 됨.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/bzLEoy/btsMyAs6k3A/hkeznUQFSynNOEIJQkPI01/tibero6-jdbc-18.jar?attach=1&amp;amp;knm=tfile.jar&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;tibero6-jdbc-18.jar&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.70MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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: #9d9d9d;&quot;&gt;p.s 그지같은 티베로.&lt;/span&gt;&lt;/p&gt;</description>
      <category>기타</category>
      <category>AbstractMethodError</category>
      <category>tibero</category>
      <category>tibero6</category>
      <category>tibero6-18</category>
      <category>티베로</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/125</guid>
      <comments>https://im-diary.tistory.com/125#entry125comment</comments>
      <pubDate>Wed, 26 Feb 2025 21:03:55 +0900</pubDate>
    </item>
    <item>
      <title>jpa OutOfMemoryException</title>
      <link>https://im-diary.tistory.com/124</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API를 사용할 때마다 로그를 DB에 저장 -&amp;gt; 데이터가 너무 많이 쌓여서 데이터를 삭제하는 스케줄러 작성 -&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 한번에 70만 건을 삭제하려고 하니깐 OutOfMemoryException이 발생!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 원인&lt;/h3&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 interface ApiLogRepository extends JpaRepository&amp;lt;ApiLog, Long&amp;gt;, CustomApiLogRepository{

    void deleteApiLogsByRegistrationDateLessThan(LocalDateTime localDateTime);

}&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;나는 쿼리를 DB에 직접 날려서 삭제하는 줄로 알았더니 위와 같이 작성하면 데이터를 모두 조회한 다음에 delete 쿼리를 일일이 날림! 즉 메모리에 대용량 데이터를 불러오니깐 oom이 발생하는 것.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 db에 쿼리를 직접 날려도 지금 생각해보면 문제가 될 듯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해결방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;querydsl 벌크쿼리로 삭제!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 건씩 id를 조회한 다음에 삭제 -&amp;gt; 쿼리를 날리고 영속성 컨텍스트 초기화 반복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간중간에 영속성 컨텍스트를 비워주지 않으면 또 메모리에 과부화가 갈 수도 있으니 .clear()를 호출해주었음.&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;// CustomApiLogRepositoryImpl
@Override
public void deleteBatch(LocalDateTime localDateTime) {
    while (true) {
        List&amp;lt;Long&amp;gt; ids = queryFactory
                .select(apiLog.sn)
                .from(apiLog)
                .where(apiLog.registrationDate.lt(localDateTime))
                .limit(10000)
                .fetch();
        if (ids.isEmpty()) {
            break;
        }
        queryFactory
                .delete(apiLog)
                .where(apiLog.sn.in(ids))
                .execute();
        entityManager.flush();
        entityManager.clear();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 일하는 곳의 도메인은 도서관이라 트래픽이 많지 않다. 그래서 oom이 발생할 거라고는 생각을 못했었는데, 이번 잘못으로 대충 코드를 작성하는 일이 없도록 노력해야겠다.&lt;/p&gt;</description>
      <category>JPA</category>
      <category>exception</category>
      <category>JPA</category>
      <category>oom</category>
      <category>outofmemeory</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/124</guid>
      <comments>https://im-diary.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 24 Feb 2025 22:01:14 +0900</pubDate>
    </item>
    <item>
      <title>[React Native] 환경 구축,The emulator process for AVD has terminated 해결 방법</title>
      <link>https://im-diary.tistory.com/123</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.Node.js 설치&lt;/h3&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://nodejs.org/ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738759075104&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;Node.js &amp;mdash; 어디서든 JavaScript를 실행하세요&quot; data-og-description=&quot;Node.js&amp;reg; is a JavaScript runtime built on Chrome's V8 JavaScript engine.&quot; data-og-host=&quot;nodejs.org&quot; data-og-source-url=&quot;https://nodejs.org/ko&quot; data-og-url=&quot;https://nodejs.org/ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhOY6e/hyX7P07bnl/UEYVT5OkoIgXTS0XXD33t1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/TOlab/hyYcdlFFSW/X0KFKdeAtE22S82rt9AvU0/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256&quot;&gt;&lt;a href=&quot;https://nodejs.org/ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nodejs.org/ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhOY6e/hyX7P07bnl/UEYVT5OkoIgXTS0XXD33t1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/TOlab/hyYcdlFFSW/X0KFKdeAtE22S82rt9AvU0/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256');&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;Node.js &amp;mdash; 어디서든 JavaScript를 실행하세요&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Node.js&amp;reg; is a JavaScript runtime built on Chrome's V8 JavaScript engine.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nodejs.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;h3 data-ke-size=&quot;size23&quot;&gt;2. Chocolatey 설치&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chocolatey.org/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chocolatey.org/install&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738759154408&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;product&quot; data-og-title=&quot;Installing Chocolatey&quot; data-og-description=&quot;Chocolatey is software management automation for Windows that wraps installers, executables, zips, and scripts into compiled packages. Chocolatey integrates w/SCCM, Puppet, Chef, etc. Chocolatey is trusted by businesses to manage software deployments.&quot; data-og-host=&quot;chocolatey.org&quot; data-og-source-url=&quot;https://chocolatey.org/install&quot; data-og-url=&quot;https://chocolatey.org/install&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFCyHH/hyX7XkzLnE/qRUKAKwLkNnZf6HS5vnFZ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/YBN4k/hyX7SDAxWu/eGkVMUmuTfazeyhX2oQj61/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150,https://scrap.kakaocdn.net/dn/mEdU9/hyX7YX5z53/iq77frIvfxhqyBGjThrxvk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://chocolatey.org/install&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chocolatey.org/install&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFCyHH/hyX7XkzLnE/qRUKAKwLkNnZf6HS5vnFZ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/YBN4k/hyX7SDAxWu/eGkVMUmuTfazeyhX2oQj61/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150,https://scrap.kakaocdn.net/dn/mEdU9/hyX7YX5z53/iq77frIvfxhqyBGjThrxvk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Installing Chocolatey&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chocolatey is software management automation for Windows that wraps installers, executables, zips, and scripts into compiled packages. Chocolatey integrates w/SCCM, Puppet, Chef, etc. Chocolatey is trusted by businesses to manage software deployments.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chocolatey.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6qhiL/btsL7jzTB26/HybGYaqYMhZ3WskgYHvEj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6qhiL/btsL7jzTB26/HybGYaqYMhZ3WskgYHvEj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6qhiL/btsL7jzTB26/HybGYaqYMhZ3WskgYHvEj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6qhiL%2FbtsL7jzTB26%2FHybGYaqYMhZ3WskgYHvEj1%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;1539&quot; height=&quot;146&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 부분을 복사한 뒤 &lt;b&gt;PowerShell&lt;/b&gt;을 관리자 권한으로 실행한 다음 입력해준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSFYrY/btsL75A9SvT/X6KWbS8cixJSkduVWwBXRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSFYrY/btsL75A9SvT/X6KWbS8cixJSkduVWwBXRk/img.png&quot; data-alt=&quot;이미지처럼 버전이 나와야함.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSFYrY/btsL75A9SvT/X6KWbS8cixJSkduVWwBXRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSFYrY%2FbtsL75A9SvT%2FX6KWbS8cixJSkduVWwBXRk%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;623&quot; height=&quot;119&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지처럼 버전이 나와야함.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Android Studio 설치&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/studio?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738759283440&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;Android 스튜디오 및 앱 도구 다운로드 - Android 개발자 &amp;nbsp;|&amp;nbsp; Android Studio &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.&quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/studio?hl=ko&quot; data-og-url=&quot;https://developer.android.com/studio?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AmDJ2/hyX7WFTC2g/l0cPeE3SPbi40Tf3fiU2gk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/vPWU8/hyX7WFTC3S/y2zeAtim16UmSPzKb8mKl0/img.png?width=1832&amp;amp;height=1082&amp;amp;face=0_0_1832_1082,https://scrap.kakaocdn.net/dn/b9gWL9/hyX74xdNN5/OUromo7a15UbyDy0kaT3wK/img.png?width=1480&amp;amp;height=772&amp;amp;face=0_0_1480_772&quot;&gt;&lt;a href=&quot;https://developer.android.com/studio?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/studio?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AmDJ2/hyX7WFTC2g/l0cPeE3SPbi40Tf3fiU2gk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676,https://scrap.kakaocdn.net/dn/vPWU8/hyX7WFTC3S/y2zeAtim16UmSPzKb8mKl0/img.png?width=1832&amp;amp;height=1082&amp;amp;face=0_0_1832_1082,https://scrap.kakaocdn.net/dn/b9gWL9/hyX74xdNN5/OUromo7a15UbyDy0kaT3wK/img.png?width=1480&amp;amp;height=772&amp;amp;face=0_0_1480_772');&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;Android 스튜디오 및 앱 도구 다운로드 - Android 개발자 &amp;nbsp;|&amp;nbsp; Android Studio &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 설치 후 안드로이드 스튜디오 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) virtual Device Manager 클릭 후 Pixel 5 선택.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EuJNF/btsL7CFXueY/y59NjYuy3pHamoHf0VlqS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EuJNF/btsL7CFXueY/y59NjYuy3pHamoHf0VlqS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EuJNF/btsL7CFXueY/y59NjYuy3pHamoHf0VlqS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEuJNF%2FbtsL7CFXueY%2Fy59NjYuy3pHamoHf0VlqS1%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;416&quot; height=&quot;299&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TDEF0/btsL6VlKSzS/YXrnUP6a5PZtkvP6qhQRCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TDEF0/btsL6VlKSzS/YXrnUP6a5PZtkvP6qhQRCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TDEF0/btsL6VlKSzS/YXrnUP6a5PZtkvP6qhQRCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTDEF0%2FbtsL6VlKSzS%2FYXrnUP6a5PZtkvP6qhQRCk%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;543&quot; data-origin-width=&quot;1091&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 34, 33 모두 설치&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brB1wo/btsL87Y4dbj/LomzmlZateAwKyUoegavn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brB1wo/btsL87Y4dbj/LomzmlZateAwKyUoegavn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brB1wo/btsL87Y4dbj/LomzmlZateAwKyUoegavn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrB1wo%2FbtsL87Y4dbj%2FLomzmlZateAwKyUoegavn0%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;647&quot; height=&quot;63&quot; data-origin-width=&quot;647&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;4) SDK Manager 설치&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 13, 14, 15 설치를 했으며 모두 이미지와 같이 체크함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 하단에&amp;nbsp; Show Package Details를 누르면 이미지처럼 체크박스 선택이 가능.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehyg9X/btsL8TzZp8R/SpslF3CBSOoci2R2Jvg3yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehyg9X/btsL8TzZp8R/SpslF3CBSOoci2R2Jvg3yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehyg9X/btsL8TzZp8R/SpslF3CBSOoci2R2Jvg3yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fehyg9X%2FbtsL8TzZp8R%2FSpslF3CBSOoci2R2Jvg3yK%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;941&quot; height=&quot;603&quot; data-origin-width=&quot;941&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) SDK Tools&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cchMsj/btsL9qD6y5d/pPNgZmT8lYSwAyAG3WXox0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cchMsj/btsL9qD6y5d/pPNgZmT8lYSwAyAG3WXox0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cchMsj/btsL9qD6y5d/pPNgZmT8lYSwAyAG3WXox0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcchMsj%2FbtsL9qD6y5d%2FpPNgZmT8lYSwAyAG3WXox0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;951&quot; height=&quot;712&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. openjdk 설치 (17 권장)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/set-up-your-environment&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://reactnative.dev/docs/set-up-your-environment&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738759436027&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;Set Up Your Environment &amp;middot; React Native&quot; data-og-description=&quot;In this guide, you'll learn how to set up your environment, so that you can run your project with Android Studio and Xcode. This will allow you to develop with Android emulators and iOS simulators, build your app locally, and more.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/set-up-your-environment&quot; data-og-url=&quot;https://reactnative.dev/docs/set-up-your-environment&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/6LW11/hyX7UuCKvv/scqo1GTJXLkX1xavL6rjL1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eG9ea9/hyYb6z3EyW/tq1ux0xA9nyisMU12wTkPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b7ETJH/hyX7XdMbL7/J3uJUeJlHWCGh9iwdrAts1/img.png?width=1884&amp;amp;height=1324&amp;amp;face=0_0_1884_1324&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/set-up-your-environment&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://reactnative.dev/docs/set-up-your-environment&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/6LW11/hyX7UuCKvv/scqo1GTJXLkX1xavL6rjL1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eG9ea9/hyYb6z3EyW/tq1ux0xA9nyisMU12wTkPK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/b7ETJH/hyX7XdMbL7/J3uJUeJlHWCGh9iwdrAts1/img.png?width=1884&amp;amp;height=1324&amp;amp;face=0_0_1884_1324');&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;Set Up Your Environment &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In this guide, you'll learn how to set up your environment, so that you can run your project with Android Studio and Xcode. This will allow you to develop with Android emulators and iOS simulators, build your app locally, and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;reactnative.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u8f9g/btsL9vMaRFN/5JdQQOMtPR1mtguzluawkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u8f9g/btsL9vMaRFN/5JdQQOMtPR1mtguzluawkK/img.png&quot; data-alt=&quot;PowerShell를 관리자 권한으로 실행한 뒤 입력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u8f9g/btsL9vMaRFN/5JdQQOMtPR1mtguzluawkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu8f9g%2FbtsL9vMaRFN%2F5JdQQOMtPR1mtguzluawkK%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;1135&quot; height=&quot;133&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PowerShell를 관리자 권한으로 실행한 뒤 입력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. React Native 설치&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/getting-started-without-a-framework&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://reactnative.dev/docs/getting-started-without-a-framework&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1738759343129&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;Get Started Without a Framework &amp;middot; React Native&quot; data-og-description=&quot;If you have constraints that are not served well by a Framework, or you prefer to write your own Framework, you can create a React Native app without using a Framework.&quot; data-og-host=&quot;reactnative.dev&quot; data-og-source-url=&quot;https://reactnative.dev/docs/getting-started-without-a-framework&quot; data-og-url=&quot;https://reactnative.dev/docs/getting-started-without-a-framework&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qWugo/hyX7UBlSVp/9TyIRV03atCia7s9z3PZKk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cKNokD/hyX7RYW3yD/QDGFLAu26ncwSH7jBuDbG0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bMn8Eb/hyX7QFL6a5/uwzmHS4Q1Y2bXKXozHns1K/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/getting-started-without-a-framework&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://reactnative.dev/docs/getting-started-without-a-framework&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qWugo/hyX7UBlSVp/9TyIRV03atCia7s9z3PZKk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cKNokD/hyX7RYW3yD/QDGFLAu26ncwSH7jBuDbG0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bMn8Eb/hyX7QFL6a5/uwzmHS4Q1Y2bXKXozHns1K/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&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;Get Started Without a Framework &amp;middot; React Native&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;If you have constraints that are not served well by a Framework, or you prefer to write your own Framework, you can create a React Native app without using a Framework.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;reactnative.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 안드로이드 스튜디오 환경변수 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수 &amp;gt; 새로만들기 &amp;gt; 변수이름에 ANDROID_HOME&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C:\Users\${사용자이름}\AppData\Local\Android\Sdk&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Path &amp;gt; 편집 &amp;gt; %ANDROID_HOME%\platform-tools 추가&amp;nbsp;&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: #ee2323;&quot;&gt;&lt;b&gt;참고로 시스템 변수에다가 등록하는 것이 아닌, 사용자 변수에 추가해야함.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&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;b&gt;C:\Users\홍길동\AppData\Local\Android\Sdk&amp;nbsp;&lt;/b&gt;이렇게 돼있을 경우 에뮬레이터 실행이 안 되는 오류가 발생함.&amp;nbsp;&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;npx react-native run-android&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 입력해도 실행이 안 되고, 안드로이드 스튜디오에서 직접 실행해도 안 되는 경우가 있음. ( &lt;/span&gt;&lt;b&gt;The emulator process for AVD has terminated&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&gt;&lt;/p&gt;</description>
      <category>리액트</category>
      <category>android</category>
      <category>Android Studio</category>
      <category>react</category>
      <category>react native</category>
      <category>the emulator process for avd has terminated</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/123</guid>
      <comments>https://im-diary.tistory.com/123#entry123comment</comments>
      <pubDate>Wed, 5 Feb 2025 22:57:52 +0900</pubDate>
    </item>
    <item>
      <title>[axios] 인터셉터(intercepter)</title>
      <link>https://im-diary.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용한 프레임워크&lt;/b&gt;:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios, react-cookie&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 왜 사용했는지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만료시간이 다 된 토큰을 사용했을 때, 자동으로 represh token으로 access token을 재발급 받기 위해서 사용함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 cookie에 담아서 사용하기 때문에 react-cookie를 사용함.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;인터셉터(Interceptor&lt;/b&gt;)란? &lt;b&gt;요청(Request)&lt;/b&gt; 또는 &lt;b&gt;응답(Response)&lt;/b&gt;이 애플리케이션에서 처리되기 전에 가로채어 특정 작업을 수행할 수 있도록 해주는 중간 처리 메커니즘&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 사용 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1 먼저 &lt;b&gt;cookie&lt;/b&gt;를 전역에서 사용하기 위해 최상위 컴포넌트에 &lt;b&gt;&amp;lt;CookiesProvider&amp;gt;&lt;/b&gt;를 감싸준다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// App.tsx
useAxiosInterceptor();
return (
    &amp;lt;&amp;gt;
        &amp;lt;CookiesProvider&amp;gt;
            &amp;lt;Routes&amp;gt;
                &amp;lt;Route element={&amp;lt;MainContent/&amp;gt;}&amp;gt;
                    &amp;lt;Route path={MAIN_PATH()} element={&amp;lt;Main/&amp;gt;}/&amp;gt;
                    &amp;lt;Route path={SIGNUP_PATH()} element={&amp;lt;SignUp/&amp;gt;}/&amp;gt;
                    &amp;lt;Route path={LOGIN_PATH()} element={&amp;lt;Login/&amp;gt;}/&amp;gt;
                    &amp;lt;Route path={TEST_PATH()} element={&amp;lt;Test/&amp;gt;}/&amp;gt;
                    &amp;lt;Route path={CATEGORY_PATH()} element={&amp;lt;Category/&amp;gt;}/&amp;gt;
                &amp;lt;/Route&amp;gt;
            &amp;lt;/Routes&amp;gt;
        &amp;lt;/CookiesProvider&amp;gt;
    &amp;lt;/&amp;gt;
);&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;2-2 &lt;b&gt;useAxiosIntercepter.tsx&lt;/b&gt; 작성&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import React from 'react';
import axios from 'axios';
import { useCookies } from 'react-cookie';
import {LOGIN_PATH} from &quot;../constant&quot;;

const API_DOMAIN = process.env.REACT_APP_SERVER_DOMAIN + &quot;/api&quot;;

// 커스텀 훅으로 인터셉터 관리
export const useAxiosInterceptor = () =&amp;gt; {
    const [cookies, setCookie, removeCookie] = useCookies(['accessToken', 'refreshToken']);
    React.useEffect(() =&amp;gt; {
        // 요청 인터셉터
        // const requestInterceptor = axios.interceptors.request.use(
        //     (config) =&amp;gt; {
        //         const accessToken = cookies.accessToken;
        //         console.log(cookies);
        //         debugger;
        //         if (accessToken) {
        //             config.headers.Authorization = `Bearer ${accessToken}`;
        //         }
        //         return config;
        //     },
        //     (error) =&amp;gt; Promise.reject(error)
        // );

        // 응답 인터셉터
        const responseInterceptor = axios.interceptors.response.use(
            (response) =&amp;gt; response,
            async (error) =&amp;gt; {
                const originalRequest = error.config;
                if (error.response?.status === 401 &amp;amp;&amp;amp; !originalRequest._retry &amp;amp;&amp;amp; error.response?.data.code === 'EJT') {
                    originalRequest._retry = true;

                    try {
                        if (!cookies.refreshToken) {
                            console.log(&quot;refreshToken이 없습니다.&quot;);
                            return;
                        }
                        const refreshToken = cookies.refreshToken;
                        const response = await axios.post(`${API_DOMAIN}/auth/refresh`, { refreshToken });

                        const { accessToken, refreshToken: newRefreshToken, accessTokenExpiredMs, refreshTokenExpiredMs } = response.data;

                        const now = new Date().getTime();
                        const accessTokenExpires = new Date(now + accessTokenExpiredMs);
                        const refreshTokenExpires = new Date(now + refreshTokenExpiredMs);
                        // 새 토큰들 쿠키에 설정
                        setCookie('accessToken', accessToken, {
                            path: '/',
                            expires: accessTokenExpires
                        });

                        if (newRefreshToken) {
                            setCookie('refreshToken', newRefreshToken, {
                                path: '/',
                                expires: refreshTokenExpires
                                // 리프레시 토큰 만료 시간 설정
                            });
                        }

                        // 원래 요청 재시도
                        originalRequest.headers.Authorization = `Bearer ${accessToken}`;
                        return axios(originalRequest);
                    } catch (refreshError) {
                        // 토큰 갱신 실패 시 로그아웃 처리
                        removeCookie('accessToken');
                        removeCookie('refreshToken');

                        // 로그인 페이지로 리다이렉트 등 추가 처리
                        window.location.href = LOGIN_PATH();

                        return Promise.reject(refreshError);
                    }
                }

                return Promise.reject(error);
            }
        );

        // 클린업 함수: 컴포넌트 언마운트 시 인터셉터 제거
        return () =&amp;gt; {
            // axios.interceptors.request.eject(requestInterceptor);
            axios.interceptors.response.eject(responseInterceptor);
        };
    }, [cookies.accessToken, cookies.refreshToken, setCookie, removeCookie]);
};&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;API 서버에서 만료된 토큰일 경우 &lt;b&gt;401 &lt;/b&gt;응답 값과 &lt;b&gt;EJT &lt;/b&gt;에러 코드를 반환을 했으며, 다시 요청하지 않았을 경우에만 &lt;b&gt;${API_DOMAIN}/auth/refresh&lt;/b&gt;에 refreshToken을 보내서 accessToken을 요청&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;if (error.response?.status === 401 &amp;amp;&amp;amp; !originalRequest._retry &amp;amp;&amp;amp; error.response?.data.code === 'EJT') {&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 재귀요청 방지
originalRequest._retry = true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// refreshToken이 없으면 요청X
if (!cookies.refreshToken) {
    console.log(&quot;refreshToken이 없습니다.&quot;);
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// accessToken이 필요한 api를 호출하니깐 원래 요청을 다시 요청할 때 header에 accessToken을 담아서 요청
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axios(originalRequest); // 원래 요청 재시도&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 response 인터셉터만 필요하기 때문에 해당 내용만 정리.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request는 필요하면 나중에 추가 정리.&lt;/p&gt;</description>
      <category>리액트</category>
      <category>axios</category>
      <category>Intercepter</category>
      <category>response</category>
      <category>리액트</category>
      <category>인터셉터</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/122</guid>
      <comments>https://im-diary.tistory.com/122#entry122comment</comments>
      <pubDate>Tue, 7 Jan 2025 22:06:48 +0900</pubDate>
    </item>
    <item>
      <title>[JWT] JwtProvider</title>
      <link>https://im-diary.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://im-diary.tistory.com/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;이전 글: https://im-diary.tistory.com/120&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732190093565&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;[JWT] JwtFilter&quot; data-og-description=&quot;이전 글: https://im-diary.tistory.com/119&amp;nbsp;[JWT] Security Config 6.x.xcommon/config/WebSecurityConfig.java (전체코드)@Configuration@EnableWebSecurity@RequiredArgsConstructorpublic class WebSecurityConfig { private final JwtAuthenticationFilter jwtAu&quot; data-og-host=&quot;im-diary.tistory.com&quot; data-og-source-url=&quot;https://im-diary.tistory.com/120&quot; data-og-url=&quot;https://im-diary.tistory.com/120&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjTUt8/hyXDdfdS4o/VKwnfKs7BUtkxxU5kdSOCk/img.png?width=800&amp;amp;height=138&amp;amp;face=0_0_800_138,https://scrap.kakaocdn.net/dn/dX4Y3B/hyXDk6vlDz/L0O3By8H3H3UvSGpRclvH0/img.png?width=800&amp;amp;height=138&amp;amp;face=0_0_800_138,https://scrap.kakaocdn.net/dn/EQoTE/hyXDapgXVW/VDTTKwrjxANTBI1hWWg4AK/img.png?width=2217&amp;amp;height=829&amp;amp;face=0_0_2217_829&quot;&gt;&lt;a href=&quot;https://im-diary.tistory.com/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://im-diary.tistory.com/120&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjTUt8/hyXDdfdS4o/VKwnfKs7BUtkxxU5kdSOCk/img.png?width=800&amp;amp;height=138&amp;amp;face=0_0_800_138,https://scrap.kakaocdn.net/dn/dX4Y3B/hyXDk6vlDz/L0O3By8H3H3UvSGpRclvH0/img.png?width=800&amp;amp;height=138&amp;amp;face=0_0_800_138,https://scrap.kakaocdn.net/dn/EQoTE/hyXDapgXVW/VDTTKwrjxANTBI1hWWg4AK/img.png?width=2217&amp;amp;height=829&amp;amp;face=0_0_2217_829');&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;[JWT] JwtFilter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이전 글: https://im-diary.tistory.com/119&amp;nbsp;[JWT] Security Config 6.x.xcommon/config/WebSecurityConfig.java (전체코드)@Configuration@EnableWebSecurity@RequiredArgsConstructorpublic class WebSecurityConfig { private final JwtAuthenticationFilter jwtAu&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;im-diary.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;h3 data-ke-size=&quot;size23&quot;&gt;생성자&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Keys.hmacShaKeyFor(secret.getBytes())&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;secret&lt;/b&gt; 문자열을 바이트 배열로 변환한 후, hmacShaKeyFor 메서드를 사용하여 HMAC-SHA 알고리즘용 비밀키 객체를 생성&lt;/li&gt;
&lt;li&gt;**Keys.hmacShaKeyFor**는 &lt;b&gt;io.jsonwebtoken.security.Keys&lt;/b&gt; 클래스의 메서드로, JWT 서명에 사용되는 &lt;b&gt;SecretKey&lt;/b&gt; 객체를 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 &lt;b&gt;SecretKey&lt;/b&gt; 객체는 JWT의 서명을 생성하거나 검증할 때 사용&lt;/li&gt;
&lt;li&gt;이 과정에서 사용하는 키는 반드시 HMAC-SHA 알고리즘이 요구하는 최소 길이를 만족해야 합니다. 예를 들어, HMAC-SHA256의 경우 최소 256비트(32바이트)가 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Component
public class JwtProvider {


    public SecretKey secretKey;

    public JwtProvider(@Value(&quot;${jwt.secretKey}&quot;) String secret) {
        secretKey = Keys.hmacShaKeyFor(secret.getBytes());
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;create (토큰생성)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 만들어둔 SecretKey를 사용해서 토큰을 생성&amp;nbsp;&lt;/li&gt;
&lt;li&gt;아래 코드의 주석 참고&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.claims():&amp;nbsp;&lt;/b&gt;Map으로 여러 값 셋팅 가능 / &lt;b&gt;claim(): &lt;/b&gt;단일 값&amp;nbsp;&lt;/li&gt;
&lt;/ul&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 String create(String email, String role) {
    Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS)); // 엑세스 토큰 유효시간 1시간
    Map&amp;lt;String, String&amp;gt; claimsMap = Map.of(&quot;email&quot;, email, &quot;role&quot;, role);
    return Jwts.builder()
            .claims(claimsMap) // jwt 토큰 내 정보
            .expiration(expiredDate) // 만료시간
            .signWith(secretKey) // 암호화
            .issuedAt(Date.from(Instant.now())) // 발급 시간
            .subject(&quot;주제&quot;) // 토큰과 관련된 주요 대상  
            .compact();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;토큰에서 값 가져오기 및 검증&lt;/h4&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 String getEmail(String jwt) { // JWT 토큰의 유효성 검사
    Claims claims = validateToken().parseSignedClaims(jwt).getPayload();
    return claims.get(&quot;email&quot;, String.class);
}

public String getRole(String jwt) { // JWT 토큰의 유효성 검사
    Claims claims = validateToken().parseSignedClaims(jwt).getPayload();
    return claims.get(&quot;role&quot;, String.class);
}

private JwtParser validateToken() {
    try{
        return Jwts.parser().verifyWith(secretKey).build();
    } catch (SecurityException | MalformedJwtException e) {
        // 서명 검증 실패 시 처리
        throw new IllegalArgumentException(&quot;Invalid JWT signature&quot;, e);
    } catch (ExpiredJwtException e) {
        // JWT가 만료된 경우 처리
        throw new IllegalArgumentException(&quot;Expired JWT token&quot;, e);
    } catch (Exception e) {
        // 기타 예외 처리
        throw new IllegalArgumentException(&quot;Invalid JWT token&quot;, e);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체코드&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.my.cook_recipe.common.provider;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Map;

@Component
public class JwtProvider {


    public SecretKey secretKey;

    public JwtProvider(@Value(&quot;${jwt.secretKey}&quot;) String secret) {
        secretKey = Keys.hmacShaKeyFor(secret.getBytes());
    }

    public String create(String email, String role) {
        Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS)); // 엑세스 토큰 유효시간 1시간
        Map&amp;lt;String, String&amp;gt; claimsMap = Map.of(&quot;email&quot;, email, &quot;role&quot;, role);
        return Jwts.builder()
                .claims(claimsMap) // jwt 토큰 내 정보
                .expiration(expiredDate) // 만료시간
                .signWith(secretKey) // 암호화
                .compact();
    }

    public String getEmail(String jwt) { // JWT 토큰의 유효성 검사
        Claims claims = validateToken().parseSignedClaims(jwt).getPayload();
        return claims.get(&quot;email&quot;, String.class);
    }

    public String getRole(String jwt) { // JWT 토큰의 유효성 검사
        Claims claims = validateToken().parseSignedClaims(jwt).getPayload();
        return claims.get(&quot;role&quot;, String.class);
    }

    private JwtParser validateToken() {
        try{
            return Jwts.parser().verifyWith(secretKey).build();
        } catch (SecurityException | MalformedJwtException e) {
            // 서명 검증 실패 시 처리
            throw new IllegalArgumentException(&quot;Invalid JWT signature&quot;, e);
        } catch (ExpiredJwtException e) {
            // JWT가 만료된 경우 처리
            throw new IllegalArgumentException(&quot;Expired JWT token&quot;, e);
        } catch (Exception e) {
            // 기타 예외 처리
            throw new IllegalArgumentException(&quot;Invalid JWT token&quot;, e);
        }
    }


}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-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>JWT</category>
      <category>jwt provider</category>
      <category>스프링시큐리티</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/121</guid>
      <comments>https://im-diary.tistory.com/121#entry121comment</comments>
      <pubDate>Thu, 21 Nov 2024 21:06:42 +0900</pubDate>
    </item>
    <item>
      <title>[JWT] JwtFilter</title>
      <link>https://im-diary.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;이전 글: &lt;a href=&quot;https://im-diary.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://im-diary.tistory.com/119&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732109721472&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;[JWT] Security Config 6.x.x&quot; data-og-description=&quot;common/config/WebSecurityConfig.java (전체코드)@Configuration@EnableWebSecurity@RequiredArgsConstructorpublic class WebSecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public BCryptPasswordEncoder bCryptPasswordEnco&quot; data-og-host=&quot;im-diary.tistory.com&quot; data-og-source-url=&quot;https://im-diary.tistory.com/119&quot; data-og-url=&quot;https://im-diary.tistory.com/119&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iAeTF/hyXDgplvt1/zYCZHr4f7tHNp236xWquKK/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459,https://scrap.kakaocdn.net/dn/CWXiN/hyXzIuiXJT/aJfy721kYB6BpjKuKVJoKK/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459,https://scrap.kakaocdn.net/dn/cmDG5K/hyXC70e6ua/9fEdRCryjTHFnZPsGWGyE0/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459&quot;&gt;&lt;a href=&quot;https://im-diary.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://im-diary.tistory.com/119&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iAeTF/hyXDgplvt1/zYCZHr4f7tHNp236xWquKK/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459,https://scrap.kakaocdn.net/dn/CWXiN/hyXzIuiXJT/aJfy721kYB6BpjKuKVJoKK/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459,https://scrap.kakaocdn.net/dn/cmDG5K/hyXC70e6ua/9fEdRCryjTHFnZPsGWGyE0/img.png?width=689&amp;amp;height=459&amp;amp;face=0_0_689_459');&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;[JWT] Security Config 6.x.x&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;common/config/WebSecurityConfig.java (전체코드)@Configuration@EnableWebSecurity@RequiredArgsConstructorpublic class WebSecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public BCryptPasswordEncoder bCryptPasswordEnco&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;im-diary.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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;jwtAuthenticationFilter&lt;/b&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o-mini&quot; data-message-id=&quot;b0cd662d-2ebe-4622-b0f6-3a011ab021fc&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JwtAuthenticationFilter&lt;/b&gt;는 &lt;b&gt;JWT(JSON Web Token)&lt;/b&gt;를 사용한 인증을 처리하는 &lt;b&gt;필터&lt;/b&gt;이다. 이 필터는 스프링 시큐리티의 필터 체인에서 동작하며, 클라이언트가 보낸 JWT 토큰을 검증하고 인증 정보를 설정하는 역할을 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 역할:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;JWT 추출&lt;/b&gt;: 요청의 헤더에서 JWT 토큰을 추출한다. 일반적으로 Authorization 헤더에 &quot;Bearer &amp;lt;token&amp;gt;&quot; 형태로 JWT가 포함된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 검증&lt;/b&gt;: 추출한 JWT 토큰을 서버에서 사용 가능한 비밀 키를 통해 검증한다. 검증에 성공하면 해당 토큰에 포함된 사용자 정보를 추출할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication 설정&lt;/b&gt;: 토큰이 유효하면, JWT에 포함된 사용자 정보(예: 사용자 이름, 권한 등)를 바탕으로 Authentication 객체를 생성하고, 이를 스프링 시큐리티의 &lt;b&gt;SecurityContext&lt;/b&gt;에 설정하여 인증된 사용자로 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 실패 시 처리&lt;/b&gt;: JWT가 유효하지 않거나 만료된 경우, 예외를 던지거나 적절한 오류 응답을 반환한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예외는 exceptionHandling에서 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JwtAuthenticationFilter&lt;/b&gt; 전체 코드&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OncePerRequestFilter&lt;/b&gt;를 상속받은 JwtAuthenticationFilter의 &lt;b&gt;doFilterInternal&amp;nbsp;&lt;/b&gt;메서드를 실행&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;package com.my.cook_recipe.common.filter;

import com.my.cook_recipe.common.provider.JwtProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtProvider jwtProvider;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try{
            String token = parseBearerToken(request);
            if(token==null){
                filterChain.doFilter(request,response);
                return;
            }
            String email = jwtProvider.getEmail(token);
            if(email == null){
                filterChain.doFilter(request,response);
                return;
            }
            String role = jwtProvider.getRole(token);

            /*             // 권한부여X           */
//            AbstractAuthenticationToken authenticationToken =
//                    new UsernamePasswordAuthenticationToken(email,null, AuthorityUtils.NO_AUTHORITIES);

            /*          // 권한 여러개 부여    */
//            AbstractAuthenticationToken authenticationToken =
//                    new UsernamePasswordAuthenticationToken(email, null, Arrays.asList(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role),new SimpleGrantedAuthority(&quot;ROLE_&quot; + role) ));

            AbstractAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(email, null, Collections.singletonList(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role))); // 권한 부여

            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContext context = SecurityContextHolder.getContext();
            context.setAuthentication(authenticationToken);
//            SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); // 새로운 빈 보안 컨텍스트 생성
//            securityContext.setAuthentication(authenticationToken); // 앞서 생성한 인증 토큰을 보안 컨텍스트에 설정
        }catch (Exception e){
            e.printStackTrace();
        }
        filterChain.doFilter(request,response);
    }

    private String parseBearerToken(HttpServletRequest request){
        String authorization = request.getHeader(&quot;Authorization&quot;);
        boolean hasAuthorization = StringUtils.hasText(authorization);
        if(!hasAuthorization) return null;

        boolean isBearer = authorization.startsWith(&quot;Bearer &quot;); // &quot;Bearer &quot; 로 시작하느냐?
        if(!isBearer) return null;
        String token = authorization.substring(7);
        return token;
    }
}
&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;parseBearerToken(HttpServletRequest request)&lt;/b&gt; // 토큰을 가져오는 메서드&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;private String parseBearerToken(HttpServletRequest request){
    String authorization = request.getHeader(&quot;Authorization&quot;);
    boolean hasAuthorization = StringUtils.hasText(authorization);
    if(!hasAuthorization) return null;

    boolean isBearer = authorization.startsWith(&quot;Bearer &quot;); // &quot;Bearer &quot; 로 시작하느냐?
    if(!isBearer) return null;
    String token = authorization.substring(7);
    return token;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;String &lt;/b&gt;authorization &lt;b&gt;= &lt;/b&gt;request&lt;b&gt;.getHeader(&quot;Authorization&quot;); &lt;/b&gt;&lt;br /&gt;&lt;b&gt;boolean &lt;/b&gt;hasAuthorization &lt;b&gt;= StringUtils.hasText(&lt;/b&gt;authorization&lt;b&gt;);&lt;/b&gt; =&amp;gt; 헤더에 Authorization이 있는 지 확인한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;boolean&lt;/b&gt; isBearer = authorization.&lt;b&gt;startsWith&lt;/b&gt;(&quot;Bearer &quot;); // &quot;Bearer &quot; 로 시작하느냐? &lt;br /&gt;if(!isBearer) return null; &lt;br /&gt;&lt;b&gt;String&lt;/b&gt; token = authorization.&lt;b&gt;substring&lt;/b&gt;(7); &lt;br /&gt;&lt;b&gt;return&lt;/b&gt; token; =&amp;gt; 토큰 반환&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;나머지 간단한 설명 &lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;jwtProvider.getEmail(), .getRole()&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰에서 email과 role을 가져오는 메서드&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AbstractAuthenticationToken authenticationToken = &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new UsernamePasswordAuthenticationToken(email, null, Collections.singletonList(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role)));&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권한&amp;nbsp;부여&lt;/li&gt;
&lt;li&gt;&quot;ROLE_&quot;을 꼭 붙여주어야함.&lt;/li&gt;
&lt;li&gt;SecurityConfig -&amp;gt; .requestMatchers().hasRole(); 이 때 사용함. (뒤에 자세히 설명)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SecurityContext&amp;nbsp;context&amp;nbsp;=&amp;nbsp;SecurityContextHolder.getContext(); &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;authenticationToken.setDetails(new&amp;nbsp;WebAuthenticationDetailsSource().buildDetails(request)); &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context.setAuthentication(authenticationToken);&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setDetails() 메서드는 Authentication 객체의 &lt;b&gt;세부 정보를 설정&lt;/b&gt;하는 데 사용. 여기서 설정된 세부 정보는 주로 &lt;b&gt;웹 요청&lt;/b&gt;에 대한 추가적인 정보를 포함. WebAuthenticationDetailsSource는 이러한 세부 정보를 생성하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;context.setAuthentication(authenticationToken);
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 정보를 담아줌.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;            String email = jwtProvider.getEmail(token);
            if(email == null){
                filterChain.doFilter(request,response);
                return;
            }
            String role = jwtProvider.getRole(token);

            /*             // 권한부여X           */
//            AbstractAuthenticationToken authenticationToken =
//                    new UsernamePasswordAuthenticationToken(email,null, AuthorityUtils.NO_AUTHORITIES);

            /*          // 권한 여러개 부여    */
//            AbstractAuthenticationToken authenticationToken =
//                    new UsernamePasswordAuthenticationToken(email, null, Arrays.asList(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role),new SimpleGrantedAuthority(&quot;ROLE_&quot; + role) ));

            AbstractAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(email, null, Collections.singletonList(new SimpleGrantedAuthority(&quot;ROLE_&quot; + role))); // 권한 부여
            SecurityContext context = SecurityContextHolder.getContext();
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            context.setAuthentication(authenticationToken);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사용&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 테스트를 위해 로그인 기능 없이 토큰을 생성해서 클라이언트로 보내줌&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.my.cook_recipe;

import com.my.cook_recipe.common.provider.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class MainController {

    private final JwtProvider jwtProvider;

    @GetMapping(&quot;/&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; mainView(){
        String token = jwtProvider.create(&quot;test@naver.com&quot;, &quot;USER&quot;);
        return ResponseEntity.ok(token);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd3Wq6/btsKPeUkKJ5/1aTr7QE2hrpXkXtLDTjgCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd3Wq6/btsKPeUkKJ5/1aTr7QE2hrpXkXtLDTjgCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd3Wq6/btsKPeUkKJ5/1aTr7QE2hrpXkXtLDTjgCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd3Wq6%2FbtsKPeUkKJ5%2F1aTr7QE2hrpXkXtLDTjgCk%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;1273&quot; height=&quot;220&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해당 토큰을 헤더에 넣어서 서버로 요청&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1 현재 &quot;/api/user/**&quot;는 &quot;USER&quot; 권한을 가진 토큰만 접근을 허용시켜줌.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;.requestMatchers(&quot;/api/user/**&quot;).hasRole(&quot;USER&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIi3Zw/btsKRctRE5K/QWkZv81BYrd5HVNw1rzqMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIi3Zw/btsKRctRE5K/QWkZv81BYrd5HVNw1rzqMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIi3Zw/btsKRctRE5K/QWkZv81BYrd5HVNw1rzqMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIi3Zw%2FbtsKRctRE5K%2FQWkZv81BYrd5HVNw1rzqMk%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;615&quot; height=&quot;304&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnTz1j/btsKRTUVO7t/oD9I6krqoxms7kElyt4NK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnTz1j/btsKRTUVO7t/oD9I6krqoxms7kElyt4NK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnTz1j/btsKRTUVO7t/oD9I6krqoxms7kElyt4NK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnTz1j%2FbtsKRTUVO7t%2FoD9I6krqoxms7kElyt4NK1%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;1273&quot; height=&quot;332&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQi72/btsKQsjKSI8/NX8ryd948548qnjYffZlUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQi72/btsKQsjKSI8/NX8ryd948548qnjYffZlUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQi72/btsKQsjKSI8/NX8ryd948548qnjYffZlUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQi72%2FbtsKQsjKSI8%2FNX8ryd948548qnjYffZlUk%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;1293&quot; height=&quot;152&quot; data-origin-width=&quot;1293&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;2-2 내부 로직을 보면 &lt;b&gt;JwtAuthenticationFilter&lt;/b&gt;의 &lt;b&gt;doFilterInternal&lt;/b&gt;로 필터가 먼저 적용됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰에서 &quot;role&quot; 값을 꺼낸 뒤 아래 이미지처럼 권한을 부여해줌.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uhviB/btsKQEc7Ppy/8pDyka5FK2T5f4Zcbbp4d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uhviB/btsKQEc7Ppy/8pDyka5FK2T5f4Zcbbp4d0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uhviB/btsKQEc7Ppy/8pDyka5FK2T5f4Zcbbp4d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuhviB%2FbtsKQEc7Ppy%2F8pDyka5FK2T5f4Zcbbp4d0%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;1376&quot; height=&quot;66&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2217&quot; data-origin-height=&quot;829&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yGSTD/btsKRTtR77g/pwK5GyDEEka60Aggcz0DKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yGSTD/btsKRTtR77g/pwK5GyDEEka60Aggcz0DKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yGSTD/btsKRTtR77g/pwK5GyDEEka60Aggcz0DKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyGSTD%2FbtsKRTtR77g%2FpwK5GyDEEka60Aggcz0DKK%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;2217&quot; height=&quot;829&quot; data-origin-width=&quot;2217&quot; data-origin-height=&quot;829&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3 현재 권한은 &quot;USER&quot;로, hasRole의 권한과 일치하기 때문에 접근이 가능함.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XXzbR/btsKRfqBCA9/jvByQj3IUdkjB3JfkIGI9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XXzbR/btsKRfqBCA9/jvByQj3IUdkjB3JfkIGI9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XXzbR/btsKRfqBCA9/jvByQj3IUdkjB3JfkIGI9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXXzbR%2FbtsKRfqBCA9%2FjvByQj3IUdkjB3JfkIGI9K%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;37&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-4 만약 일치하지 않으면? 예외가 발생해서 &lt;b&gt;exceptionHandling&lt;/b&gt;이 작동하게 됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdWMOH/btsKPRqPwNk/LzEUXvo42AVqMASrT1kxTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdWMOH/btsKPRqPwNk/LzEUXvo42AVqMASrT1kxTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdWMOH/btsKPRqPwNk/LzEUXvo42AVqMASrT1kxTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdWMOH%2FbtsKPRqPwNk%2FLzEUXvo42AVqMASrT1kxTK%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;1231&quot; height=&quot;39&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내일 정리할 내용:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;jwtProvider&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Filter</category>
      <category>JWT</category>
      <category>jwt filter</category>
      <category>jwt token</category>
      <category>Security</category>
      <category>spring security</category>
      <category>스프링부트</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/120</guid>
      <comments>https://im-diary.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 20 Nov 2024 23:33:13 +0900</pubDate>
    </item>
    <item>
      <title>[JWT] Security Config 6.x.x</title>
      <link>https://im-diary.tistory.com/119</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;common/config/WebSecurityConfig.java (전체코드)&lt;/h4&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors(cors -&amp;gt; cors.configurationSource(corsConfigrationSourse()))
                .csrf(CsrfConfigurer::disable) // csrf disable
                .httpBasic(HttpBasicConfigurer::disable) // http basic 인증 방식 disable
                .formLogin(FormLoginConfigurer::disable) // form 로그인 방식 disable
                .sessionManagement(sessionManagement -&amp;gt; sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                ).authorizeHttpRequests(request -&amp;gt; request
                                .requestMatchers(&quot;/&quot;, &quot;/user/*&quot;, &quot;/css/**&quot;,&quot;/js/**&quot;).permitAll() // permitAll():  모든 권한 허용
                                .requestMatchers(HttpMethod.POST, &quot;/api/user/**&quot;).permitAll()
                                .requestMatchers(HttpMethod.GET, &quot;/api/board/**&quot;, &quot;/api/user/**&quot;).permitAll()
                                .anyRequest().authenticated()
                ).exceptionHandling(exceptionHandling -&amp;gt; exceptionHandling.authenticationEntryPoint(new FailedAuthenticationEntryPoint()))
                        .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return httpSecurity.build();
    }

    @Bean
    protected CorsConfigurationSource corsConfigrationSourse() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(&quot;*&quot;));
        configuration.setAllowedMethods(Arrays.asList(&quot;GET&quot;,&quot;POST&quot;,&quot;PUT&quot;,&quot;DELETE&quot;,&quot;PATCH&quot;));
        configuration.setAllowedHeaders(Arrays.asList(&quot;X-Requested-With&quot;, &quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;X-XSRF-token&quot;));
        configuration.setAllowCredentials(false);
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, configuration);
        return source;
    }
}
class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType(&quot;application/json&quot;);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write(&quot;{\&quot;code:\&quot;: \&quot;AF\&quot;, \&quot;message\&quot;: \&quot;Authorization Failed.\&quot;}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. httpSecurity.cors()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.1) CORS&lt;/b&gt;(Cross-Origin Resource Sharing) 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 도메인에서 API를 호출할 때 허용 여부를 제어하는 규칙을 설정&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt; configuration.setAllowedOrigins(Arrays.asList(&quot;*&quot;))&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 도메인에서의 요청을 허용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; configuration.setAllowedMethods(Arrays.asList(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;PATCH&quot;))&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;허용하는 메서드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; configuration.setAllowedHeaders(Arrays.asList(&quot;X-Requested-With&quot;, &quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;X-XSRF-token&quot;)) &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 보낼 수 있는 요청 헤더를 지정&lt;/li&gt;
&lt;li&gt;주로 인증에 필요한 Authorization 헤더 등을 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.notion.so/iamsihwa/JWT-Security-setAllowCredentials-false-143d296d2ce38050bfe0c3085aa6d064?pvs=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;configuration.setAllowCredentials(false)&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&amp;nbsp; (링크O)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키를 포함한 &lt;b&gt;자격 증명&lt;/b&gt;을 허용하지 않도록 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;configuration.setMaxAge(3600L)&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐싱 가능한 시간(초)을 설정. 3600초(1시간) 동안 CORS 결과를 캐시.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;source.registerCorsConfiguration(&quot;/**&quot;, configuration);&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 URL 경로에 대해 위의 CORS 설정을 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;@Bean
protected CorsConfigurationSource corsConfigrationSourse() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList(&quot;*&quot;));
    configuration.setAllowedMethods(Arrays.asList(&quot;GET&quot;,&quot;POST&quot;,&quot;PUT&quot;,&quot;DELETE&quot;,&quot;PATCH&quot;));
    configuration.setAllowedHeaders(Arrays.asList(&quot;X-Requested-With&quot;, &quot;Content-Type&quot;, &quot;Authorization&quot;, &quot;X-XSRF-token&quot;));
    configuration.setAllowCredentials(false);
    configuration.setMaxAge(3600L);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration(&quot;/**&quot;, configuration);
    return source;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. CSRF&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF는 사용자가 인증된 상태에서 악의적인 웹사이트가 사용자를 대신해 요청을 보내는 공격&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 사용자의 쿠키를 이용해 사용자의 권한으로 서버에 요청을 보냄&lt;/li&gt;
&lt;li&gt;주로 &lt;b&gt;쿠키 기반 인증&lt;/b&gt;에서 문제가 발생&lt;br /&gt;(예: 세션 ID가 쿠키에 저장된 상태에서 쿠키가 자동으로 서버에 전송되는 상황)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.1) JWT와 CSRF&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;JWT는 클라이언트가 명시적으로 헤더에 추가해야 서버가 인증 요청을 처리&lt;/li&gt;
&lt;li&gt;CSRF는 서버가 쿠키를 자동으로 전송하는 점을 악용하는 공격인데, JWT는 쿠키를 사용하지 않아 이러한 취약점에서 자유롭다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 인증은 쿠키가 아닌 &lt;b&gt;HTTP 헤더&lt;/b&gt;(주로 Authorization: Bearer &amp;lt;JWT&amp;gt; 헤더)를 통해 토큰을 전송하기 때문에 &lt;b&gt;CSRF 공격이 어렵다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.3) 왜 CSRF를 disable해야 하는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF 방어는 쿠키를 기반으로 동작&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security는 기본적으로 CSRF 보호를 활성화하며, 이를 위해 CSRF 토큰을 쿠키로 전달하고 확인&lt;/li&gt;
&lt;li&gt;그러나 JWT 방식은 쿠키 기반 세션 관리가 아닌, &lt;b&gt;stateless&lt;/b&gt;(상태를 저장하지 않는) 방식으로 동작.&lt;/li&gt;
&lt;li&gt;따라서 CSRF 보호를 유지하면 불필요한 오버헤드가 생기고, JWT 인증 로직에 혼란을 줄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Stateless 환경에서 CSRF 방어가 불필요한 이유&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;JWT 인증 방식에서는 &lt;b&gt;서버가 클라이언트의 상태를 유지 X&lt;/b&gt;&lt;br /&gt;즉, 세션이 없으므로 CSRF 토큰을 검증할 서버 측 데이터가 없음&lt;/li&gt;
&lt;li&gt;또한, CSRF 공격은 사용자가 신뢰하지 않는 사이트를 방문하여 발생하므로, &lt;b&gt;CORS 정책&lt;/b&gt;과 함께 사용하면 추가적인 방어가 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&amp;nbsp;3. httpBasic (HttpBasicConfigurer::disable)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP Basic 인증 방식 비활성화:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP Basic은 사용자 이름과 비밀번호를 HTTP 요청 헤더에 포함시켜 인증하는 방식&lt;/li&gt;
&lt;li&gt;이 코드는 Basic 인증 대신 JWT를 사용하기 때문에 이를 비활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. formLogin(FormLoginConfigurer::disable)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폼 로그인 방식 비활성화&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security에서 제공하는 기본 로그인 페이지와 로그인 처리를 비활성화&lt;/li&gt;
&lt;li&gt;JWT 기반 인증에서는 클라이언트가 직접 로그인 요청을 처리하고, 토큰을 발급받으므로 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. sessionManagement(sessionManagement -&amp;gt; sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&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;SessionCreationPolicy.STATELESS: 서버가 사용자의 세션을 관리하지 않도록 설정&lt;/li&gt;
&lt;li&gt;JWT는 상태를 유지하지 않는(stateless) 방식으로 동작하므로, 세션을 사용하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. authorizeHttpRequests&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;authenticated(): 인증된 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;fullyAuthenticated(): 완전히 인증된 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;hasRole(String role): 특정 역할을 가진 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;hasAnyRole(String... roles): 주어진 역할 중 하나라도 가진 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;hasAuthority(String authority): 특정 권한을 가진 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;hasAnyAuthority(String... authorities): 주어진 권한 중 하나라도 가진 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;denyAll(): 모든 접근을 거부&lt;/li&gt;
&lt;li&gt;anonymous(): 익명 사용자만 접근을 허용&lt;/li&gt;
&lt;li&gt;rememberMe(): &quot;Remember Me&quot; 인증을 통해 인증된 사용자만 접근을 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;.authorizeHttpRequests(request -&amp;gt; request
                .requestMatchers(&quot;/&quot;, &quot;/user/*&quot;, &quot;/css/**&quot;,&quot;/js/**&quot;).permitAll() // permitAll():  모든 권한 허용
                .requestMatchers(HttpMethod.POST, &quot;/api/user/**&quot;).permitAll()
                .requestMatchers(HttpMethod.GET, &quot;/api/board/**&quot;, &quot;/api/user/**&quot;).hasAnyRole(&quot;ADMIN&quot;, &quot;USER&quot;)
                .anyRequest().authenticated()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. exceptionHandling&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 실패 시 동작을 정의함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;authenticationEntryPoint(new FailedAuthenticationEntryPoint())
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인증 실패 시 커스텀한 동작을 수행하도록 설정.&lt;/li&gt;
&lt;li&gt;아래 코드를 예로 들어서 설명하자면, 허용되지 않은 url로 접근을 하거나, 인증이 실패한 경우 &quot;application/json&quot; 타입으로 401 코드와 같이 이미지와 같은 메시지를 출력해줌.&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZRH4/btsKPcgQF8K/y8jNKrtITJk2oGkxAry951/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZRH4/btsKPcgQF8K/y8jNKrtITJk2oGkxAry951/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZRH4/btsKPcgQF8K/y8jNKrtITJk2oGkxAry951/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZRH4%2FbtsKPcgQF8K%2Fy8jNKrtITJk2oGkxAry951%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;689&quot; height=&quot;459&quot; data-origin-width=&quot;689&quot; data-origin-height=&quot;459&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;vbscript&quot;&gt;&lt;code&gt;class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType(&quot;application/json&quot;);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write(&quot;{\&quot;code:\&quot;: \&quot;AF\&quot;, \&quot;message\&quot;: \&quot;Authorization Failed.\&quot;}&quot;);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. addFilterBefore&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JWT 인증 필터&lt;/b&gt;를 Spring Security 필터 체인에 추가&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UsernamePasswordAuthenticationFilter 앞에 jwtAuthenticationFilter를 추가하여 JWT 인증을 처리하도록 설정.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>6.x.x</category>
      <category>JWT</category>
      <category>Security</category>
      <category>security  6.3</category>
      <category>security 6.3.4</category>
      <category>security config</category>
      <category>개발</category>
      <category>공부</category>
      <category>스프링</category>
      <category>오블완</category>
      <category>코딩</category>
      <category>티스토리챌린지</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/119</guid>
      <comments>https://im-diary.tistory.com/119#entry119comment</comments>
      <pubDate>Tue, 19 Nov 2024 21:46:38 +0900</pubDate>
    </item>
    <item>
      <title>[JWT] 프로젝트 생성1</title>
      <link>https://im-diary.tistory.com/118</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;필수 의존성&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Web&lt;/li&gt;
&lt;li&gt;Spring Security&lt;/li&gt;
&lt;li&gt;Spring Data JPA&lt;/li&gt;
&lt;li&gt;DB Driver (Maria DB Driver)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JWT 필수 의존성&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 토큰을 생성, 관리를 위해 JWT 의존성을 필수로 설정해야 함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 최신 버전은 0.12.6&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api/0.12.6&quot;&gt;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api/0.12.6&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// build.gradle
dependencies {
    implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
    implementation 'io.jsonwebtoken:jjwt-impl:0.12.6'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.12.6'
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>JWT</category>
      <category>Security</category>
      <category>Spring</category>
      <category>시큐리티</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/118</guid>
      <comments>https://im-diary.tistory.com/118#entry118comment</comments>
      <pubDate>Mon, 18 Nov 2024 21:17:46 +0900</pubDate>
    </item>
    <item>
      <title>[오류] 배포 중 로DB 접근 관련 오류</title>
      <link>https://im-diary.tistory.com/117</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 오류 내용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 관련 공부를 진행하던 중 오류가 발생함.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;java.sql.SQLNonTransientConnectionException: Socket fail to connect to host:address=(host=172.30.1.32)(port=3307)(type=primary). Connect timed out&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 연결에 실패했다는 로그가 찍힌 것을 보고 문제 해결에 나섬.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 DB 서버는 어떻게 해야할 지 고민이라, 현재 사용하고 있는 컴퓨터에 DB를 설치 후 개방해서 임시로 사용하기로 정함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 해결 과정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 방화벽 포트를 개방해줌.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZEHb/btsJAjtMNUj/lhvcJRqX4PSaU5gJ9xhV7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZEHb/btsJAjtMNUj/lhvcJRqX4PSaU5gJ9xhV7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZEHb/btsJAjtMNUj/lhvcJRqX4PSaU5gJ9xhV7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZEHb%2FbtsJAjtMNUj%2FlhvcJRqX4PSaU5gJ9xhV7K%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;1527&quot; height=&quot;926&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;926&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;blockquote data-ke-style=&quot;style3&quot;&gt;여기서 이바운드 규칙이란?&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부에서 내 컴퓨터로 접근할 수 있는 규칙을 의미함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 포트포워딩&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소창에 172.30.1.254를 입력 후 포트 포워딩 설정을 해주었음. (kt 기준)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/11PMN/btsJznjvYwF/lN7N8WGNK8iKPyYrbmOykK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/11PMN/btsJznjvYwF/lN7N8WGNK8iKPyYrbmOykK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/11PMN/btsJznjvYwF/lN7N8WGNK8iKPyYrbmOykK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F11PMN%2FbtsJznjvYwF%2FlN7N8WGNK8iKPyYrbmOykK%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;221&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;221&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;3) DB 계정을 외부에서 접근이 가능하도록 권한 설정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;계정 생성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;userid&lt;/b&gt; : 생성할 ID를 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;% : &lt;/b&gt;허용 위치를 적어주면 됨. %는 모든 접근을 허용하겠다는 의미&lt;/li&gt;
&lt;li&gt;&lt;b&gt;'password' :&amp;nbsp;&lt;/b&gt;비밀번호 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CREATE USER 'userid'@'%' IDENTIFIED BY 'password';&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확인&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;USE mysql;&lt;br /&gt;CREATE USER 'userid'@'%' IDENTIFIED BY 'password';&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csmlnh/btsJyMdqBaW/dkzG890k8SSCLnnKuslzj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csmlnh/btsJyMdqBaW/dkzG890k8SSCLnnKuslzj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csmlnh/btsJyMdqBaW/dkzG890k8SSCLnnKuslzj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsmlnh%2FbtsJyMdqBaW%2FdkzG890k8SSCLnnKuslzj0%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;450&quot; height=&quot;387&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결과&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1711&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRPJKf/btsJyiqhBPf/6lMXAFKOJ9CB0RwlGQ7yjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRPJKf/btsJyiqhBPf/6lMXAFKOJ9CB0RwlGQ7yjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRPJKf/btsJyiqhBPf/6lMXAFKOJ9CB0RwlGQ7yjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRPJKf%2FbtsJyiqhBPf%2F6lMXAFKOJ9CB0RwlGQ7yjk%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;1711&quot; height=&quot;274&quot; data-origin-width=&quot;1711&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRm0p/btsJzRqUuJv/K3QY2D7Nu0DUHDDzrzMVg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRm0p/btsJzRqUuJv/K3QY2D7Nu0DUHDDzrzMVg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRm0p/btsJzRqUuJv/K3QY2D7Nu0DUHDDzrzMVg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlRm0p%2FbtsJzRqUuJv%2FK3QY2D7Nu0DUHDDzrzMVg0%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;498&quot; height=&quot;254&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨날 코드만 작성했 지 이런 환경 셋팅같은 건 처음 해봐서 많이 헤맸었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그대로 혼자 하나 씩 문제를 해결해 나가는 과정이 재밌기도 했으며, 성취감이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부한 내용들을 까먹지 않기 위해서 정리를 해야하는데 생각보다 쉽지가 않다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 계획:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 이번에 했던 DB 관련된 내용 정리하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 로그 모니터링 기능 개발&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프론트 배포 방법 공부 및 정리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 자료구조, 알고리즘 공부 및 정리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할 수 있다!!! 화이팅~~~~~~~~~&lt;/p&gt;</description>
      <category>기타</category>
      <category>db</category>
      <category>MySQL</category>
      <category>오류</category>
      <author>나는시화</author>
      <guid isPermaLink="true">https://im-diary.tistory.com/117</guid>
      <comments>https://im-diary.tistory.com/117#entry117comment</comments>
      <pubDate>Wed, 11 Sep 2024 23:07:02 +0900</pubDate>
    </item>
  </channel>
</rss>