<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>choigoyo_o.log</title>
        <link>https://velog.io/</link>
        <description>i'm best</description>
        <lastBuildDate>Sat, 21 Sep 2024 12:43:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>choigoyo_o.log</title>
            <url>https://velog.velcdn.com/images/choigoyo_o/profile/cfd507ac-747c-4c3e-bd83-f867b8862cb2/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. choigoyo_o.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/choigoyo_o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[flutter] MacOs Sequioa 15.0 업데이트 후 Xcode error ]]></title>
            <link>https://velog.io/@choigoyo_o/flutter-MacOs-Sequioa-15.0-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%9B%84-X-code-error</link>
            <guid>https://velog.io/@choigoyo_o/flutter-MacOs-Sequioa-15.0-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%ED%9B%84-X-code-error</guid>
            <pubDate>Sat, 21 Sep 2024 12:43:22 GMT</pubDate>
            <description><![CDATA[<h2 id="evaluatejavascript_completionhandler">evaluateJavaScript(_:completionHandler:)</h2>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/1cbef4a7-3d42-490d-8fec-80f576b1ec45/image.png" alt=""></p>
<p>해당 오류는 </p>
<pre><code class="language-swift">// 변경 할 코드 
 public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -&gt; Void)? = nil) {</code></pre>
<pre><code class="language-swift">// 변경 코드
 public override func evaluateJavaScript(_ javaScriptString: String, completionHandler: (@MainActor @Sendable (Any?, (any Error)?) -&gt; Void)? = nil) {</code></pre>
<p>위 처럼 코드를 수정해주면 InAppWebView 에서 발생하는 오류는 사라진다.</p>
<h2 id="include-of-non-modular-error">include of non-modular error</h2>
<p><a href="https://stackoverflow.com/questions/66148505/flutter-include-of-non-modular-header-inside-framework-module-firebase-core-fl">https://stackoverflow.com/questions/66148505/flutter-include-of-non-modular-header-inside-framework-module-firebase-core-fl</a>
글을 참고하여 오류를 수정하였음 </p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/f2783a54-211e-47e5-a747-9af3bedd4f9a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/10c10b34-b7ee-48d1-b001-6fbc337e47d2/image.png" alt=""></p>
<h2 id="command-phasescriptexecution-failed-with-a-nonzero-exit-code">Command PhaseScriptExecution failed with a nonzero exit code</h2>
<p>이 오류가 발생했을 때 여러 글에서 사진의 44번째 줄의 </p>
<pre><code>// 1번 코드 
source=&quot;$(readlink &quot;${source}&quot;)&quot;</code></pre><pre><code>// 수정을 권한 2번 코드 
source=&quot;$(readlink -f &quot;${source}&quot;)&quot;</code></pre><p>1번 코드를 2번 코드로 -f 를 추가하면 된다고 하였지만 코드에는 이미 -f가 추가되어있는 상태였다</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/77413b4e-9f74-4cb0-b2a0-7500866e2248/image.png" alt=""></p>
<p>해당 오류는 </p>
<p>Runner - TARGETS의 Runner - build Phases - FlutterFire: &quot;flutterfire upload-crashlytics-symbols&quot; 에서 Run script 체크란에  For install builds only 옵션을 체크하여 해결하였다...</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/5518232f-010e-4bb5-8630-45678a9f0814/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Flutter SingleChildScrollView 내부에 Expanded 위젯 사용시 에러 해결방법 ]]></title>
            <link>https://velog.io/@choigoyo_o/Flutter-SingleChildScrollView-%EB%82%B4%EB%B6%80%EC%97%90-Expanded-%EC%9C%84%EC%A0%AF-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@choigoyo_o/Flutter-SingleChildScrollView-%EB%82%B4%EB%B6%80%EC%97%90-Expanded-%EC%9C%84%EC%A0%AF-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 18 Dec 2023 05:07:11 GMT</pubDate>
            <description><![CDATA[<p>화면 비율에따라 맞춰 구현하려 expanded를 사용하였는데 
키보드를 사용해야 하는 경우가 생겨 SingleChildScrollView를 사용하려하니 overflow 문제가 생겼었다.
해당 문제에 대해서는 CustomScrollView를 사용하여 해결하였다.</p>
<p><a href="https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html">https://api.flutter.dev/flutter/widgets/CustomScrollView-class.html</a></p>
<pre><code class="language-dart">CustomScrollView(
  slivers: [
    SliverFillRemaining(
      hasScrollBody: false,
      child: Column(
        children: &lt;Widget&gt;[
          const Text(&#39;hellooo&#39;),
          Expanded(child: Container(color: Colors.red)),
          Expanded(child: Container(color: Colors.red)),
          Expanded(child: Container(color: Colors.red)),
          const Text(&#39;hello&#39;),
        ],
      ),
    ),
  ],
)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Xcode 에서  B/BL out of range 135384124 (max +/-128MB) to '' 를 해결한 방법]]></title>
            <link>https://velog.io/@choigoyo_o/Xcode-%EC%97%90%EC%84%9C-BBL-out-of-range-135384124-max-128MB-to-%EB%A5%BC-%ED%95%B4%EA%B2%B0%ED%95%9C-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@choigoyo_o/Xcode-%EC%97%90%EC%84%9C-BBL-out-of-range-135384124-max-128MB-to-%EB%A5%BC-%ED%95%B4%EA%B2%B0%ED%95%9C-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 20 Oct 2023 01:58:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/487edd8a-69ca-4a6b-b5a7-4ca2554e56c9/image.png" alt="">
xcode </p>
<p>아래 두가지 오류를 해결한 방법 </p>
<pre><code class="language-swift">Linker command failed with exit code 1 (use -v to see invocation)</code></pre>
<pre><code class="language-swift">B/BL out of range 135384124 (max +/-128MB) to &#39;&#39;</code></pre>
<p>TARGETS</p>
<p> Runner → build Settings → Linking - General → Other Linker Flags</p>
<p>에서 + 를 눌러 아래 코드를 추가 해준다</p>
<pre><code class="language-swift">-ld64</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/3ae1edcd-a817-4513-9cd2-e6e91d5c2184/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[문자, 문자열 리터럴 그리고 문자열 결합 개념정리]]></title>
            <link>https://velog.io/@choigoyo_o/%EB%AC%B8%EC%9E%90-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B0%ED%95%A9-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@choigoyo_o/%EB%AC%B8%EC%9E%90-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%AC%B8%EC%9E%90%EC%97%B4-%EA%B2%B0%ED%95%A9-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 23 May 2023 11:24:07 GMT</pubDate>
            <description><![CDATA[<h1 id="문자-문자열-리터럴-그리고-문자열-결합">문자, 문자열 리터럴 그리고 문자열 결합</h1>
<hr>

<h3 id="문자">문자</h3>
<p>문자는 단일한 문자를 나타내는 데이터 타입입니다. 자바에서 문자는 <code>char</code> 데이터 타입으로 표현되며, 작은따옴표 <code>&#39;&#39;</code>로 감싸서 표기합니다.</p>
<pre><code class="language-java">char c = &#39;A&#39;;
System.out.println(c); // 출력: A

char c1 =&#39;AB&#39;; // 에러</code></pre>
<hr>

<h3 id="문자열">문자열</h3>
<p>문자열은 여러 개의 문자로 구성된 데이터 타입입니다. 자바에서 문자열은 <code>String</code> 클래스로 표현되며, 큰따옴표 <code>&quot;&quot;</code>로 감싸서 표기합니다.</p>
<pre><code class="language-java">String str = &quot;Hello&quot;;
System.out.println(str); // 출력: Hello</code></pre>
<hr>
문자열은 연속된 여러 문자로 볼 수 있습니다.

<pre><code class="language-java">String s = &quot;A&quot;;
String s1 = &quot;&quot;; // 허용
char = &#39;&#39;; // 에러</code></pre>
<hr>

<h3 id="문자열-결합">문자열 결합</h3>
<ol>
<li><p>문자열과 문자열 결합 : 두 개의 문자열을 결합하여 새로운 문자열을 생성합니다.</p>
<pre><code class="language-java">String str1 = &quot;Hello&quot;;
String str2 = &quot;World&quot;;
String result = str1 + str2;
System.out.println(result); // 출력: HelloWorld</code></pre>
<br>
</li>
<li><p>문자열과 다른 데이터 타입 결합: 문자열과 다른 데이터 타입(정수, 실수, 문자 등)을 결합할 때 자바는 자동으로 해당 데이터를 문자열로 변환하여 결합합니다.</p>
<pre><code class="language-java">String name = &quot;John&quot;;
int age = 25;
String message = &quot;My name is &quot; + name + &quot; and I am &quot; + age + &quot; years old.&quot;;
System.out.println(message); // 출력: My name is John and I am 25 years old.</code></pre>
<br>

</li>
</ol>
<p>즉, 
<code>문자열</code> + <code>any type</code>  =  <code>문자열</code> </p>
<p><code>any type</code>+<code>문자열</code> =  <code>문자열</code> </p>
<br>

<br>
<br>

<ol start="3">
<li>문자열과 변수 결합: 변수의 값을 문자열과 결합할 수 있습니다.<pre><code class="language-java">int number = 42;
String text = &quot;The number is &quot; + number;
System.out.println(text); // 출력: The number is 42</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java와 spring boot 게시판 프로젝트 - 회원 가입 이메일 인증]]></title>
            <link>https://velog.io/@choigoyo_o/Java%EC%99%80-spring-boot-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@choigoyo_o/Java%EC%99%80-spring-boot-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EC%9B%90-%EA%B0%80%EC%9E%85-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Tue, 09 May 2023 12:55:14 GMT</pubDate>
            <description><![CDATA[<h2 id="회원가입-폼-만들기">회원가입 폼 만들기</h2>
<p>회원가입을 위해 클라이언트 입장에서 정보를 입력할 폼이 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/20f44d27-7e02-42be-8b46-767e479f6837/image.png" alt=""></p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot; xmlns:layout=&quot;http://www.ultraq.net.nz/thymeleaf/layout&quot;
      layout:decorate=&quot;layout/default_layout&quot;&gt;
&lt;!-- 현재 화면에서만 사용하는 js --&gt;
&lt;head&gt;
    &lt;script th:inline=&quot;javascript&quot; th:src=&quot;@{/js/memberInfoCheck.js}&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;div&gt;
    &lt;th:block layout:fragment=&quot;content&quot;&gt;
        &lt;!-- 회원가입 폼 --&gt;
        &lt;div class=&quot;container-md shadow p-3 mb-5 mt-5 w-50 bg-body rounded&quot;&gt;
            &lt;form id=&quot;SignUpForm&quot;  onsubmit=&quot;SignUpFormSumitCheck(event)&quot; action=&quot;/member/save&quot; method=&quot;post&quot; &gt;
                &lt;h3 class=&quot;center text-center&quot;&gt;회원가입&lt;/h3&gt;
                &lt;span id=&quot;warningMsg&quot;&gt;&lt;/span&gt;
                &lt;div class=&quot;mb-3&quot;&gt;
                    &lt;label class=&quot;form-label&quot;&gt;Email&lt;/label&gt;
                    &lt;div class=&quot;row&quot;&gt;
                        &lt;div class=&quot;col-10&quot;&gt;
                            &lt;input type=&quot;email&quot; class=&quot;form-control&quot; name=&quot;email&quot; id=&quot;email&quot; onblur=&quot;emailDuplicateCheck()&quot; placeholder=&quot;이메일을 입력해주세요&quot;&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;col-2&quot;&gt;
                            &lt;input class=&quot;btn btn-outline-primary&quot;  name=&quot;checkEmailNumber&quot; id=&quot;checkEmailNumber&quot;  type=&quot;button&quot; value=&quot;인증번호&quot; onclick=&quot;sendEmail()&quot;&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;span id=&quot;checkEmail&quot;&gt;
                 &lt;!-- 사용가능 이메일 확인여부 출력 하기  --&gt;
            &lt;/span&gt;
                &lt;/div&gt;
                &lt;div class=&quot;mb-3&quot;&gt;
                    &lt;label class=&quot;form-label&quot;&gt;인증번호&lt;/label&gt;
                    &lt;div class=&quot;row&quot;&gt;
                        &lt;div class=&quot;col-10&quot;&gt;
                            &lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;inputCode&quot; id=&quot;inputCode&quot; maxlength=&quot;6&quot; placeholder=&quot;인증번호를 입력해주세요&quot; &gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;col-2&quot;&gt;
                            &lt;input class=&quot;btn btn-outline-primary&quot;  name=&quot;check&quot; id=&quot;check&quot;  type=&quot;button&quot; value=&quot;확인하기&quot; onclick=&quot;verifyCode()&quot;&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                    &lt;div class=&quot;col-auto&quot;&gt;
                        &lt;!--&lt;span id=&quot;#&quot; class=&quot;#&quot;&gt;
                             일치 / 불일치 결과 출력 (미진행)
                        &lt;/span&gt;--&gt;
                    &lt;/div&gt;
                    &lt;div class=&quot;mb-3&quot;&gt;
                        &lt;label class=&quot;form-label&quot; for=&quot;password&quot;&gt;Password&lt;/label&gt;
                        &lt;input type=&quot;password&quot; class=&quot;form-control&quot; name=&quot;password&quot; id=&quot;password&quot; placeholder=&quot;비밀번호를 입력해주세요&quot; aria-describedby=&quot;passwordHelpInline&quot;&gt;
                        &lt;span id=&quot;password-strength&quot;&gt;&lt;/span&gt;
                        &lt;div class=&quot;mb-3&quot;&gt;
                            &lt;label class=&quot;form-label&quot;&gt;Confirm Password&lt;/label&gt;
                            &lt;input type=&quot;password&quot; class=&quot;form-control&quot; name=&quot;confirmPassword&quot; id=&quot;confirmPassword&quot; placeholder=&quot;비밀번호 확인&quot; onblur=&quot;chackPassword()&quot;&gt;
                            &lt;span id=&quot;confirmMsg&quot;&gt;&lt;/span&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;mb-3&quot;&gt;
                            &lt;label class=&quot;form-label&quot;&gt;User name&lt;/label&gt;
                            &lt;!-- name속성: 서버로 전송할 때 변수 이름의 역할 --&gt;
                            &lt;input type=&quot;text&quot; class=&quot;form-control&quot; name=&quot;name&quot; id=&quot;name&quot; placeholder=&quot;이름을 입력해주세요&quot;&gt;
                        &lt;/div&gt;
                        &lt;div class=&quot;center text-center&quot;&gt;
                            &lt;input class=&quot;btn btn-primary&quot; type=&quot;submit&quot; value=&quot;가입하기&quot; &gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    &lt;/th:block&gt;
&lt;/div&gt;

&lt;/html&gt;</code></pre>
<p>사용자가 이메일을 입력하고 인증번호를 누르면 인증 메일이 발송되어 인증번호를 확인하여
가입이 가능 하도록했다.</p>
<hr>

<h2 id="이메일인증-코드-전송">이메일인증 코드 전송</h2>
<h3 id="javascript">Javascript</h3>
<p>아래 코드는 이메일 인증 코드를 버튼을 클릭하면 컨트롤러로 이메일 값을 컨트롤러로 post형식으로 보내 컨트롤러는 사용자가 입력한 이메일을 받아
해당 이메일로 인증코드를 보내게된다.</p>
<pre><code class="language-javascript">/**
* 회원가입 조건 &gt;
* 받아온 이메일이 DB에 이미 존재하는 이메일이면 안된다.
* 이메일 형식에 맞지 않으면 안된다.
* 입력한 비밀번호와 비밀번호 확인란에 기재한 문자는 같아야한다 .
* */


// 인증번호 발송
function sendEmail() {
  const email = $(&quot;#email&quot;).val(); // 사용자가 입력한 이메일 주소
  // 이메일칸이 비어있으면 알림
  if (!email || email.trim() === &#39;&#39;) {
      alert(&quot;이메일 주소를 입력해주세요.&quot;);
      return;
  }

  $.ajax({
      url: &quot;/infoCheck/sendEmail&quot; ,// @PostMapping(&quot;/infoCheck/sendEmail&quot;) EmailController
      type: &quot;POST&quot;,
      data: {
          &quot;email&quot;: email
      },
      success: function(data) {
          // 이메일 전송 성공
          alert(&quot;인증번호가 전송되었습니다.&quot;);
      },
      error: function() {
          // 이메일 전송 실패
          alert(&quot;인증번호 전송에 실패했습니다.&quot;);
      }
  });
}

// 인증번호 확인
function verifyCode() {
  const inputCode = $(&quot;#inputCode&quot;).val(); // 사용자가 입력한 인증번호
  if (!inputCode || inputCode.trim() === &#39;&#39;) { // 인증번호입력없이 확인하기를 눌렀을 경우 알림
    alert(&quot;인증번호를 입력해주세요.&quot;);
    return;
  }

  $.ajax({
    url: &quot;/infoCheck/verifyCode&quot;,
    type: &quot;POST&quot;,
    data: {
      &quot;inputCode&quot;: inputCode
    },
    success: function(isCodeCorrect) {
      if (isCodeCorrect) {
        alert(&quot;인증번호가 일치합니다.&quot;);
      } else {
        alert(&quot;인증번호가 일치하지 않습니다.&quot;);
        $(&quot;#inputCode&quot;).focus();
      }
    },
    error: function() {
      alert(&quot;인증번호 확인에 실패했습니다.&quot;);
    }
  });
}

 //비밀번호 확인(span.innerHtml)
 function  chackPassword(){
       var password = $(&quot;#password&quot;);
       var confirmPassword = $(&quot;#confirmPassword&quot;);
       var confrimMsg = $(&quot;#confirmMsg&quot;);
       var correctColor = &quot;green&quot;;    //맞았을 때 출력되는 색깔.
       var wrongColor =&quot;red&quot;;    //틀렸을 때 출력되는 색깔
       if(password.value == confirmPassword.value){
           confirmMsg.style.color = correctColor;/* span 태그의 ID(confirmMsg) 사용  */
           confirmMsg.innerHTML =&quot;비밀번호 일치&quot;;
       } else{
        confirmMsg.style.color = wrongColor;
        confirmMsg.innerHTML =&quot;비밀번호 불일치&quot;;
       }
 }

    $(document).ready(function() {
        $(&#39;#password&#39;).on(&#39;keyup&#39;, function() {
            var password = $(this).val();
            var strength = &#39;&#39;;
            if (password.length &lt; 7) {
                strength = &#39;week&#39;;
                $(&#39;#password-strength&#39;).css(&#39;color&#39;, &#39;red&#39;);
            } else if (password.length &gt;= 8) {
                strength = &#39;good!&#39;;
                $(&#39;#password-strength&#39;).css(&#39;color&#39;, &#39;green&#39;);
            }
            $(&#39;#password-strength&#39;).text(strength);
        });
    });


function SignUpFormSumitCheck(event) {
  // 폼 제출을 막기 위해 event.preventDefault() 호출
  event.preventDefault();

  // 입력된 이메일, 비밀번호, 비밀번호 확인 값을 가져옴
  const email = $(&quot;#email&quot;).val(); // 이메일
  const password = $(&quot;#password&quot;).val(); // 비밀번호
  const confirmPassword = $(&quot;#confirmPassword&quot;).val(); // 비밀번호 확인
  const inputCode = $(&quot;#inputCode&quot;).val(); // 인증번호 확인

  if (!isEmailValid(email)) {
    alert(&quot;올바른 이메일 형식이 아닙니다.&quot;);
    $(&quot;#email&quot;).focus();
    return;
  }


  // 인증번호 확인 AJAX 요청
  $.ajax({
    url: &quot;/infoCheck/verifyCode&quot;,
    type: &quot;POST&quot;,
    data: {
      &quot;inputCode&quot;: inputCode
    },
    success: function(isCodeCorrect) {
      if (!isCodeCorrect) {
        alert(&quot;인증번호가 일치하지 않습니다.&quot;);
        return;
      }

      // 인증번호가 일치하면 회원가입 양식 확인 AJAX 요청
      const ajaxPromise = new Promise((resolve, reject) =&gt; {
        $.ajax({
          type: &quot;post&quot;,
          url: &quot;/SignUpForm/SignUpFormSumitCheck&quot;,
          data: {
            &quot;email&quot;: email,
            &quot;password&quot;: password,
            &quot;confirmPassword&quot;: confirmPassword,
            &quot;inputCode&quot;: inputCode,
          },
          success: function(res) {
            resolve(res);
          },
          error: function(err) {
            reject(err);
          }
        });
      });

      ajaxPromise.then((res) =&gt; {
        if (res == &quot;Emailno&quot;) {
          alert(&quot;이메일을 확인해주세요.&quot;);
          document.getElementById(&quot;email&quot;).focus();

        } else if (res == &quot;PWno&quot;) {
          alert(&quot;비밀번호를 확인해주세요.&quot;);
          document.getElementById(&quot;confirmPassword&quot;).focus();

        } else {
          alert(&quot;회원가입 성공.&quot;);
          console.log(&quot;res : &quot;, res);
          document.getElementById(&quot;SignUpForm&quot;).submit();
        }
      }).catch((err) =&gt; {
        console.log(&quot;에러발생: &quot;, err);
      });

    },
    error: function() {
      alert(&quot;인증번호 확인에 실패했습니다.&quot;);
    }
  });
}


// 이메일 형식 확인하는 함수
function isEmailValid(email) {
  const emailRegex = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
  return emailRegex.test(email);
}


function emailDuplicateCheck(){
     const email = $(&quot;#email&quot;).val();
     // &lt;span id=&quot;checkResult&quot;&gt;      &lt;/span&gt;에 출력하기
     const checkResult = $(&quot;#checkResult&quot;);
     console.log(&quot;입력값 :&quot; , email);

        // 형식
     if (!isEmailValid(email)) {
       console.log(&quot;올바른 이메일 형식이 아닙니다.&quot;);
       checkEmail.style.color = &quot;red&quot;;
       checkEmail.innerHTML = &quot;올바른 이메일 형식이 아닙니다.&quot;;
       return;
     }

     $.ajax({
         type:&quot;post&quot;,
         url:&quot;/SignUpForm/emailDuplicateCheck&quot;,
         data: {
             &quot;email&quot;: email // &quot;전송되는 파라미터 값 이름 &quot;: 파라미터
         },
         success : function(res){
             console.log(&quot;요청성공 : &quot;, res);
             if(res == &quot;ok&quot;){
                 console.log(&quot;사용가능한 이메일입니다.&quot;);
                 checkEmail.style.color = &quot;green&quot;;
                 checkEmail.innerHTML=&quot;사용가능한 이메일입니다.&quot;;
             } else {
                 console.log(&quot;이미 사용중인 이메일입니다.&quot;);
                 checkEmail.style.color = &quot;red&quot;;
                 checkEmail.innerHTML=&quot;이미 사용중인 이메일입니다.&quot;;
             }
         },
         error : function(err){
            console.log(&quot;에러발생: &quot;,err);
            }

     })
}
</code></pre>
<h3 id="controller">Controller</h3>
<pre><code class="language-java">package com.dandelion.dandelion.controller;

import com.dandelion.dandelion.service.EmailService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

@Controller
@RequiredArgsConstructor // 생성자 주입 방식 사용
public class EmailController {
    private final EmailService emailService;

    @PostMapping(&quot;/infoCheck/sendEmail&quot;)
    public ResponseEntity&lt;Void&gt; sendEmail(@RequestParam(&quot;email&quot;) String email, HttpSession session) {
        // 이메일 전송 및 인증번호 저장
        String verificationCode = emailService.sendVerificationCode(email);
        session.setAttribute(&quot;verificationCode&quot;, verificationCode); // 세션에 전송된 인증번호를 저장.
        return ResponseEntity.ok().build();
    }

    //    입력한 인증번호와 세션에 저장된 인증번호를 비교
    @PostMapping(&quot;/infoCheck/verifyCode&quot;)
    public ResponseEntity&lt;Boolean&gt; verifyCode(@RequestParam(&quot;inputCode&quot;) String inputCode, HttpSession session) {
        String storedCode = (String) session.getAttribute(&quot;verificationCode&quot;);

        if (storedCode != null &amp;&amp; storedCode.equals(inputCode)) {
            return ResponseEntity.ok(true);
        } else {
            return ResponseEntity.ok(false);
        }
    }
}
</code></pre>
<p>sendEmail 메서드는 이메일 주소를 받아 EmailService를 통해 인증번호를 전송한다.
전송된 인증번호는 사용자가 입력한 인증번호와 비교하기위해 세션에 저장된다.</p>
<p>verifyCode 메서드는 사용자가 입력한 인증번호와 세션에저장된 인증번호를 비교하는 메서드다.
일치하는 경우 <code>true</code>  불일치하는 경우 <code>false</code> 를 응답 본문에 담아 반환한다.</p>
<h2 id="service">Service</h2>
<pre><code class="language-java">package com.dandelion.dandelion.service;

import lombok.RequiredArgsConstructor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import java.security.SecureRandom;
import java.util.Random;


@RequiredArgsConstructor // 생성자 주입 사용하기
@Service
public class EmailService {

    private final JavaMailSender javaMailSender;

    public String sendVerificationCode(String recipient) {
        String verificationCode = generateEmailCode();
        String subject = &quot;dandelion 인증번호&quot;;
        String message = &quot;인증번호는 &quot; + verificationCode + &quot; 입니다.&quot;;

        sendEmail(recipient, subject, message);
        return verificationCode; // 세션에 저장하기위해 반환
    }

    public void sendEmail(String recipient, String subject, String text) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(recipient); // 수신자 설정
        message.setSubject(subject); // 제목 설정
        message.setText(text); // 내용 설정
        javaMailSender.send(message); // 메일 전송
    }

    /**
     * 이메일 인증번호를 생성하는 메서드
     *
     * @return 6자리 랜덤 알파벳 인증번호
     */
    public String generateEmailCode() {
        int codeLength = 6;  // 코드자리 6자리로 설정
        String alphabet = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&quot;;
        StringBuilder sb = new StringBuilder(codeLength);
        Random random = new SecureRandom();

        for (int i = 0; i &lt; codeLength; i++) {
            int index = random.nextInt(alphabet.length());
            char randomChar = alphabet.charAt(index);
            sb.append(randomChar);
        }
        return sb.toString();
    }
}
</code></pre>
<p><code>sendVerificationCode</code> 메서드는 수신자의 이메일 주소를 입력받아 인증번호를 생성하고 이메일을 전송한 후, 생성된 인증번호를 반환한다.</p>
<p><code>sendEmail</code> 메서드는 수신자, 제목, 내용을 입력받아 이메일을 전송하는 역할을 한다.
SimpleMailMessage 객체를 사용하여 메일을 구성하고 JavaMailSender를 사용하여 메일을 전송한다.</p>
<p><code>generateEmailCode</code> 메서드는 6자리 랜덤 알파벳 인증번호를 생성하는 메서드다.
SecureRandom을 사용해서 무작위 인덱스를 생성하고,
해당 인덱스에 있는 알파벳 문자를 결과문자열에 추가한다.</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/00d82a47-b9a9-439d-a2b8-29fbf3062271/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/c056d635-35aa-4220-b26c-974caf1dc393/image.png" alt=""></p>
<hr>

<p>사용자는 인증번호를 입력하고 확인하기 버튼을 누른다 </p>
<pre><code class="language-javascript">url: &quot;/infoCheck/verifyCode&quot;,</code></pre>
<p>버튼을 클릭하면 post형식으로 값이 컨트롤러로 넘어가면서 세션에 저장되어있는 값과 비교하여 
인증 번호 일치 여부를 alert창으로 알린다.</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/4bf28fcb-0c6e-4ee4-994a-f92eabee615b/image.png" alt=""></p>
<hr>


<pre><code class="language-java">action=&quot;/member/save&quot;</code></pre>
<p>회원가입 버튼을 누르면 form값이 위 주소로 넘어가기전에 </p>
<pre><code class="language-java">onsubmit=&quot;SignUpFormSumitCheck(event)&quot;</code></pre>
<p>모든 회원가입 조건이 충족하는지 체크하고 회원가입이 진행된다.</p>
<p>-jascript 전체 코드 위에 참고-</p>
<h2 id="member컨트롤러-회원정보-save하기">member컨트롤러 회원정보 save하기</h2>
<pre><code class="language-java">package com.dandelion.dandelion.controller;

import com.dandelion.dandelion.dto.MemberDTO;
import com.dandelion.dandelion.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;

@RequiredArgsConstructor // 생성자 주입 사용하기

@Controller
public class MemberController {
    private final MemberService memberService;

    //    회원가입요청시 이메일 중복체크 비밀번호와 비밀번호 확인체크
    @PostMapping(&quot;/SignUpForm/SignUpFormSumitCheck&quot;)
    public @ResponseBody String SignUpFormSumitCheck(@RequestParam(&quot;email&quot;) String email, @RequestParam(&quot;password&quot;)
    String password, @RequestParam(&quot;confirmPassword&quot;)String confirmPassword){ // @ResponseBody ajax사용시 필요 어노테이션

        System.out.println(&quot;email = &quot; + email);
        String checkresult = memberService.SignUpFormSumitCheck(email, password, confirmPassword);
        System.out.println(checkresult);

        return checkresult;

    }

    //    회원가입 이메일 입력시 innerhtml에 들어갈 메서드
    @PostMapping(&quot;/SignUpForm/emailDuplicateCheck&quot;)
    public @ResponseBody String emailDuplicateCheck(@RequestParam(&quot;email&quot;) String email){
        String emailChek = memberService.emailDuplicateCheck(email);
        return emailChek;

    }

    //  회원가입을 위한 메서드 입력받은 값을 userService에 넘긴다
    @PostMapping(&quot;/member/save&quot;) // 요청이 들어오면 메서드를 실행하겠다
    public String save(@ModelAttribute MemberDTO memberDTO){ // SignUpForm에서 입력받은 값의 name이 DTO의 멤버 명과 같을 때 세팅됨

        System.out.println(&quot;MemberController.save&quot;);
        System.out.println(&quot;memberDTO = &quot; + memberDTO);
        memberService.save(memberDTO); // service객체에 dto객체를 넘김
        return &quot;redirect:/member/loginForm&quot;;
    }
 }</code></pre>
<p>컨트롤러로 넘어온 값은 @ModelAttribute을 사용해 MemberDTO 로 받고
memberService.save메서드를 호출하여 값을 저장한다 </p>
<h2 id="memberservice에서-회원정보-저장">MemberService에서 회원정보 저장</h2>
<pre><code class="language-java">@RequiredArgsConstructor // 생성자 주입 사용하기
@Service
public class MemberService {

    private final MemberRepository memberRepository;

    public void save(MemberDTO memberDTO) {
        //        1. dto -&gt; entity 변환
        MemberEntity memberEntity = MemberEntity.toMemberEntity(memberDTO);
        //        2. repository의 save메서드 호출
        try {
            memberRepository.save(memberEntity); // jpa가 제공해주는 save 메서드
            System.out.println(&quot;회원테이블 DB저장시도&quot;);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<p>컨트롤러에서 넘어온 memberDTO 는 엔티티로 변환되어 
MemberRepository의 save를 사용해 DB에 저장한다.</p>
<h2 id="memberrepository-인터페이스">MemberRepository 인터페이스</h2>
<pre><code class="language-java">package com.dandelion.dandelion.repository;

import com.dandelion.dandelion.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

// JpaRepository 상속
public interface MemberRepository extends JpaRepository&lt;MemberEntity, Long&gt; {
//    이메일로 회원정보 조회
    Optional&lt;MemberEntity&gt; findByEmail(String email);
}</code></pre>
<p>MemberRepository 는 JPA에서 제공하는 JpaRepository를 확장하여 
JpaRepository가 가지는 여러가지 기능을 상속받아 사용할 수 있다.
예를들면 찾기(find),삭제하기(delete),저장하기(save) 등이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java와 spring boot 게시판 프로젝트 - 테이블 설계]]></title>
            <link>https://velog.io/@choigoyo_o/Java%EC%99%80-spring-boot-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@choigoyo_o/Java%EC%99%80-spring-boot-%EA%B2%8C%EC%8B%9C%ED%8C%90-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%85%8C%EC%9D%B4%EB%B8%94-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Tue, 09 May 2023 09:46:15 GMT</pubDate>
            <description><![CDATA[<h2 id="테이블-설계">테이블 설계</h2>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/8054aed8-2067-4280-bf1a-e04db25f021a/image.png" alt=""></p>
<br>

<ol start="0">
<li>테이블 간 시간과 관련된 컬럼을 상속해 줄 BaseTime테이블<pre><code class="language-java">package com.dandelion.dandelion.entity;
</code></pre>
</li>
</ol>
<p>import lombok.Getter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;</p>
<p>import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;</p>
<p>@Getter
@MappedSuperclass // 모든 JPA 엔티티들이 BaseTimeEntity를 상속 받을 경우 필드도 컬럼으로 인식하도록 한다.
@EntityListeners(AuditingEntityListener.class) // BaseTimeEntity 클래스에 Auditing 기능을 포함시킨다.
public class BaseTimeEntity {</p>
<pre><code>@CreationTimestamp // 3
@Column(updatable = false)
private LocalDateTime localDateTime;

@UpdateTimestamp // 4
@Column(insertable = false)
private LocalDateTime modifiedDate;</code></pre><p>}</p>
<pre><code>회원테이블에는 회원가입 날짜와 회원정보 수정 일자가 필요했고, 게시글도 마찬가지였다.
그래서 이를 상속해줄 수 있는 클래스를 작성하여 시간 클래스를 상속받는 테이블들은 컬럼을 가지도록 코드를 작성했다. 

&lt;br&gt;&lt;br&gt;

1. 회원정보를 담고있는 member테이블

```java
package com.dandelion.dandelion.entity;

import com.dandelion.dandelion.dto.MemberDTO;
import lombok.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor // 기본 생성자가 생성
@AllArgsConstructor
@Table(name = &quot;membertable&quot;) // db에 생성될 테이블 이름
public class MemberEntity extends BaseTimeEntity{ // BaseEntity를 상속
    @Id // pk
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 시퀀스의 역할 auto_increment
    @Column(name=&quot;id&quot; , nullable = false)
    private Long id; // 회원번호아이디
    @Column(name =&quot;email&quot;,nullable = false , unique = true ,length = 50)
    private String email; // 회원이메일
    @Column(name =&quot;password&quot;,nullable = false,length = 100)
    private String password; // 회원비밀번호
    @Column(name =&quot;name&quot;,nullable = false,length = 50)
    private String name; // 회원이름
    @Column(name =&quot;role&quot;,nullable = false)
    private String role = &quot;user&quot;; // 역할 디폴트 user

    @OneToMany(mappedBy = &quot;member&quot; , cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List&lt;BoardEntity&gt; boards = new ArrayList&lt;&gt;();
    @OneToMany(mappedBy = &quot;member&quot; , cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List&lt;CommentsEntity&gt; comments = new ArrayList&lt;&gt;();

    public static MemberEntity toMemberEntity(MemberDTO memberDTO){
        MemberEntity memberEntity = new MemberEntity();
        /*DTO에 감긴 값을 Entity로 세팅하는 과정*/
        memberEntity.setId(memberDTO.getId());
        memberEntity.setName(memberDTO.getName());
        memberEntity.setEmail(memberDTO.getEmail());
        memberEntity.setPassword(memberDTO.getPassword());
        memberEntity.setRole(memberDTO.getRole() == null ? &quot;user&quot; : memberDTO.getRole());
        return memberEntity;
    }
}</code></pre><p>회원과 게시글의 관계를 설명하자면, 한 명의 회원은 여러 개의 게시글을 작성할 수도 있다 
댓글도 마찬가지로, 한 명의 회원은 여러 개의 댓글을 작성할 수 있다 </p>
<p>따라서 @OneToMany로 테이블 간의 관계를 설정해주었다.</p>
<p><br><br>
2. 게시글의 정보를 가지고있는 Board테이블</p>
<pre><code class="language-java">package com.dandelion.dandelion.entity;

import com.dandelion.dandelion.dto.BoardDTO;
import com.dandelion.dandelion.repository.MemberRepository;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor // 기본 생성자가 생성
@AllArgsConstructor
@Table(name = &quot;baordtable&quot;) // db에 생성될 테이블 이름
public class BoardEntity extends BaseTimeEntity{
    @Id // pk
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 시퀀스의 역할 auto_increment
    @Column(name=&quot;id&quot; , nullable = false)
    private Long id; // 게시글 넘버
    @Column(name=&quot;title&quot; , nullable = false,length = 100)
    private String title; // 제목
    @Column(name=&quot;content&quot; , nullable = false)
    private String content; // 내용
    @Column(name=&quot;categories&quot; , nullable = false)
    private String categories; // 카테고리

    @ManyToOne
    @JoinColumn(name = &quot;member_id&quot;)
    private MemberEntity member;
    @OneToMany(mappedBy = &quot;board&quot; , cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List&lt;CommentsEntity&gt; comments = new ArrayList&lt;&gt;();


    public static BoardEntity toBoardEntity(BoardDTO boardDTO, MemberEntity member) {
        BoardEntity boardEntity = new BoardEntity();
        boardEntity.setId(boardDTO.getId());
        boardEntity.setCategories(boardDTO.getCategories());
        boardEntity.setTitle(boardDTO.getTitle());
        boardEntity.setContent(boardDTO.getContent());
        boardEntity.setMember(member);
        return boardEntity;
    }

    public String getMemberName() {
        return member != null ? member.getName() : &quot;&quot;;
    }

}</code></pre>
<p>게시글은 한 명의 회원의 정보만을 가질 수 있으며, 반대로 댓글은 여러개가 하나의 게시글에 연결되기 때문에 
게시글과 회원테이블간의 관계는 @ManyToOne로
게시글과 댓글의 관계는 @OneToMany 로 작성했다.</p>
<p><br><br></p>
<ol start="3">
<li>댓글의 정보를 가지고있는 comment 테이블 </li>
</ol>
<pre><code class="language-java">package com.dandelion.dandelion.entity;

import com.dandelion.dandelion.dto.CommentsDTO;
import com.dandelion.dandelion.repository.BoardRepository;
import com.dandelion.dandelion.repository.MemberRepository;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.Optional;

@Entity
@Getter
@Setter
@NoArgsConstructor // 기본 생성자가 생성
@AllArgsConstructor
@Table(name = &quot;conmmentstable&quot;) // db에 생성될 테이블 이름
public class CommentsEntity extends BaseTimeEntity{
        @Id // pk
        @GeneratedValue(strategy = GenerationType.IDENTITY) // 시퀀스의 역할 auto_increment
        @Column(name=&quot;id&quot; , nullable = false)
        private Long id; // 댓글 고유번호
        @Column(name=&quot;title&quot; , nullable = false,length = 100)
        private String content; // 내용

        @ManyToOne
        @JoinColumn(name = &quot;member_id&quot;)
        private MemberEntity member;
        @ManyToOne
        @JoinColumn(name = &quot;board_id&quot;)
        private BoardEntity board;

        public static CommentsEntity toCommentsEntity(CommentsDTO commentsDTO, MemberRepository memberRepository, BoardRepository boardRepository) {
                CommentsEntity commentsEntity = new CommentsEntity();
                commentsEntity.setId(commentsDTO.getId());
                commentsEntity.setContent(commentsDTO.getContent());

                if (commentsDTO.getMemberId() != null) {
                        Optional&lt;MemberEntity&gt; memberOpt = memberRepository.findById(commentsDTO.getMemberId());
                        memberOpt.ifPresent(commentsEntity::setMember);
                }

                if (commentsDTO.getBoardId() != null) {
                        Optional&lt;BoardEntity&gt; boardOpt = boardRepository.findById(commentsDTO.getBoardId());
                        boardOpt.ifPresent(commentsEntity::setBoard);
                }

                return commentsEntity;
        }

}
</code></pre>
<p>댓글은 한명의 회원 그리고 하나의 게시글에만 작성되기때문에 
둘다 @ManyToOne 으로 테이블관계를 설정해주면 된다.
<br><br></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - literal의 타입, 접두사와 접미사 내용 정리]]></title>
            <link>https://velog.io/@choigoyo_o/Java-literal%EC%9D%98-%ED%83%80%EC%9E%85-%EC%A0%91%EB%91%90%EC%82%AC%EC%99%80-%EC%A0%91%EB%AF%B8%EC%82%AC-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@choigoyo_o/Java-literal%EC%9D%98-%ED%83%80%EC%9E%85-%EC%A0%91%EB%91%90%EC%82%AC%EC%99%80-%EC%A0%91%EB%AF%B8%EC%82%AC-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 09 May 2023 07:53:39 GMT</pubDate>
            <description><![CDATA[<h1 id="literal의-타입-접두사와-접미사">literal의 타입, 접두사와 접미사</h1>
<p>리터럴의 타입은 저장되는 값의 데이터 타입에 따라 결정됩니다.
자바에서는 정수, 부동소수점, 문자,문자열,불리언 등의 리터럴 타입이 있습니다.</p>
<p>리터럴 값은 접두사(prefix)와 접미사(suffix)를 사용하여 표현할 수 있습니다.<br>
이를 통해 리터럴 값의 데이터 타입이나 진법을 명시적으로 지정할 수 있습니다.</p>
<hr>

<h3 id="정수-리터럴">정수 리터럴</h3>
<p>10진수 - 기본적으로 정수 리터럴은 10진수로 표현됩니다. 접두사나 접미사가 없습니다.</p>
<pre><code class="language-java">int decimal = 42;</code></pre>
<p>2진수 - 접두사 <code>0b</code>  또는 <code>0B</code>를 사용하여 2진수 정수 리터럴을 표현합니다.</p>
<pre><code class="language-java">int binary = 0b101010;</code></pre>
<p>8진수 - 접두사 <code>0</code>를 사용하여 8진수 리터럴을 표현합니다.</p>
<pre><code class="language-java">int octal = 052;</code></pre>
<p>16진수 - 접두사 <code>0x</code> 또는 <code>0X</code>를 사용하여 16진수 정수 리터럴을 표현합니다.</p>
<pre><code class="language-java">int hexadecimal = 0x2A;</code></pre>
<hr>

<h3 id="부동소수점-리터럴">부동소수점 리터럴</h3>
<p>실수 리터럴 - 기본적으로 부동소수점 리터럴은 실수로 표현됩니다. 접미사가 없으면 기본적으로 double 타입으로 간주됩니다.</p>
<pre><code class="language-java">double real = 3.14159;</code></pre>
<p>float 타입 - 접미사 f 또는 F를 사용하여 float 타입의 부동소수점 리터럴을 표현합니다.</p>
<pre><code class="language-java">float floatValue = 3.14F;</code></pre>
<p>double 타입 - 접미사 d 또는 D를 사용하여 double 타입의 부동소수점 리터럴을 명시적으로 표현할 수 있습니다.</p>
<pre><code class="language-java">double doubleValue = 3.14159D;</code></pre>
<hr>

<h3 id="문자-리터럴">문자 리터럴</h3>
<p>문자 리터럴은 작은따옴표 <code>&#39;&#39;</code>로 묶인 단일 문자를 사용하여 표현합니다.</p>
<pre><code class="language-java">char letter = &#39;A&#39;;</code></pre>
<hr>

<h3 id="문자열-리터럴">문자열 리터럴</h3>
<p>문자열 리터럴은 큰따옴표 <code>&quot;&quot;</code>로 묶인 문자들의 시퀀스를 사용하여 표현합니다.</p>
<pre><code class="language-java">String name = &quot;choigoyo&quot;;</code></pre>
<hr>

<h3 id="불리언-리터럴">불리언 리터럴</h3>
<p>불리언 리터럴은 <code>true</code> 와 <code>false</code>값을 사용하여 표현합니다.</p>
<pre><code class="language-java">boolean isActive = true;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - 변수와 상수 리터럴 개념 정리 ]]></title>
            <link>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98%EC%99%80-%EC%83%81%EC%88%98-%EB%A6%AC%ED%84%B0%EB%9F%B4-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98%EC%99%80-%EC%83%81%EC%88%98-%EB%A6%AC%ED%84%B0%EB%9F%B4-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 09 May 2023 07:24:50 GMT</pubDate>
            <description><![CDATA[<h1 id="변수와-상수-그리고-리터럴">변수와 상수, 그리고 리터럴</h1>
<h3 id="변수variable">변수(variable)</h3>
<p><code>값이 변할 수 있는 메모리</code> 공간을 나타내며, 프로그램 실행 중에 값이 변경될 수 있습니다.</p>
<pre><code class="language-java">int number = 10;
number = 20;</code></pre>
<p>여기에서 number는 변수이며, 값을 변경할 수 있습니다.</p>
<hr>

<h3 id="상수constant">상수(constant)</h3>
<p><code>값이 변하지 않는 메모리 공간</code>을 나타내며, 프로그램 실행 중에 값이 변경되지 않습니다. <br>
상수는 일반적으로 대문자와 밑줄(_)로 구성된 이름을 사용하여 선언하며, final 키워드를 사용하여 선언합니다.</p>
<pre><code class="language-java">final double PI = 3.14159;</code></pre>
<p>여기에서 PI는 상수이며, 값을 변경할 수 없습니다.</p>
<p>리터럴(literal)은 소스 코드에서 직접 사용되는 고정된 값입니다. <br>
리터럴은 변수나 상수에 할당될 수 있습니다.</p>
<hr>

<h3 id="리터럴literal">리터럴(literal)</h3>
<p>소스 코드에서 직접 사용되는 고정된 값입니다.  <br>
리터럴은 변수나 상수에 할당될 수 있습니다.</p>
<pre><code class="language-java">int number = 42;         // 42는 정수형 리터럴입니다.
double price = 9.99;     // 9.99는 부동소수점 리터럴입니다.
char letter = &#39;A&#39;;       // &#39;A&#39;는 문자 리터럴입니다.
String name = &quot;John&quot;;    // &quot;John&quot;은 문자열 리터럴입니다.
boolean isActive = true; // true는 불리언 리터럴입니다.</code></pre>
<p>이 예시에서 42, 9.99, &#39;A&#39;, &quot;John&quot; 및 true는 각각 정수, 부동소수점, 문자, 문자열 및 불리언 리터럴입니다. <br>
이러한 리터럴은 코드에서 직접 사용되며, 변수나 상수에 할당할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - 변수 타입]]></title>
            <link>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98%ED%83%80%EC%9E%85</guid>
            <pubDate>Mon, 08 May 2023 18:16:55 GMT</pubDate>
            <description><![CDATA[<h1 id="변수의-타입">변수의 타입</h1>
<hr>
자바에서 변수의 타입은 기본 데이터 타입과 참조 데이터 타입으로 구분됩니다.


<ol>
<li>기본 데이터 타입 : 기본 데이터 타입은 단순한 값을 저장하는 변수 타입으로, 메모리에 직접 값을 저장합니다.</li>
</ol>
<p>자바에서는 다음과 같은 기본 데이터 타입이 있습니다.</p>
<ul>
<li><p>정수형 : 정수값을 저장할 수 있는 타입으로, byte, short, int, long이 있습니다. 각각의 크기와 값의 범위가 다릅니다.</p>
<ul>
<li>byte: 8비트, -128 ~ 127 범위의 정수</li>
<li>short: 16비트, -32,768 ~ 32,767 범위의 정수</li>
<li>int: 32비트, -2,147,483,648 ~ 2,147,483,647 범위의 정수</li>
<li>long: 64비트, -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 범위의 정수</li>
</ul>
</li>
<li><p>실수형 : 부동소수점 값을 저장할 수 있는 타입으로, float와 double이 있습니다.</p>
<ul>
<li>float : 32비트, 대략 ±3.4 x 10^38 범위의 실수</li>
<li>double : 64비트, 대략 ±1.8 x 10^308 범위의 실수</li>
</ul>
</li>
<li><p>문자형 : 문자 값을 저장할 수 있는 타입으로, char가 있습니다.</p>
<ul>
<li>char: 16비트, 0 ~ 65,535 범위의 유니코드 문자</li>
</ul>
</li>
<li><p>논리형 : 참(true) 또는 거짓(false) 값을 저장할 수 있는 타입으로, boolean이 있습니다. boolean: true 또는 false</p>
</li>
</ul>
<ol start="2">
<li>참조 데이터 타입 : 참조 데이터 타입은 객체의 참조(주소)를 저장하는 변수 타입입니다. 참조 데이터 타입에는 클래스, 인터페이스, 배열이 있습니다. 참조 데이터 타입 변수는 힙(heap) 메모리에 생성된 객체를 참조합니다.</li>
</ol>
<ul>
<li>클래스 : 사용자 정의 클래스 또는 자바에서 제공하는 클래스를 사용하여 변수를 선언할 수 있습니다. 예를 들어, String 클래스를 사용하는 변수는 참조 데이터 타입입니다.</li>
<li>인터페이스 : 인터페이스 타입의 변수는 해당 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있습니다.</li>
<li>배열 : 배열은 동일한 타입의 여러 개의 값을 저장할 수 있는 참조 데이터 타입입니다. 배열은 기본 데이터 타입이나 참조 데이터 타입을 요소로 가질 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java - 변수 variable]]></title>
            <link>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98-variable</link>
            <guid>https://velog.io/@choigoyo_o/Java-%EB%B3%80%EC%88%98-variable</guid>
            <pubDate>Mon, 08 May 2023 17:40:11 GMT</pubDate>
            <description><![CDATA[<h1 id="변수variable-란-">변수(variable) 란 ?</h1>
<p>프로그래밍 언어에서 값을 저장하고 참조하기 위한 <code>메모리 공간</code>에 대한 이름입니다.
자바에서수는 데이터 타입(data type)과 이름(identifier)을 사용하여 선언하며,
값을 저장하거나 추출할 때 변수 이름을 사용합니다.</p>
<pre><code class="language-java">int number;</code></pre>
<p>여기서 int는 데이터 타입이며, number는 변수의 이름입니다. 
이제 number라는 이름으로 해당 변수에 값을 저장하거나 추출할 수 있습니다.</p>
<br>

<h3 id="변수에-값을-할당하려면-다음과-같이-작성합니다">변수에 값을 할당하려면 다음과 같이 작성합니다.</h3>
<pre><code class="language-java">int anotherNumber = number + 5;</code></pre>
<p>anotherNumber 변수에는 number 변수의 값(10)에 5를 더한 결과인 15가 저장됩니다.</p>
<p><code>=</code> 기호는 등호가 아니라 대입의 의미를 가지고있습니다.</p>
<p>자바에서는 대입 연산자라고 부릅니다.</p>
<hr>

<h3 id="변수의-초기화">변수의 초기화</h3>
<p>변수 초기화란 변수에 처음 값을 할당하는 과정입니다. </p>
<p>프로그램에서 변수를 사용하기 전에 변수를 초기화해야 합니다. </p>
<p>초기화되지 않은 변수는 메모리에 존재하는 기존의 쓰레기 값이나 불확실한 값이 할당되어 있을 수 있으며, 이로 인해 예기치 않은 결과가 발생할 수 있습니다.</p>
<p>변수를 선언하면서 동시에 초기화하는 방법을 사용할 수 있습니다. </p>
<p>예를 들어, 다음과 같이 변수를 선언하면서 초기화할 수 있습니다.</p>
<pre><code class="language-java">int number = 10; // &#39;number&#39; 변수를 선언하고 10으로 초기화</code></pre>
<p>또는 먼저 변수를 선언한 후 별도의 문장에서 초기화할 수도 있습니다:</p>
<pre><code class="language-java">int number; // &#39;number&#39; 변수를 선언
number = 10; // &#39;number&#39; 변수를 10으로 초기화</code></pre>
<p>클래스 내의 인스턴스 변수와 정적 변수는 자동으로 기본 값으로 초기화됩니다. </p>
<p>기본값은 데이터 타입에 따라 달라집니다. </p>
<p>예를 들어, 정수형 변수의 기본값은 0이고, 부동소수점형 변수의 기본값은 0.0입니다. 
불리언 변수의 기본값은 false이고, 참조형 변수의 기본값은 null입니다.</p>
<p>로컬 변수(메소드 내에서 선언된 변수)는 자동으로 초기화되지 않습니다. </p>
<p>로컬 변수를 사용하기 전에 명시적으로 초기화해야 합니다. </p>
<p>그렇지 않으면 컴파일러는 초기화되지 않은 변수 사용에 대한 오류 메시지를 표시합니다.</p>
<hr>

<h3 id="변수의-세가지-유형">변수의 세가지 유형</h3>
<p>자바에서 변수는 클래스 변수, 인스턴스 변수, 그리고 지역 변수로 구분됩니다.<br> 
이 세 가지 변수 유형은 선언 위치, 사용 범위, 그리고 생명 주기에 따라 차이가 있습니다.</p>
<ol>
<li>클래스 변수 (static 변수): 클래스 변수는 클래스 내에서 static 키워드를 사용해 선언되며, 해당 클래스의 모든 인스턴스에서 공유됩니다. 클래스 변수는 클래스가 로드될 때 생성되고, 클래스가 언로드될 때 소멸합니다. 클래스 변수는 메모리에서 한 번만 할당되므로, 
모든 객체가 동일한 메모리 공간에 접근합니다.</li>
</ol>
<pre><code class="language-java">class MyClass {
    static int classVar; // 클래스 변수
}</code></pre>
<ol start="2">
<li>인스턴스 변수: 인스턴스 변수는 클래스 내에서 선언되지만, static 키워드를 사용하지 않습니다. 인스턴스 변수는 각 객체마다 별도로 할당되며, 객체가 생성될 때 생성되고 객체가 소멸될 때 소멸합니다. 인스턴스 변수는 객체마다 각각의 메모리 공간을 가집니다.<pre><code class="language-java">class MyClass {
int instanceVar; // 인스턴스 변수
}</code></pre>
</li>
<li>지역 변수: 지역 변수는 메소드 또는 블록 내에서 선언되며, 선언된 메소드나 블록 내에서만 사용할 수 있습니다. 지역 변수는 메소드 호출 시 생성되고, 메소드가 종료될 때 소멸합니다. 지역 변수는 메소드 호출 시마다 새로운 메모리 공간을 할당받습니다.<pre><code class="language-java">void myMethod() {
 int localVar; // 지역 변수
}</code></pre>
</li>
</ol>
<p>지역 변수와 인스턴스 변수의 주요 차이점은 다음과 같습니다.</p>
<ul>
<li>선언위치 : 지역변수는 메소드나 블록 내에서 선언되지만, 인스턴스 변수는 클래스 내에서 선언됩니다.</li>
<li>사용범위 : 지역변수는 선언된 메소드나 블록 내에서만 사용할 수 있습니다. 인스턴스 변수는 클래스 내의 모든 메소드에서 사용할 수 있습니다.</li>
<li>생명주기 : 지역 변수는 메소드 호출시 생성되고 메소드가 종료될 때 소멸합니다. 인스턴스 변수는 객체가 생성될때 생성되고 객체가 소멸될때 소멸합니다.</li>
<li>메모리할당 : 지역 변수는 스택 메모리에 할당되며, 인스턴스 변수는 힙 메모리에 할당됩니다.
이러한 차이점을 이해하면 변수의 사용범위와 생명주기를 적절하게 관리할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT -  인증이 필요한 페이지 요청시 토큰으로 권한 인증 ]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%EC%9D%B8%EC%A6%9D%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9A%94%EC%B2%AD%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9C%BC%EB%A1%9C-%EA%B6%8C%ED%95%9C-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%EC%9D%B8%EC%A6%9D%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9A%94%EC%B2%AD%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9C%BC%EB%A1%9C-%EA%B6%8C%ED%95%9C-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Wed, 03 May 2023 16:59:15 GMT</pubDate>
            <description><![CDATA[<h3 id="인증된-사용자에게-토큰을-담은-응답하기">인증된 사용자에게 토큰을 담은 응답하기</h3>
<pre><code class="language-java">package com.choigoyo.config.JWT;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.choigoyo.config.auth.PrincipalDetails;
import com.choigoyo.entity.UserEntityJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    /**
     * Spring Security 의 UsernamePasswordAuthenticationFilter 를 확장하여 사용하게되면
     * http://localhost:8081/login 주소로 userName,password 를 post 형식으로 전송하면
     * UsernamePasswordAuthenticationFilter 가 동작함 SecurityConFig 클래스에 form 로그인을 비활성화 시켜서 동작하지 않는 상태이나,
     * JwtAuthenticationFilter 를 다시 SecurityConFig 클래스에 등록해주면 동작하게됨
     */

    private final AuthenticationManager authenticationManager;


    // http://localhost:8081/login 요청이 들어오면 authenticationManager 통해서 로그인시도
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println(&quot;JwtAuthenticationFilter : 로그인 시도중...&quot;);

        // 1 사용자의 userName, password 를 받는다
        // 2 로그인이 가능한지 확인
        // 3 PrincipalDetails 를 세션에 담는다 (권한 관리를 위해)
        // 4 JWT 토큰을 만들어서 응답해준다

        try {
            // request.getInputStream() 에 담겨있는 값 확인하기
//            BufferedReader reader = request.getReader();
//            String input = null;
//            while ((input = reader.readLine())!= null) {
//                System.out.println(input); // 보내온 값을 한줄씩 출력


            // json 데이터를 파싱해줌
            ObjectMapper objectMapper = new ObjectMapper();
            UserEntityJWT userEntityJWT = objectMapper.readValue(request.getInputStream(), UserEntityJWT.class);
            // 유저에 정보가 잘 담겼는지 print 해보기
            System.out.println(userEntityJWT);
            // 토큰 생성(userName, password 를 담은)
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(userEntityJWT.getUserName(),userEntityJWT.getPassword());
            // authenticationManager.authenticate() 인증 수행하기
            // 그리고 PrincipalDetailsService 의 loadByUsername() 함수가 실행됨
            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            System.out.println(request.getInputStream()); // request.getInputStream() 은 HttpServletRequest 객체로부터 입력 스트림을 가져오는 데 사용
            // 클라이언트 또는 다른 웹브라우저에서 서버로 전송된 데이터를 읽는데 사용할 수 있는 InputStream 객체
            // authenticate 객체가 session 영역에 저장됨
            PrincipalDetails principalDetails = (PrincipalDetails)authenticate.getPrincipal();
            System.out.println(&quot;==================로그인 완료==================&quot;); // 구분선
            System.out.println(&quot;principalDetails userName :&quot;+principalDetails.getUser().getUserName());
            System.out.println(&quot;===============================================&quot;); // 구분선
            return authenticate;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    // 순서 -&gt;  attemptAuthentication 에서 인증이 정상적으로 실행되고 successfulAuthentication 메서드 실행
    // JWT 토큰을 만들어서 request 요청한 사용자에게 토큰을 응답과함께 전달
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        System.out.println(&quot;사용자 인증이 완료되어 successfulAuthentication 메서드가 실행됩니다.&quot;);

        PrincipalDetails principalDetails = (PrincipalDetails)authResult.getPrincipal();

        // 빌드패턴으로 토큰 생성하기
        String jwtToken = JWT.create()
                .withSubject(&quot;JWT-TOKEN&quot;)
                .withExpiresAt(new Date(System.currentTimeMillis() + (60000 * 10))) // 토큰의 유효시간을 10분으로 지정
                .withClaim(&quot;id&quot;, principalDetails.getUser().getId())
                .withClaim(&quot;username&quot;, principalDetails.getUser().getUserName())
                .sign(Algorithm.HMAC512(&quot;server-secret&quot;)); // server만 알고있는 secret값 으로 서명

        response.addHeader(&quot;Authentication&quot;,&quot;Bearer &quot;+jwtToken);  // 사용자에게 응답

        System.out.println(&quot;==================토큰 생성==================&quot;);
        System.out.println(&quot;name : Authentication&quot;);
        System.out.println(&quot;value: Bearer &quot;+ jwtToken);

    }
}
</code></pre>
<p>클라이언트가 로그인 요청을하고  인증단계에 들어갑니다.
인증이 완료가되면 successfulAuthentication 메서드가 실행되며 토큰을 만들어 클라이언트에게 응답합니다. 
 (응답 Header의 Authentication 객체 안에는 토큰이 들어있습니다.)
<img src="https://velog.velcdn.com/images/choigoyo_o/post/939d1672-ff25-44ca-8223-89361eb53851/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/59f15047-41ce-4649-bdd9-77fd4fbe9f93/image.png" alt=""></p>
<p>postman으로 클라이언트의 아이디와 비밀번호 정보를 json으로 입력해주고 
post 타입이르 로그인 요청을 보내면, JwtAuthenticationFilter 클래스의 attemptAuthentication 메서드가 실행되면 데이터를 파싱하고 유저정보를 가지고 인증을 수행합니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/944a0298-9a31-4a2b-bf23-0e6c61377391/image.png" alt=""></p>
<p>인증이 완료되고나서 successfulAuthentication 메서드에서 토큰을 생성해 응답 Header의Authentication 객체에  토큰을 담아 응답합니다.</p>
<p><br><br></p>
<h3 id="권한이-필요한-페이지를-요청했을-때">권한이 필요한 페이지를 요청했을 때</h3>
<pre><code class="language-java">package com.choigoyo.config.JWT;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.choigoyo.config.auth.PrincipalDetails;
import com.choigoyo.entity.UserEntityJWT;
import com.choigoyo.repository.UserRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
    // BasicAuthenticationFilter Spring Security에서 제공하는 필터 중 하나로, HTTP 요청이 들어올 때 기본 인증(Basic Authentication)을 처리하는 역할
    // 인증이나 권한이 필요한 주소요청이 있을 때 해당 필터를 거치게됨
    private UserRepository userRepository;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, UserRepository userRepository) {
        super(authenticationManager);
        this.userRepository = userRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(&quot;인증이나 권한이 필요한 주소가 요청되었습니다.&quot;);
        String jwtHeader = request.getHeader(&quot;Authorization&quot;); // header 값을 확인해서 출력해보기
        System.out.println(&quot;jwtHeader : &quot;+jwtHeader);
        // JWT 토큰을 검증해서 정상적인 사용자인지 확인
        if (jwtHeader == null || !jwtHeader.startsWith(&quot;Bearer &quot;)) { // header에 토큰이 존재하지 않거나 토큰의 시작이 Bearer이 아니라면
            chain.doFilter(request,response); // 다시 필터를 거치게
            return;
        }

        // Authorization 에서 토큰값만 저장하기위해  &quot;Bearer &quot;을 빈 문자열로 변경하여 저장 (토큰값만 뽑아내는 작업)
        String jwtToken = request.getHeader(&quot;Authorization&quot;).replace(&quot;Bearer &quot;, &quot;&quot;);
        // 서명하여 user의 이름을 가져온다.
        String userName = JWT.require(Algorithm.HMAC512(&quot;server-secret&quot;)).build().verify(jwtToken)
                .getClaim(&quot;username&quot;).asString();
        System.out.println(&quot;================ username ==================&quot;);
        System.out.println(&quot;userName : &quot;+userName);
        // 서명이 정상적으로 되면 if문 실행
        if (userName != null) {
            UserEntityJWT userEntityJWT = userRepository.findByUserName(userName);
            System.out.println(&quot;=================== 토큰으로 사용자 정보 찾기 ===================&quot;);
            System.out.println(&quot;userEntityJWT : &quot;+userEntityJWT);
            PrincipalDetails principalDetails = new PrincipalDetails(userEntityJWT);

            Authentication authentication =
                    new UsernamePasswordAuthenticationToken(
                            principalDetails, //나중에 컨트롤러에서 DI해서 쓸 때 사용하기 편함.
                            null, // 패스워드는 모르니까 null 처리
                            principalDetails.getAuthorities());

            // 강제로 세션공간에 접근하여 Authentication 객체를 세션 영역에 저장
            SecurityContextHolder.getContext().setAuthentication(authentication);
            chain.doFilter(request,response); // 다시 필터를 거치게
        }

    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/4afbd801-ea50-47e1-819c-255d3441af69/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/15b6c2c0-a129-4123-ac57-95e647a6140e/image.png" alt="">
JwtAuthorizationFilter 클래스는 권한이 필요한 페이지에 대한 요청이 발생할 때 호출되며, 사용자가 제공한 JWT 토큰을 검증합니다. 검증이 완료되면 토큰에서 사용자 이름을 추출하고, 이를 사용하여 데이터베이스에서 사용자 정보를 가져옵니다.</p>
<p>PrincipalDetails 객체를 생성한 후, UsernamePasswordAuthenticationToken 객체를 생성하여 사용자 정보와 권한을 설정합니다. 이후, SecurityContextHolder를 사용하여 인증 객체를 세션에 저장하고, 필터 체인을 계속 실행하여 요청을 처리합니다.</p>
<p>요약하면, 이 클래스는 다음과 같은 작업을 수행합니다:</p>
<ol>
<li>요청 헤더에서 JWT 토큰을 검색합니다.</li>
<li>토큰이 올바른지 검증하고, 사용자 이름을 추출합니다.</li>
<li>사용자 이름을 기반으로 데이터베이스에서 사용자 정보를 가져옵니다.</li>
<li>사용자 정보와 권한을 포함하는 UsernamePasswordAuthenticationToken 객체를 생성합니다.</li>
<li>SecurityContextHolder를 사용하여 인증 객체를 세션에 저장합니다.</li>
<li>필터 체인을 계속 실행하여 요청을 처리합니다.</li>
</ol>
<p>이 프로세스는 권한이 필요한 페이지에 대한 요청을 처리하는 데 사용되며, 올바른 사용자 인증을 보장합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - 사용자 로그인 요청시 토큰을 생성하여 응답하기]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%EC%82%AC%EC%9A%A9%EC%9E%90-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9A%94%EC%B2%AD%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%83%9D%EC%84%B1%ED%95%98%EC%97%AC-%EC%9D%91%EB%8B%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%EC%82%AC%EC%9A%A9%EC%9E%90-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9A%94%EC%B2%AD%EC%8B%9C-%ED%86%A0%ED%81%B0%EC%9D%84-%EC%83%9D%EC%84%B1%ED%95%98%EC%97%AC-%EC%9D%91%EB%8B%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 03 May 2023 07:39:42 GMT</pubDate>
            <description><![CDATA[<h3 id="jwtauthenticationfilter-의-successfulauthentication-메서드">JwtAuthenticationFilter 의 successfulAuthentication 메서드</h3>
<pre><code class="language-java">    // 순서 -&gt;  attemptAuthentication 에서 인증이 정상적으로 실행되고 successfulAuthentication 메서드 실행
    // JWT 토큰을 만들어서 request 요청한 사용자에게 토큰을 응답과함께 전달
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        System.out.println(&quot;사용자 인증이 완료되어 successfulAuthentication 메서드가 실행됩니다.&quot;);

        PrincipalDetails principalDetails = (PrincipalDetails)authResult.getPrincipal();

        // 빌드패턴으로 토큰 생성하기
        String jwtToken = JWT.create()
                .withSubject(principalDetails.getUsername()+&quot;님의 토큰&quot;)
                .withExpiresAt(new Date(System.currentTimeMillis()+(6000*10))) // 토큰의 유효시간 10분으로 지정
                .withClaim(&quot;id&quot;, principalDetails.getUser().getId())
                .withClaim(&quot;username&quot;, principalDetails.getUser().getUserName())
                .sign(Algorithm.HMAC512(&quot;server-secret&quot;)); // server만 알고있는 secret값 으로 서명

        response.addHeader(&quot;Authentication&quot;,&quot;Bearer &quot;+jwtToken);  // 사용자에게 응답
        System.out.println(&quot;==================토큰 생성==================&quot;);
        System.out.println(&quot;name : Authentication&quot;);
        System.out.println(&quot;value: Bearer &quot;+ jwtToken);

    }</code></pre>
<p>사용자 인증이 성공적으로 완료된 후 실행되는 successfulAuthentication 메서드입니다. 
이 메서드의 주요 목적은 인증된 사용자에게 JWT 토큰을 생성하고 응답과 함께 전달하는 것입니다. 코드 설명은 다음과 같이 진행할 수 있습니다.
<br></p>
<ol>
<li><p>successfulAuthentication 메서드는 인증이 성공한 후 호출됩니다.</p>
</li>
<li><p>인증 결과(authResult)에서 PrincipalDetails 객체를 가져옵니다. 이 객체는 인증된 사용자의 정보를 포함하고 있습니다.</p>
</li>
<li><p>JWT 토큰을 생성하기 위해 JWT.create()를 호출하고, 이어서 다음 작업을 수행합니다:</p>
</li>
</ol>
<ul>
<li>사용자 이름을 서브젝트로 설정합니다.</li>
<li>토큰의 만료 시간을 현재 시간으로부터 10분 후로 설정합니다.</li>
<li>사용자의 ID와 사용자 이름을 클레임으로 추가합니다.</li>
<li>서버의 비밀 키(&quot;server-secret&quot;)를 사용하여 토큰을 서명합니다.</li>
</ul>
<ol start="4">
<li>생성된 JWT 토큰을 응답 헤더에 추가합니다. 헤더 이름은 &quot;Authentication&quot;이고, 값은 &quot;Bearer &quot; + jwtToken입니다.
<br><br><h3 id="postman으로-로그인-완료시-클라이언트에게-토큰과함께-응답하는지-확인하기">postman으로 로그인 완료시 클라이언트에게 토큰과함께 응답하는지 확인하기</h3>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/d1d65887-b09a-45c1-91ee-a6b3c24de92a/image.png" alt="">
json 방식으로 로그인 요청을 했을 때 
정상적으로 사용자 인증이되면 successfulAuthentication 메서드에서 토큰을 만들어 응답 헤더에 토큰값을 넣어줍니다.</p>
<p>Authentication 의 value값으로 토큰이 잘 들어간 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/8f534a3c-2fc7-450c-b667-398b09e76ad7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - json 로그인 요청에 대한 파싱과 사용자 인증 테스트  ]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%EA%B0%95%EC%A0%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%EA%B0%95%EC%A0%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 02 May 2023 17:39:50 GMT</pubDate>
            <description><![CDATA[<h3 id="postman-로그인-테스트---x-www-form-urlencoded-회원정보-send">postman 로그인 테스트 - x-www-form-urlencoded 회원정보 send</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/0af41936-4334-42fd-91e0-e2ecbd105124/image.png" alt="">
사용자가 로그인시도를 할 때 요청 본문의 데이터를 한 줄씩 읽어 출력해 봤습니다.</p>
<br>


<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/23afcb9e-daeb-4644-ac26-83c24b4a4b22/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/9c042977-1e5d-4484-a994-81021b8e1f7f/image.png" alt=""></p>
<h3 id="postman-로그인-테스트---json-회원정보-send">postman 로그인 테스트 - json 회원정보 send</h3>
<br>


<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/2fff82ff-a39f-4b3f-9c6c-431e3b465b23/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/60677a0e-3272-4bff-b9cf-16a53ef7840a/image.png" alt=""></p>
<p>각 다른식으로 출력되는 것을 확인할 수 있었습니다.</p>
<p>클라이언트로부터 전송된 데이터를 적절하게 처리하고 분석하기 위해 파싱을 사용해 인증을 수행해야합니다.</p>
<p>파싱을 사용하는 이유는 클라이언트로부터 전송된 데이터 요청에 필요한 인증정보를 추출하고 이를 사용하여 인증을 수행하기 위함입니다.</p>
<p><br><br><hr></p>
<h3 id="json-으로-넘어온-값을-파싱하기">json 으로 넘어온 값을 파싱하기</h3>
<pre><code class="language-java">package com.choigoyo.config.JWT;

import com.choigoyo.config.auth.PrincipalDetails;
import com.choigoyo.entity.UserEntityJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    /**
     * Spring Security 의 UsernamePasswordAuthenticationFilter 를 확장하여 사용하게되면
     * http://localhost:8081/login 주소로 userName,password 를 post 형식으로 전송하면
     * UsernamePasswordAuthenticationFilter 가 동작함 SecurityConFig 클래스에 form 로그인을 비활성화 시켜서 동작하지 않는 상태이나,
     * JwtAuthenticationFilter 를 다시 SecurityConFig 클래스에 등록해주면 동작하게됨
     */

    private final AuthenticationManager authenticationManager;


    // http://localhost:8081/login 요청이 들어오면 authenticationManager 통해서 로그인시도
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println(&quot;JwtAuthenticationFilter : 로그인 시도중...&quot;);

        // 1 사용자의 userName, password 를 받는다
        try {
            // json 데이터를 파싱해줌
            ObjectMapper objectMapper = new ObjectMapper();
            UserEntityJWT userEntityJWT = objectMapper.readValue(request.getInputStream(), UserEntityJWT.class);
            // 유저에 정보가 잘 담겼는지 print 해보기
            System.out.println(userEntityJWT);
            // 토큰 생성(userName, password 를 담은)
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(userEntityJWT.getUserName(),userEntityJWT.getPassword());
            // authenticationManager.authenticate() 인증 수행하기
            // 그리고 PrincipalDetailsService 의 loadByUsername() 함수가 실행됨
            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            System.out.println(request.getInputStream()); // request.getInputStream() 은 HttpServletRequest 객체로부터 입력 스트림을 가져오는 데 사용
            // 클라이언트 또는 다른 웹브라우저에서 서버로 전송된 데이터를 읽는데 사용할 수 있는 InputStream 객체
            // authenticate 객체가 session 영역에 저장됨
            PrincipalDetails principalDetails = (PrincipalDetails)authenticate.getPrincipal();
            System.out.println(&quot;==================로그인 완료==================&quot;); // 구분선
            System.out.println(&quot;principalDetails userName :&quot;+principalDetails.getUsername());
            System.out.println(&quot;===============================================&quot;); // 구분선
            return authenticate;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    // 순서 -&gt;  attemptAuthentication 에서 인증이 정상적으로 실행되고 successfulAuthentication 메서드 실행
    // JWT 토큰을 만들어서 request 요청한 사용자에게 토큰을 응답과함께 전달
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        System.out.println(&quot;사용자 인증이 완료되어 successfulAuthentication 메서드가 실행됩니다.&quot;);
        super.successfulAuthentication(request, response, chain, authResult);
    }

}

</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/57c18382-7326-410c-962b-3885e907e271/image.png" alt=""></p>
<p>객체에 잘 담겨 출력되었습니다.
이 값으로 로그인 시도를 하기위해 토큰을 만들어야합니다.
( formlogin을 비활성화해서 직접 만들어야합니다. ) </p>
<p>코드를 순서대로 설명하자면 아래와 같습니다.
<br></p>
<ol>
<li>ObjectMapper 객체를 생성합니다.
ObjectMapper는 Jackson 라이브러리에서 제공하는 클래스로, JSON 데이터를 Java 객체로 변환하거나 Java 객체를 JSON으로 변환할 때 사용합니다.<br></li>
<li>request.getInputStream()을 사용하여 UserEntityJWT 객체를 생성합니다.
ObjectMapper의 readValue() 메소드를 사용하여 요청 본문의 JSON 데이터를 UserEntityJWT 클래스의 객체로 변환합니다. 이 작업을 통해 사용자가 제공한 userName과 password 정보가 userEntityJWT 객체에 저장됩니다.<br></li>
<li>System.out.println(userEntityJWT)를 호출하여 변환된 userEntityJWT 객체의 내용을 출력합니다. 이를 통해 요청 본문의 데이터가 제대로 변환되었는지 확인할 수 있습니다.<br></li>
<li>UsernamePasswordAuthenticationToken 객체를 생성합니다.
UserEntityJWT 객체에서 userName과 password 정보를 가져와 UsernamePasswordAuthenticationToken 객체를 생성합니다. 이 토큰은 인증 프로세스에서 사용됩니다.<br></li>
<li>authenticationManager.authenticate() 메소드를 호출하여 인증을 수행합니다.
생성된 UsernamePasswordAuthenticationToken 객체를 사용하여 authenticationManager의 authenticate() 메소드를 호출합니다. 이 때 PrincipalDetailsService의 loadByUsername() 메소드가 실행되며, 인증이 성공적으로 수행되면 Authentication 객체를 반환합니다.<br></li>
<li>request.getInputStream()을 출력합니다.
마지막으로 System.out.println(request.getInputStream())를 호출하여 InputStream 객체를 출력합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/955a4e7f-f772-4731-9150-5fa87aa745d6/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/127e7599-7570-4dbe-a41b-5f6babb5beef/image.png" alt=""></p>
<p>attemptAuthentication 사용자 인증이 완료되면, successfulAuthentication 메서드가 실행됩니다.</p>
<p>정상적으로 메서드가 동작하는지 테스트하기위해 print문으로 출력해보겠습니다. </p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/8256e7f4-e0e8-4cbd-b55b-c8bbe4d32de6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - 회원가입 로직 작성하기]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A1%9C%EC%A7%81-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%A1%9C%EC%A7%81-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 02 May 2023 13:09:59 GMT</pubDate>
            <description><![CDATA[<p>로그인을 학습하기 전 회원가입 로직을 간단하게 만들었습니다.
먼저, 회원가입시 입력한 비밀번호를 암호화 하기위해 BCryptPasswordEncoder 를 추가해줍니다.</p>
<h3 id="jwtapplication">JwtApplication</h3>
<pre><code class="language-java">package com.choigoyo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
public class JwtApplication {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    public static void main(String[] args) {
        SpringApplication.run(JwtApplication.class, args);
    }

}</code></pre>
<h3 id="restapicontroller">RestApiController</h3>
<p>회원정보를 DB에 저장하기위해 UserRepository를 @Autowired 하고,
비밀번호를 DB에 저장시 보안을 위해 BCryptPasswordEncoder 를 @Autowired 합니다.</p>
<p>/join 요청이 들어왔을 때 처리를 위해 PostMapping타입으로 컨트롤러를 간단하게 합니다.</p>
<pre><code class="language-java">package com.choigoyo.controller;

import com.choigoyo.entity.UserEntityJWT;
import com.choigoyo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestApiController {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @GetMapping(&quot;/&quot;)
    public String home(){
        return &quot;&lt;h1&gt;HOME&lt;/h1&gt;&quot;;
    }

    @PostMapping(&quot;/token&quot;) // 토큰 테스트를 위해 임시 컨트롤러 생성
    public String token(){
        return &quot;&lt;h1&gt;TOKEN&lt;/h1&gt;&quot;;
    }

    @PostMapping(&quot;join&quot;)
    public String join(@RequestBody UserEntityJWT userEntityJWT) {
        userEntityJWT.setPassword(bCryptPasswordEncoder.encode(userEntityJWT.getPassword())); // 비밀번호 암호화
        userEntityJWT.setRole(&quot;user&quot;); // 회원가입 기본 역할 세팅
        userRepository.save(userEntityJWT);
        return &quot;회원가입이 완료되었습니다.&quot;;
    }
}
</code></pre>
<h3 id="postman으로-회원가입에-필요한-정보-send해보기">postman으로 회원가입에 필요한 정보 send해보기</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/e05bd26f-feee-440d-9642-d7c373d4d01e/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/144a9870-295c-42aa-b832-10663be37a79/image.png" alt=""></p>
<p>json형식으로 각 값을 보내 컨트롤러가 정상 작동하여 회원가입이 완료되었다는 print문이 출력되었습니다.</p>
<h3 id="database">DataBase</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/99ba6a5f-1101-46ac-b005-03eeca287476/image.png" alt=""></p>
<p>저장된 값을 확인해 보면 회원정보가 저장되면서 id 값은 자동으로 값이 부여되었고 
비밀번호는 입력한 값이 노출되지 않도록 암호화가 잘 되어있습니다
role은 컨트롤러에서 기본적으로 user 로 값을 주었습니다.
userName값에도 입력한 값이 정상적으로 저장된 것을 확인할 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - 토큰 로그인 시도]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%ED%86%A0%ED%81%B0-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%8B%9C%EB%8F%84</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%ED%86%A0%ED%81%B0-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%8B%9C%EB%8F%84</guid>
            <pubDate>Tue, 02 May 2023 09:06:02 GMT</pubDate>
            <description><![CDATA[<h3 id="패키지-구조">패키지 구조</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/f2a5a458-5435-49c7-9476-12218dc7ddb4/image.png" alt=""></p>
<h3 id="principaldetails">PrincipalDetails</h3>
<pre><code class="language-java">package com.choigoyo.config.auth;

import com.choigoyo.entity.UserEntityJWT;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@NoArgsConstructor // 기본 생성자
public class PrincipalDetails implements UserDetails {
    private UserEntityJWT user;

    // 생성자
    public PrincipalDetails (UserEntityJWT user){
        this.user = user;
    }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        Collection&lt;GrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();
        /*
        *  user.getRoleList()를 통해 해당 유저가 가지고 있는 모든 역할(Role)을 가져온 뒤, 각각의 역할에 대한 GrantedAuthority 객체를 생성하여 이를 authorities 리스트에 추가
        *  GrantedAuthority는 권한 정보를 나타내는 인터페이스
        * */
        user.getRoleList().forEach(r -&gt;{
            authorities.add(() -&gt; r);
        });
        return null;
    }

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

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

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}</code></pre>
<p>getAuthorities() 메소드는 해당 유저 객체가 가지고 있는 권한 정보를 Spring Security가 이해할 수 있는 형태로 반환하는 역할을 합니다.</p>
<p>위 코드에서는 user.getRoleList()를 통해 해당 유저가 가지고 있는 모든 역할(Role)을 가져온 뒤, 각각의 역할에 대한 GrantedAuthority 객체를 생성하여 이를 authorities 리스트에 추가하고 있습니다.</p>
<p>Spring Security에서 GrantedAuthority는 권한 정보를 나타내는 인터페이스이며, 각각의 권한은 문자열 형태로 저장됩니다. </p>
<p>위 코드에서는 람다식을 이용하여 역할(Role) 이름을 GrantedAuthority 객체로 변환하여 authorities 리스트에 추가하고 있습니다.</p>
<p>따라서 getAuthorities() 메소드는 해당 유저 객체가 가지고 있는 모든 역할에 대한 GrantedAuthority 리스트를 반환하는 역할을 합니다.</p>
<p>이외 @Override 메서드는 spring security 시리즈에 정리하였으므로 다음으로 넘어가겠습니다. </p>
<hr>

<h3 id="principaldetailsservice">PrincipalDetailsService</h3>
<pre><code class="language-java">package com.choigoyo.config.auth;

import com.choigoyo.entity.UserEntityJWT;
import com.choigoyo.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        System.out.println(&quot;PrincipalDetails 의 loadUserByUsername()&quot;);
        UserEntityJWT userEntity = userRepository.findByUserName(userName);
        return new PrincipalDetails(userEntity);
    }
}</code></pre>
<h3 id="userrepository">UserRepository</h3>
<pre><code class="language-java">package com.choigoyo.repository;

import com.choigoyo.entity.UserEntityJWT;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository&lt;UserEntityJWT,Long&gt; {
    UserEntityJWT findByUserName(String userName);
}
</code></pre>
<p><br><br><br></p>
<ul>
<li>postman으로 요청 시도</li>
</ul>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/1a9beeaa-9649-4264-9a16-e720c4936e56/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/de82a289-ab05-423a-82f0-fa9f29bfac1e/image.png" alt="">
PrincipalDetailsService 클래스는 
<a href="http://localhost:8081/login%EC%9A%94%EC%B2%AD%EC%9D%B4">http://localhost:8081/login요청이</a> 들어왔을 때 동작을 합니다.
하지만 설정에서 Spring Security 기본 로그인 요청 주소를 SecurityConfig 설정에 form 로그인을 사용하지 않겠다 정했기 때문에 404에러가 발생하게됩니다.</p>
<h3 id="jwtauthenticationfilter">JwtAuthenticationFilter</h3>
<p>로그인 시도를 테스트하기위해 필터를 추가해 주었습니다.</p>
<pre><code class="language-java">package com.choigoyo.config.JWT;

import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;


    // http://localhost:8081/login 요청이 들어오면 authenticationManager 통해서 로그인시도
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println(&quot;JwtAuthenticationFilter : 로그인 시도중...&quot;);
        return super.attemptAuthentication(request, response);
    }
}
</code></pre>
<p>Spring Security 의 UsernamePasswordAuthenticationFilter 를 확장하여 사용하게되면
사용자가 <a href="http://localhost:8081/login">http://localhost:8081/login</a> 주소로 userName,password 를 post 형식으로 전송하였을 때 
UsernamePasswordAuthenticationFilter 가 동작합니다.</p>
<p>SecurityConFig 클래스에 form 로그인을 비활성화 시켜서 동작하지 않는 상태이나,
JwtAuthenticationFilter 를 다시 SecurityConFig 클래스에 등록해주면 동작하게됩니다.</p>
<h3 id="securityconfig">SecurityConFig</h3>
<p>만들어 둔 필터를 추가해주고 postman으로 로그인 요청을 보내면 
필터가 잘 작동하는지 확인해보겠습니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/25d8e10e-66af-4371-b32d-fb914b2c6de0/image.png" alt=""></p>
<h3 id="postman-으로-post요청-send-해보기">postman 으로 post요청 send 해보기</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/93e8d9b8-1a44-4725-96f9-680a0b39f808/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/79fd2d63-0ea7-49f7-b14d-1d3f856ccc15/image.png" alt="">
404 에러는 사라졌고, 500에러가 생겼지만 아직 코드를 추가하지 않아서 생긴 오류니 무시하고 intelliJ 콘솔창을 확인해보겠습니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/aaba61ea-5b6a-4b54-a9e3-52046269d000/image.png" alt=""></p>
<p>메서드가 정상적으로 실행되어 print문이 출력됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT -임시 토큰 만들어서 테스트하기]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%EC%9E%84%EC%8B%9C-%ED%86%A0%ED%81%B0-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%EC%9E%84%EC%8B%9C-%ED%86%A0%ED%81%B0-%EB%A7%8C%EB%93%A4%EC%96%B4%EC%84%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 30 Apr 2023 12:41:53 GMT</pubDate>
            <description><![CDATA[<h3 id="filter1">Filter1</h3>
<pre><code class="language-java">package com.choigoyo.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class Filter1 implements javax.servlet.Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;

        String authorization = req.getHeader(&quot;Authorization&quot;);
        System.out.println(authorization);

        System.out.println(&quot;필터1&quot;);
        chain.doFilter(req,res);

    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/9f88ffab-4652-4601-b0e4-b626294c3048/image.png" alt=""></p>
<p>HttpServletRequest의 getHeader() 메소드는 요청에 특정 헤더가 없는 경우 null을 반환합니다. 
따라서 Authorization 헤더가 없는 요청에서는 null이 출력될 수 있습니다.</p>
<hr>

<h3 id="postman-을-사용해서-authorization-넘겨보기">postman 을 사용해서 Authorization 넘겨보기</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/3e7379c7-8296-468f-89cc-7091db8321df/image.png" alt="">
postman을 검색하면 다운로드 가능합니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/0cf09ffd-1339-47e6-ad5b-f67732c1b26d/image.png" alt="">
테스트 하기 header에 Authorization 값이 들어있지 않아서 null이 들어있었는데,
postman으로 hello라는 값을 post형식으로 넘겨보겠습니다.
<img src="https://velog.velcdn.com/images/choigoyo_o/post/2d0bb914-bd55-47aa-b024-49a4a0e10e4f/image.png" alt="">
정상적으로 값을 출력하는 것을 확인했습니다.
이제는 어떤 조건을 만들어서 조건을 만족하면 컨트롤러와 매핑되고 
조건에 부합하지 않으면 컨트롤러에 접근하지 못하도록 필터 코드를 수정해보겠습니다.</p>
<p>if-else 문을 사용해서 조건을 걸어 choigoyo라는 값이 일치하면 컨트롤러와 매핑되어 뷰페이지를 반환하고 아니면 인증실패를 띄우도록 했습니다.</p>
<pre><code class="language-java">import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Filter1 implements javax.servlet.Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;

        // post 요청일 경우에만
        // 토큰이 넘어오면 인증 시도 넘어오지 않으면 컨트롤러로 접근 불가
        if (req.getMethod().equals(&quot;POST&quot;)) {
            System.out.println(&quot;POST 요청이 확인되었습니다.&quot;);
            String authorization = req.getHeader(&quot;Authorization&quot;);
            System.out.println(authorization);
            System.out.println(&quot;필터1&quot;);

            // header 의 Authorization 이 choigoyo 일때만 체인필터를 타도록 조건 걸기
            if (authorization.equals(&quot;choigoyo&quot;)) {
                chain.doFilter(req, res);
            } else {
                PrintWriter out = res.getWriter();
                out.println(&quot;인증 실패&quot;);
            }
        }
    }
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/de098b87-c2bf-4887-ab2e-394eb55a2461/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/3c85c52c-263a-4a0a-86e5-1f53bbe0e880/image.png" alt=""></p>
<hr>

<h3 id="토큰을-언제-만들어-줘야할까-">토큰을 언제 만들어 줘야할까 ?</h3>
<p>사용자의 로그인 정보가 검증되어 사용자를 인증했을 때 토큰을 만들어서
로그인이 완료되면 로그인 완료페이지와 같은 응답과 함게 보내면 됩니다. </p>
<p>토큰을 받은 사용자는 서버에 다른 요청들을 할 때 마다 HTTP 헤더의 Authorization에 value값으로 토큰을 가지고 서버에 요청을 보내게됩니다.</p>
<p>이제 서버는 사용자가 응답과함께 보낸 토큰을 대표적으로 RSA 또는 HS256과 같은 방식으로 검증만 하면 됩니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - Filter 등록 테스트]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-Filter-%EB%93%B1%EB%A1%9D-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@choigoyo_o/JWT-Filter-%EB%93%B1%EB%A1%9D-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Sat, 29 Apr 2023 18:14:27 GMT</pubDate>
            <description><![CDATA[<h1 id="filter-">Filter ?</h1>
<p>JWT에서 Filter는 인증 및 인가를 처리하는 역할을 합니다. 
일반적으로 Spring Security와 연동되어 사용되며, 인증 및 인가를 처리하기 전에 Filter가 요청을 가로채고 필요한 처리를 수행한 후 다음 단계로 요청을 전달합니다. </p>
<p>JWT에서 Filter는 JWT 토큰을 검증하고, 만료 기간을 확인하며, 유저 정보를 검증하고, 권한을 확인하는 등의 작업을 수행합니다. 이를 통해 서버는 보안성을 높이면서도 유연한 인증 및 인가 처리가 가능합니다.</p>
<h2 id="인증과-인가-">인증과 인가 ?</h2>
<p>인증(Authentication)은 사용자가 누구인지 확인하는 과정이며, 인가(Authorization)는 해당 사용자가 어떤 자원에 접근할 수 있는 권한이 있는지 확인하는 과정입니다.</p>
<p>인증은 대표적으로 사용자의 아이디와 패스워드를 입력하여 로그인하는 과정이 있습니다. 
이를 통해 사용자가 누구인지 확인하고, 로그인한 사용자에게는 인증토큰(Authentication Token)이 발급됩니다.</p>
<p>인가는 인증된 사용자가 어떤 자원(Resource)에 접근할 수 있는지 여부를 확인하는 과정입니다. 예를 들어, 관리자 권한을 가진 사용자는 관리자 페이지에만 접근할 수 있도록 설정할 수 있습니다. 이를 위해 사용자의 권한 정보가 필요하며, 대표적으로 권한 부여(Authorization)를 통해 권한을 관리합니다.</p>
<h3 id="패키지-구조">패키지 구조</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/71deab1e-9187-481b-bc74-07ab3b2e5a9c/image.png" alt=""></p>
<h3 id="filter1">Filter1</h3>
<pre><code class="language-java">package com.choigoyo.filter;


import javax.servlet.*;
import java.io.IOException;

public class Filter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(&quot;필터1&quot;);
        chain.doFilter(request,response);

    }
}</code></pre>
<p>Filter를 관리할 패키지를 생성하고 테스트 할 Filter파일을 작성하여 테스트해보겠습니다.</p>
<h3 id="securityconfig">SecurityConfig</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/9ae5836d-8e19-42b5-b52c-6123ebecffed/image.png" alt=""></p>
<pre><code class="language-java">httpSecurity.addFilter(new Filter1());</code></pre>
<p>위 코드를 SecurityConfig 파일에 추가하고  실행을 누르면 아래와 같은 오류가 발생합니다.</p>
<pre><code class="language-terminal">Caused by: org.springframework.beans.BeanInstantiationException: 
Failed to instantiate [javax.servlet.Filter]: 
Factory method &#39;springSecurityFilterChain&#39; threw exception; 
nested exception is java.lang.IllegalArgumentException: 
The Filter class com.choigoyo.filter.Filter1 does not have a registered order and cannot be added without a specified order. 
Consider using addFilterBefore or addFilterAfter instead.
</code></pre>
<p>오류 메시지를 확인해보면 filter.Filter1 클래스가 등록된 순서가 없어서 springSecurityFilterChain 에 추가할 수 없다고 나와 있습니다.</p>
<p>이를 해결하는 방법으로는 Filter1 클래스에 @Order 어노테이션을 사용하여 등록 순서를 명시하거나, 
SecurityConfig.configure 메서드에 addFilterBefore or addFilterAfter 를 사용하여 순서를 필터 순서를 지정해줄 수도 있습니다.</p>
<h3 id="spring-필터체인-순서">Spring 필터체인 순서</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/b8586c53-bb5b-4a07-8798-5ba3638136a3/image.png" alt=""></p>
<h3 id="securityconfig-필터-순서-지정해주기">SecurityConfig 필터 순서 지정해주기</h3>
<pre><code class="language-java">@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CorsFilter corsFilter;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        // Filter의 순서를 지정해 줌
        httpSecurity.addFilterBefore(new Filter1(),BasicAuthenticationFilter.class); 
        httpSecurity.csrf().disable();
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다
                .and()
                .addFilter(corsFilter)  // @CrossOrigin(인증 없을 때) , 시큐리티 필터에 등록(인증 있을 때)
                .formLogin().disable() // id pw 로그인을 form 로그인을 하지 X
                .httpBasic().disable()
                .authorizeRequests()
                .antMatchers(&quot;/api/v1/user/**&quot;)
                .access(&quot;hasRole(&#39;user&#39;) or hasRole(&#39;manager&#39;) or hasRole(&#39;admin&#39;)&quot;)
                .antMatchers(&quot;/api/v1/manager/**&quot;)
                .access(&quot;hasRole(&#39;manager&#39;) or hasRole(&#39;admin&#39;)&quot;)
                .antMatchers(&quot;/api/v1/admin/**&quot;)
                .access(&quot;hasRole(&#39;admin&#39;)&quot;)
                .anyRequest().permitAll(); // 설정 외 모든 경로는 인증없이 접근가능
    }
}</code></pre>
<blockquote>
<p>httpSecurity.addFilterBefore(new Filter1(),BasicAuthenticationFilter.class); </p>
</blockquote>
<p>이전 코드와 다르게 Filter1이  BasicAuthenticationFilter 이전에 동작하도록 수정했습니다.</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/82831c7e-f5c9-43ec-9aab-0cb1909d150d/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/eca3a587-f121-45b8-9e4c-79cdb9e42635/image.png" alt=""></p>
<p>또 다른 방법으로는 config 패키지에 FilterConfig 클래스를 작성하여 설정해주는 방법도 있습니다.</p>
<h3 id="filterconfig">FilterConfig</h3>
<pre><code class="language-java">package com.choigoyo.config;

import com.choigoyo.filter.Filter1;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // IoC (Inversion of Control) 제어의 역전을 의미
public class FilterConfig {

    @Bean
    public FilterRegistrationBean&lt;Filter1&gt; filter1(){
        FilterRegistrationBean&lt;Filter1&gt; bean = new FilterRegistrationBean&lt;&gt;(new Filter1());
        bean.addUrlPatterns(&quot;/*&quot;); // 모든 요청에서 필터를 적용
        bean.setOrder(0); // 필터의 순서 지정 / 우선순위 가장 높음
        return bean;
    }
}</code></pre>
<p>FilterConfig 파일에 필터에 대한 내용을 설정해주고 
<img src="https://velog.velcdn.com/images/choigoyo_o/post/063242d4-e786-4702-8ce5-193e53875bc8/image.png" alt="">
<img src="https://velog.velcdn.com/images/choigoyo_o/post/ae3184b3-a0a9-4359-9a0b-3d8142c32bb9/image.png" alt=""></p>
<p>웹브라우저에 요청을 해서 오류 없이 잘 동작하는 것을 확인했습니다.</p>
<p>같은 방식으로 필요한 필터를 만들어 filterConfig에서 설정하여 실행하면 됩니다.</p>
<hr>

<h3 id="필터의-우선순위">필터의 우선순위</h3>
<p>이제 필터의 우선순위를 테스트해 보겠습니다. 먼저 우선순위 체크를 위해 필터1, 필터2, 필터3을 준비하고, 2개의 필터는 FilterConfig에 우선순위를 정한 뒤, 나머지 1개의 필터는 SecurityConfig에 넣어 실행해 보도록 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/090c8a7d-f4ac-493e-adcb-34dfab53a59c/image.png" alt=""></p>
<pre><code class="language-java">package com.choigoyo.config;

import com.choigoyo.filter.Filter1;
import com.choigoyo.filter.Filter2;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // IoC (Inversion of Control) 제어의 역전을 의미
public class FilterConfig {

    @Bean
    public FilterRegistrationBean&lt;Filter1&gt; filter1(){
        FilterRegistrationBean&lt;Filter1&gt; bean = new FilterRegistrationBean&lt;&gt;(new Filter1());
        bean.addUrlPatterns(&quot;/*&quot;); // 모든 요청에서 필터를 적용
        bean.setOrder(0); // 필터의 순서 지정 / 우선순위 가장 높음
        return bean;
    }
    @Bean
    public FilterRegistrationBean&lt;Filter2&gt; filter2(){
        FilterRegistrationBean&lt;Filter2&gt; bean = new FilterRegistrationBean&lt;&gt;(new Filter2());
        bean.addUrlPatterns(&quot;/*&quot;); // 모든 요청에서 필터를 적용
        bean.setOrder(1); // 필터의 순서 지정
        return bean;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/650acee7-7a5a-4162-85e4-eb6c52a389ee/image.png" alt=""></p>
<p>SecurityConfig에 등록한 Filter3이 가장먼저 실행되고 
이후 나머지 Filter1,Filter2 는 우선순위 지정에 따라 실행되었습니다.</p>
<p>SecurityConfig에 등록한 Filter3은 addFilterBefore 이나 addFilterAfter 모두 같은 결과가 나옵니다.</p>
<p>즉, Security 필터체인이 만들어낸 필터체인보다 먼저 실행이 된다는 것 입니다.</p>
<p>필터가 적용되는 시점을 Spring 필터체인 순서를 참고하여 SecurityConfig에 등록하여 사용할 수 있습니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - Bearer 인증 방식]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-Bearer-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@choigoyo_o/JWT-Bearer-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Sat, 29 Apr 2023 16:41:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/1a936f18-53d3-4132-83fd-d16107ef9b15/image.png" alt=""></p>
<p>위 그림은 session에 요청하고 응답하는 과정을 간단히 표현한 그림입니다.</p>
<p>여러 개의 서버를 사용하는 경우, 서버들이 세션을 공유할 수 없습니다. 즉, 사용자의 요청이 다른 서버로 전달되면 해당 서버는 그 전 서버와 공유하지 않는 별도의 세션을 생성하게 됩니다. </p>
<p>따라서, 사용자의 모든 요청에 대해 같은 세션을 유지하기 위해서는 어떤 서버에서 처리하느냐에 따라 세션이 다르게 유지되는 문제가 발생할 수 있습니다.</p>
<p>이러한 문제를 해결하기 위해서는, 서버들 간에 세션 정보를 공유하는 방법이 필요합니다. </p>
<p>대표적인 방법으로는 로드밸런싱 기능을 가진 로드밸런서를 사용하여 세션을 공유하거나, 세션을 서버 외부에 저장하는 세션 클러스터링 등이 있습니다.</p>
<p>하지만 이러한 방법들은 구현하기 어렵고, 관리하기도 복잡하기 때문에 서버를 추가할 때마다 그만큼 비용이 증가하게 됩니다. </p>
<p>따라서 최근에는 이러한 문제를 해결하기 위해 세션 대신 JWT(JSON Web Token)을 사용하는 경우가 많아졌습니다. </p>
<p>JWT는 서버에서 세션을 관리하는 것이 아니라 클라이언트에게 발급된 토큰을 통해 인증을 수행하므로, 서버 간의 세션 공유 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT - Bearer 인증]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-Bearer-%EC%9D%B8%EC%A6%9D</link>
            <guid>https://velog.io/@choigoyo_o/JWT-Bearer-%EC%9D%B8%EC%A6%9D</guid>
            <pubDate>Fri, 28 Apr 2023 15:49:21 GMT</pubDate>
            <description><![CDATA[<p>클라이언트가 서버측에 JavaScript로 Ajax 요청을 보내면, 보통 브라우저에서는 서버로부터 받은 쿠키를 사용하여 세션 ID를 저장합니다. </p>
<p>그리고 브라우저는 이 세션 ID를 다음 요청에서 서버로 전달합니다. 
하지만 이 방식은 다음과 같은 단점이 있습니다.</p>
<p>쿠키 사용 여부: 다른 도메인 간 요청이나 HTTP API 호출 같은 일부 상황에서는 쿠키를 사용할 수 없기 때문에 이 방식을 사용할 수 없습니다.</p>
<p>Scale-out을 위한 분산 환경에서 문제가 발생: 세션을 저장하는 기본적인 방법인 메모리에 저장된 세션을 사용하는 경우, 서버의 스케일 아웃이나 로드 밸런싱을 수행하는 경우 세션이 분산되어 문제가 발생할 수 있습니다.</p>
<p>메모리 부족: 서버 메모리에 저장된 세션 데이터가 너무 많으면 서버 성능에 영향을 미치거나 서버 자체가 다운될 수 있습니다.</p>
<hr>
JWT를 사용하면, 서버 측에서 세션을 유지하지 않아도 되므로 서버의 확장성이 높아지며, stateless 방식으로 인증이 가능합니다. 

<p>따라서, 클라이언트 측에서 서버에 대한 요청을 할 때 매번 JWT를 포함시켜 보내고, 서버에서는 JWT를 이용하여 인증을 처리하면 됩니다. </p>
<p>이를 통해, 서버 측에서 세션 관리와 같은 부담을 줄일 수 있습니다. </p>
<p>또한, JWT의 bearer 인증 방식을 이용하면, 클라이언트에서 JWT를 포함시켜 요청을 보내면 서버에서는 JWT의 유효성만 검사하고, 별도의 인증 정보를 확인하지 않아도 됩니다. </p>
<p>이러한 이유로, JWT를 사용하면 세션 관리의 단점을 해결하고, 확장성과 보안성을 높일 수 있습니다.</p>
<hr>

<p>JWT의 Bearer 인증 방식은 HTTP 헤더의 Authorization 필드를 이용하여 인증을 수행하는 방식입니다. </p>
<p>Bearer 인증 방식에서는 클라이언트가 인증 요청을 보낼 때, JWT 토큰을 HTTP 헤더의 Authorization 필드에 담아서 보내게 됩니다.</p>
<p>이때, JWT 토큰은 &quot;Bearer &quot; 문자열과 함께 Authorization 필드에 담겨서 전송됩니다. 
서버는 이 JWT 토큰을 수신해서 유효성 검증을 수행하고, 인증이 성공하면 요청에 대한 응답을 반환합니다.</p>
<p>Bearer 인증 방식은 간단하면서도 확장성이 높아서, 대부분의 웹 애플리케이션에서 사용됩니다. 또한, JWT 토큰을 이용하기 때문에 세션 관리를 하지 않아도 되고, 로그인 상태 유지 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT 학습을 위한 Security 설정]]></title>
            <link>https://velog.io/@choigoyo_o/JWT-%ED%95%99%EC%8A%B5%EC%9D%84-%EC%9C%84%ED%95%9C-Security-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@choigoyo_o/JWT-%ED%95%99%EC%8A%B5%EC%9D%84-%EC%9C%84%ED%95%9C-Security-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Fri, 28 Apr 2023 09:02:11 GMT</pubDate>
            <description><![CDATA[<h2 id="readmemd">README.md</h2>
<h4 id="json-web-token-학습">JSON Web Token 학습</h4>
<h4 id="개발-환경">개발 환경</h4>
<ol>
<li>Project - Gradle - Groovy</li>
<li>Language - Java 11</li>
<li>Spring Boot Ver - 2.7.11</li>
<li>Dependencies<ol>
<li>DEVELOPER TOOLS - Spring Boot DevTools</li>
<li>DEVELOPER TOOLS - Lombok</li>
<li>java-jwt&#39;, version: &#39;4.2.1&#39;</li>
<li>WEB - Spring Web </li>
<li>SECURITY - Spring Security </li>
<li>SQL - MySQL Driver</li>
<li>JPA </li>
</ol>
</li>
</ol>
<h3 id="buildgradle">build.gradle</h3>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    implementation group: &#39;com.auth0&#39;, name: &#39;java-jwt&#39;, version: &#39;4.2.1&#39;
    compileOnly &#39;org.projectlombok:lombok&#39;
    developmentOnly &#39;org.springframework.boot:spring-boot-devtools&#39;
    runtimeOnly &#39;com.mysql:mysql-connector-j&#39;
    annotationProcessor &#39;org.projectlombok:lombok&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
    testImplementation &#39;org.springframework.security:spring-security-test&#39;
}</code></pre>
<h3 id="패키지-구조">패키지 구조</h3>
<p><img src="https://velog.velcdn.com/images/choigoyo_o/post/1031bc8d-cf4e-4c6c-989a-fb912fa29e3b/image.png" alt=""></p>
<h3 id="userentityjwt">UserEntityJWT</h3>
<pre><code class="language-java">package com.choigoyo.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@NoArgsConstructor // 기본 생성자
@Data
@Entity
@Table(name =&quot;UserEntityJWT&quot;)// db에 생성될 테이블 이름
public class UserEntityJWT {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // id값 자동 증감
    private Long id; // 회원번호
    private String userName; // 회원 이름
    private String password; // 회원 비밀번호
    private String role; // 회원 역할 User, Manager ,Admin

    public List&lt;String&gt; getRoleList(){
        if (this.role.length() &gt; 0) {
            return Arrays.asList(this.role.split(&quot;,&quot;)); // 콤마 기준으로 잘라서 반환
        }
        return new ArrayList&lt;&gt;(); // null이 뜨는 것을 방지하기 위해 임시로 리스트 반환
    }

}
</code></pre>
<h3 id="restapicontroller">RestApiController</h3>
<pre><code class="language-java">package com.choigoyo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RestApiController {


    @GetMapping(&quot;/&quot;)
    public String home(){
        return &quot;&lt;h1&gt;HOME&lt;/h1&gt;&quot;;
    }
}
</code></pre>
<h3 id="securityconfig">SecurityConfig</h3>
<pre><code class="language-java">package com.choigoyo.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.web.filter.CorsFilter;


@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CorsFilter corsFilter;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다.
        .and()
                .addFilter(corsFilter)  // @CrossOrigin(인증 없을 때) , 시큐리티 필터에 등록(인증 있을 때)
                .formLogin().disable() // id pw 로그인을 form 로그인을 하지 X
                .httpBasic().disable()
                .authorizeRequests()
                .antMatchers(&quot;/api/v1/user/**&quot;)
                .access(&quot;hasRole(&#39;user&#39;) or hasRole(&#39;manager&#39;) or hasRole(&#39;admin&#39;)&quot;)
                .antMatchers(&quot;/api/v1/manager/**&quot;)
                .access(&quot;hasRole(&#39;manager&#39;) or hasRole(&#39;admin&#39;)&quot;)
                .antMatchers(&quot;/api/v1/admin/**&quot;)
                .access(&quot;hasRole(&#39;admin&#39;)&quot;)
                .anyRequest().permitAll(); // 설정 외 모든 경로는 인증없이 접근가능
    }
}
</code></pre>
<p>이전 Spring Security와 다른점은 
STATELESS로 세션을 사용하지 않고, CrossOrigin 정책을 사용하지 않는다는 설정을하였고 
Formlogin을 사용하지 않았습니다.</p>
<h4 id="참고">참고</h4>
<blockquote>
<p>httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다.</p>
</blockquote>
<p>JWT를 사용하는 경우, 토큰 자체가 인증에 필요한 모든 정보를 가지고 있기 때문에 서버 측에서는 세션을 유지할 필요가 없습니다. 따라서 Spring Security 설정에서 STATELESS를 사용하여 서버가 상태를 유지하지 않도록 구성합니다.</p>
<blockquote>
<p>.addFilter(corsFilter)</p>
</blockquote>
<p>CrossOrigin 정책은 브라우저의 보안상의 이유로 동일한 출처에서 오는 리소스에 대한 제한을 두는 것입니다. 즉, 서로 다른 출처에서 오는 리소스 간에 접근을 제어합니다. JWT를 사용하는 경우, 인증 정보를 포함하는 토큰이 서버에 의해 생성되어 다른 도메인의 클라이언트에서도 인증이 가능해야 합니다. 따라서 CrossOrigin 정책을 따르지 않는 것입니다.</p>
<blockquote>
<p>.formLogin().disable()</p>
</blockquote>
<p>Form login은 Spring Security에서 기본적으로 제공하는 로그인 방식 중 하나입니다. 그러나 JWT를 사용하는 경우, 로그인 페이지를 통해 인증하는 방식이 아닌, 클라이언트에서 JWT를 생성하여 서버에 전달하고, 서버에서는 JWT의 유효성을 검증하여 인증하는 방식을 사용합니다. 따라서 Form login 설정은 필요하지 않습니다.</p>
<blockquote>
<p>.httpBasic().disable()</p>
</blockquote>
<p>.httpBasic().disable()는 Spring Security의 기본 인증 방식 중 하나인 HTTP Basic Authentication을 비활성화하는 설정입니다. 이 설정을 통해 HTTP Basic 인증을 사용하지 않고 JWT 토큰을 사용한 인증을 적용할 수 있습니다.</p>
<p>HTTP Basic Authentication은 클라이언트에서 사용자 이름과 비밀번호를 직접 전송하는 방식으로, 인증 정보가 암호화되지 않고 평문으로 전송되므로 보안에 취약합니다. 따라서 JWT 토큰과 같이 보안 수준이 높은 인증 방식을 사용하기 위해 HTTP Basic Authentication을 비활성화하고 다른 방식의 인증을 적용하는 것이 좋습니다.</p>
<h3 id="corsconfig">CorsConfig</h3>
<pre><code class="language-java">package com.choigoyo.config;


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

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); // 서버가 응답할 때 json 을 js 에서 처리할 수 있게 할지 설정
        config.addAllowedOrigin(&quot;*&quot;); // 모든 ip에 응답 허용
        config.addExposedHeader(&quot;*&quot;); //  모든 header 에 응답 허용
        config.addAllowedMethod(&quot;*&quot;); // 모든 post ,get, delete , patch 요청을 허용
        source.registerCorsConfiguration(&quot;/api/**&quot;,config);
        return new CorsFilter(source);
    }

}
</code></pre>
<hr>

<p>인증없이 index 페이지를 반환하는 것을 확인하기
<img src="https://velog.velcdn.com/images/choigoyo_o/post/f0df1a42-659c-4301-be3a-5c7a9f6eeba9/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>