<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>얼레벌레명래 개발자</title>
        <link>https://velog.io/</link>
        <description>독자보다 필자를 위해 포스팅합니다</description>
        <lastBuildDate>Sun, 25 Jun 2023 06:15:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>얼레벌레명래 개발자</title>
            <url>https://velog.velcdn.com/images/kim_think_rae/profile/21195210-030b-4b2e-b1f6-f8445515ac45/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 얼레벌레명래 개발자. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kim_think_rae" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[flutter]]></title>
            <link>https://velog.io/@kim_think_rae/fluuter</link>
            <guid>https://velog.io/@kim_think_rae/fluuter</guid>
            <pubDate>Sun, 25 Jun 2023 06:15:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/e6b43392-4c09-49d0-b010-15e3c328f67a/image.png" alt=""></p>
<h1 id="flutter-란-">Flutter 란 ?</h1>
<p>Flutter는 Google에서 개발 및 지원하는 오픈 소스 프레임워크입니다. 프런트엔드 및 풀 스택 개발자는 Flutter를 사용해 다수의 플랫폼에 대한 애플리케이션의 사용자 인터페이스(UI)를 단일 코드 베이스로 구축합니다.</p>
<p>Flutter가 2018년에 출시되었을 때는 주로 모바일 앱 개발을 지원했습니다. 이제 Flutter는 iOS, Android, 웹, Windows, MacOS, Linux의 여섯 가지 플랫폼에 대한 애플리케이션 개발을 지원합니다.</p>
<h2 id="장점">장점</h2>
<p>크로스 플랫폼 기반이기때문에 당연하게도 개발기간이 줄어들고 원래 IOS, AOS, WEB 모두 지원하게끔 개발하려면 각 개발팀이 필요했을테지만 flutter를 잘 사용하기만 한다면 이를 줄여낼 수 있기때문에 비용에서도 이점이 있다. </p>
<blockquote>
<p>이런 비용에서의 이점을 경영자들은 &quot;생산성증가&quot;라는 말로 포장한다. (농담입니다.)</p>
</blockquote>
<p>여기까지만 들으면 정말 낭만있는 프레임워크가 아닐까 싶다</p>
<p>근데 수반하는 단점도 장점만큼 치명적이다..</p>
<h2 id="단점">단점</h2>
<p>당연하게도 일반 네이티브 앱 보다 용량이 크다는 단점이 있고 
구글이 이 사업을 포기하면 망한다..
(구글은 사업 손절치는대에 있어서 굉장히 선수라더라)</p>
<p>플러그인 지원을하지 않으면 네이티브 개발을 해야한다. 
어떤 앱을 만들지는 모르겟으나 그 기능들이 네이티브에 치중되어 있다면 플러터의 장점 역시 사라질것이다.</p>
<h2 id="특징">특징</h2>
<p>Dart가 기존 C언어 문법과 거의 같다고 하던대 방식은 또 싱글 쓰레드의 비동기처리를 지원한다. 처음 개발하다가 이때문에 문제가 생겨서 조금 고생했었다.. 아무튼 언어 자체는 친숙할 수 있었지만 공부가 필요한건 어쩔 수 없다. 처음보는 문법들이 있었는데 그중에 하나를 대표적으로 설명하자면 </p>
<p>Dart 자체의 변수들은 기본적으로 null 값을 넣을 수 없다.
그래서 이때 사용할 수 있는것이 ? 키워드 인대 
변수 선언 시 
int? a = 15;
이런식으로 선언하게 되면 a의 값을 null로써도 사용 가능한거다 
이 외에도 _키워드라던지 특이한것들이 많아서 다른언어의 경험이 있더라도 꼭 짚고 넘어가야하는 부분들이 있는 듯 하다.</p>
<p>또한 싱글 쓰레드 방식의 비동기 처리를 지원한다고 한다. 이는 JS와 굉장히 비슷하다.</p>
<h2 id="react-native와-비교하자면-">react-native와 비교하자면 ?</h2>
<p>react-native는 js 기반의 개발자라면 좀 더 친숙하게 다가올것이라고 생각한다. 
둘의 장,단점에는 무엇이 있을까 ? </p>
<h2 id="결론">결론</h2>
<p>내 생각에는 결국 flutter 하나만 공부해서는 먹고살기는 어렵다. 결국, native개발이 필요할것이고 flutter 이후 native 하나를 더 붙여서 공부하는게 좋을듯 하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Amazon Cloud Front]]></title>
            <link>https://velog.io/@kim_think_rae/Amazon-Cloud-Front</link>
            <guid>https://velog.io/@kim_think_rae/Amazon-Cloud-Front</guid>
            <pubDate>Tue, 23 May 2023 08:36:35 GMT</pubDate>
            <description><![CDATA[<h1 id="amazon-cloud-front-란">Amazon Cloud Front 란?</h1>
<p>Amazon에서 제공하는 CDN Service이다.</p>
<p>이미지나, 동영상을 캐싱하여 엣지 로케이션에 두고 사용자에게 제공하여 먼 거리에 있는 사용자라더라도
빠르게 데이터를 전송할 수 있는것이다.</p>
<h2 id="동작방식">동작방식</h2>
<p>요청 받은 컨텐츠가 있다면 바로 전달하고 없다면 Origin 에서 받아온뒤 캐싱하고 제공한다.</p>
<blockquote>
<p>Origin은 EC2나 on-premise server가 될것.</p>
</blockquote>
<h2 id="구성">구성</h2>
<p>Origin : 실제 컨텐츠가 존재한 근원이다. (S3, EC2, ELB, Route, 53, on-premise) 
Distribution : CloudFront의 CDN 구분 단위이다.( 여러 엣지 로케이션으로 구성된 컨텐츠 제공 채널)
TTL : 캐싱된 아이템이 살아 있는 시간이다. TTL 시간 이후 캐싱된 아이템이 삭제된다.
파일 무효화 : TTL 이전에 캐싱된 아이템을 삭제하는것. -&gt; 해당 방식을 사용하면 비용이 발생한다. (한달에 천건은 무료.)
Cache Key : 어떤 기준으로 컨텐츠를 캐싱할 것인지 결정한다. 기본적으로 URL 이 되며 이후 Header와 Cookie, 쿼리스트링등을 사용 가능하다.
(Header 와 Cookie를 사용하여 국가별로 다른 컨텐츠를 보여줄 수 있다.)
정책(policy) :</p>
<h2 id="정적--동적">정적 / 동적</h2>
<p>정적 : server에서 처리할 것이아닌 client가 직접 보여주는 내용들 ( image ,CSS)
동적 : 로그인 / 게시판 처럼 실시간 데이터</p>
<p>정적 / 동적 데이터를 분기처리하여 S3(정적 데이터) 나 EC2에서 가져올 수 있다.</p>
<p>정적 컨텐츠 : 캐싱으로 접근 속도 최적화
동적 컨텐츠 : 네트워크 최적화, 연결 유지, 압축등을 사용한다.</p>
<h2 id="https-지원">HTTPS 지원</h2>
<p>origin에서 https 를 지원하지 않아도 https통신을 지원할 수 있도록 구성이 가능하다.
사용자 정의 인증서(SSL)을 사용하려면 반드시 SSL 이 us-east1 region에 있어야 한다.</p>
<h2 id="어떻게-구성이-가능한가-">어떻게 구성이 가능한가 ?</h2>
<p>client가 AWS Certificate Manager (ACM)을 사용하여 cloud front 에 접근하고 cloud front는 http 통신을 통해 오리진에서 데이터를 가져오는것이다.</p>
<h2 id="지리적-제한">지리적 제한</h2>
<p>지리적 제한이 가능하다. </p>
<h2 id="lambdaedge">Lambda@Edge</h2>
<p>Lambda@Edge를 사용하여  edge location에서의 작업을 수행할 수 있다.</p>
<p>Lambda@Edge를 사용할 수 있는 부분은 4단계로 나뉜다.
    1.client 가 clount front에 요청을 보내기 전에.
    2.cloud front가 origin에 요청을 보내기 전에.
    3.origin이 cloudfront에 응답을 보내기 전에.
    4.cloudfront가 client에게 응답을 보내기 전에.</p>
<h2 id="cloudfront-function">CloudFront Function</h2>
<p>Lambda@eage 의 1/6 비용으로 js 실행.
사용 사례 : 캐싱, 헤더 조작.</p>
<h2 id="signed-url--cookie">Signed URL / Cookie</h2>
<h3 id="url">URL</h3>
<p>URL에는 시작시간, 종료시간, IP, 파일명, URL의 유효기간 등의 정보를 담을 수 있다.
이 URL의 접근 이외의 접근을 먹고 허용된 유저에게 URL을 전달하여 컨텐츠 제공을 제어 가능하다.</p>
<h3 id="cookie">Cookie</h3>
<p>여러 제약 사항 설정 가능하고 다수의 파일 및 스트리밍 접근 허용이 가능하다.</p>
<h2 id="origin-access-identity">Origin Access Identity</h2>
<p>S3의 컨텐츠를 CloudFront를 사용해서만 볼 수 있도록 제한하는 방법
CloudFront만 권한을 가지고 나머지는 권한을 가지지 않는다.
S3 Buket Policy로 cloud Front의 접근을 허용해야 한다.</p>
<h2 id="filed-level-encryption">Filed Level Encryption</h2>
<p>Cloud Front와 origin 사이에 통신을 암호화 하는 방법.</p>
<p>해당 포스팅은 <a href="https://www.youtube.com/watch?v=6C9284C-zP4">https://www.youtube.com/watch?v=6C9284C-zP4</a> 에 의존하고 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Controller Exception Handler]]></title>
            <link>https://velog.io/@kim_think_rae/Controller-Exception-Handler</link>
            <guid>https://velog.io/@kim_think_rae/Controller-Exception-Handler</guid>
            <pubDate>Thu, 18 May 2023 10:47:52 GMT</pubDate>
            <description><![CDATA[<h1 id="사용-계기">사용 계기</h1>
<p>service logic은 당연히 service단에 있고 service 단에서 exception 처리와 log를 찍는다.</p>
<p>근데 뭔가 킹받는 부분이 있었다.</p>
<pre><code class="language-java">@GetMapping(&quot;/id&quot;)
    public HashMap&lt;String, Object&gt; findById(Long seqNo){
        response = new HashMap&lt;String, Object&gt;();
        Media m = ms.findById(seqNo);
        if(m != null){
            response.put(&quot;status&quot;, &quot;success&quot;);
            response.put(&quot;data&quot;, m);
        }else{
            response.put(&quot;status&quot;, &quot;fails&quot;);
            response.put(&quot;reason&quot;, &quot;조회된 데이터가 존재하지 않습니다.&quot;);
        }
        return response;
    }</code></pre>
<p>이건 내가 전에 만든 controller method중 일부인데 문제는 바로 
ms.findById에서 무슨 exception을 발생시킬 줄 알고 이렇게 코드를 짜는가 였다.</p>
<p>그래서 생각한 방안은 2가지였다.</p>
<ol>
<li>메소드의 반환값을 error msg 나 success msg를 string 형식으로 반환하기.</li>
</ol>
<p>-&gt; 이건 시도도 안했다.. 뭔가 시원한 방식이 아니였기 때문에.
2. service metghod에서 exception throw 하기</p>
<pre><code class="language-java">@DeleteMapping
    public HashMap&lt;String, Object&gt; remove(@RequestBody LdapUser[] users) {
        response = new HashMap&lt;String, Object&gt;();
        try {
            lss.removeLdapUser(users);
            response.put(&quot;status&quot;, &quot;success&quot;);
        }catch (NameNotFoundException e){
            response.put(&quot;status&quot;, &quot;error&quot;);
            response.put(&quot;reason&quot;, &quot;일치하는 mac 주소와 일치하는 LDAP Data가 존재하지 않습니다.&quot;);
            log.error(e.getMessage());
        }catch (Exception e){
            response.put(&quot;status&quot;, &quot;error&quot;);
            response.put(&quot;reason&quot;, &quot;exception 발생&quot;);
            log.error(e.getMessage());
        }

        return response;
    }</code></pre>
<p>service 단에서 발생한 예외를 catch 하는 로직인데  </p>
<h1 id="짜-증-난-다">짜 증 난 다</h1>
<p>뭔가 더럽다. 그리고 service에서 log 를 찍어야 하는데 이러면 controller에서 찍지 않는가 ? </p>
<p>그래서 찾아낸것이 바로 ExceptionHandler 이걸 사용하면 훨씬 코드를 간결하고 내가 생각해낸 방식으로 진행할 수 있을거라는 생각이 들었다.</p>
<pre><code class="language-java">@DeleteMapping
    public ResponseEntity&lt;String&gt; remove(@RequestBody LdapUser[] users) {
        lss.removeLdapUser(users);  
        return ResponseEntity.status(HttpStatus.OK).body(&quot;삭제 성공&quot;);
    }

    @ExceptionHandler(NameNotFoundException.class)
    public ResponseEntity&lt;String&gt; handleNameNotFoundException(NameNotFoundException e){
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(&quot;일치하는 mac 주소와 일치하는 LDAP Data가 존재하지 않습니다.&quot;);
    }</code></pre>
<p>그저 &#39;빛&#39;</p>
<p>너무나도 깔끔해진 나의 코드에 눈이그만 멀어버렸다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 설치 및 사용]]></title>
            <link>https://velog.io/@kim_think_rae/Docker-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@kim_think_rae/Docker-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Wed, 10 May 2023 02:30:56 GMT</pubDate>
            <description><![CDATA[<h1 id="docker-란-">Docker 란 ?</h1>
<p>개인 pc로 개발한 프로그램이 server에 올라갔을 때 문제가 생길 경우가 많다. 이를 대비하여 가상의 개발환경을 생성하고 그 환경에서 개발할 수 있도록 할때 Docker를 사용한다고 한다. </p>
<blockquote>
<p>가상의 개발환경을 container 라고 한다.</p>
</blockquote>
<h1 id="설치">설치</h1>
<p>참고로 필자는 macOS 사용중이다.</p>
<p>docker 설치 url : <a href="https://docs.docker.com/desktop/install/mac-install/">https://docs.docker.com/desktop/install/mac-install/</a></p>
<p>설치 한뒤 실행하고 우측 상단에 톱니바퀴를 클릭하고 Resource 창을 확인한다.
<img src="https://velog.velcdn.com/images/kim_think_rae/post/4b3ccd1b-2788-4274-a068-238589c817a2/image.png" alt="">
앞으로 내가 사용할 환경에 할당할 사양들을 적는다.</p>
<h1 id="ubuntu-install">Ubuntu Install</h1>
<p>Docker hub에서 image 를 가져와 설치를 진행한다.
terminal 을 켜고 다음 명령어를 입력한다.</p>
<pre><code class="language-bash">docker run -it --name my-ubuntu ubuntu /bin/bash</code></pre>
<blockquote>
<p>--it 은 인터랙티브 모드로 실행한다는 의미이며 name 옵션은 컨테이너 이름을 지정하는것 이다.</p>
</blockquote>
<p>이렇게 설치된 os에는 정말 아무것도 설치되어 있지않다. (vi든 vim이던) 
따라서 하나하나 차근차근 설치해주어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MSA 와 기존 방식의 차이]]></title>
            <link>https://velog.io/@kim_think_rae/MSA-%EC%99%80-%EA%B8%B0%EC%A1%B4-%EB%B0%A9%EC%8B%9DMonolishArchitecture-SOP-%EC%97%90-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@kim_think_rae/MSA-%EC%99%80-%EA%B8%B0%EC%A1%B4-%EB%B0%A9%EC%8B%9DMonolishArchitecture-SOP-%EC%97%90-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Wed, 03 May 2023 08:03:42 GMT</pubDate>
            <description><![CDATA[<p>이번에 진행할 프로젝트에서 MSA 를 사용하여 개발하기로했다. 
처음에는 MSA로 어떻게 개발하라는거지 ? 싶었다.</p>
<h1 id="msa-란">MSA 란?</h1>
<p>MSA(MicroServiceArchitecture)는 MonolishArchitecture방식에서 Service 들을 독립적으로 모듈화 하여 개발하고 서로 RESTful 통해 통신하는 구조를 뜻 한다. </p>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/462721eb-97b4-4f80-8bcf-22f7e2524816/image.png" alt=""></p>
<p>Monoish Architecture는 하나의 application으로 모든 service를 커버하는 형태이다.
즉, 나는 여태 Monolithic Architecture로 개발을 했던거였고 이를 Service 단위로 쪼개라는 의미였다.</p>
<p>그런데 Service 단위로 모듈화한 방식은 MSA 뿐만 아니라 SOA도 있다. 두가지 차이점을 보자.</p>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/830aeb5e-4a38-4dc6-86b1-e28bc66759a7/image.png" alt=""></p>
<p>SOA는 Service 들 사이의 커뮤니케이션을 SOAP를 통해 한다. </p>
<blockquote>
<p>SOAP는 Simple Object Access Protocol의 약어로써 HTTP, HTTPS, SMTP통해 XML기반의 메시지를 통신 하는프로토콜 이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/e9a2a9b0-ee0a-479a-9297-13c055c2fe85/image.png" alt="">
반면에 MSA 는 Service들간에 통신을 RESTful API를 통해 이루어진다.</p>
<h1 id="왜-msa-쓰나요-">왜 MSA 쓰나요 ?</h1>
<p>기존 Monoish Architecture 환경에서 개발된 쇼핑몰에 구매작업을 수정/기능추가를 한다면 어떨까? 
하나의 서비스를 수정하기 위해서는 다른 서비스의 연관성을 고려해야한다는 불편함이 있다.
따라서 다른 서비스들간에 결합도를 느슨하게 한다면 이런 수정/기능추가 작업이 매우 편리해질것이다.</p>
<p>갑작스럽게 트래픽이 많아진다면 어떨까 ? 
트래픽이 많아져 하나의 Server(Service)에 과부하가 걸려 Vertical Scale작업을 진행하려고 한다면 매우 큰 비용지출이 필요하다. 하지만 Service 별로 관리하게 된다면 특정 Service만 늘려주면 되는것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Amazone DynamoDB ]]></title>
            <link>https://velog.io/@kim_think_rae/Amazone-DynamoDB</link>
            <guid>https://velog.io/@kim_think_rae/Amazone-DynamoDB</guid>
            <pubDate>Tue, 02 May 2023 08:51:48 GMT</pubDate>
            <description><![CDATA[<h1 id="dynamodb란-">DynamoDB란 ?</h1>
<p>DynamoDB란 NoSQL DB로써 지연 시간이 짧고 확장성이 강한 DataBase이다. 또한, 서버나 클러스터를 따로 관리할 필요가 없다고한다.</p>
<h2 id="dynamodb-개념">DynamoDB 개념</h2>
<pre><code>1. Tabel 
2. Item
    - DynamoDB의 단일 데이터 레코드이며, 관계형 데이터베이스의 row와 비슷하다. 
3. Attribute 
    - item의 단일 데이터 요소, 즉 관계형 데이터베이스의 column과 비슷하다.
4. primary key
    - primary key는 DynamoDB 테이블의 단일 Item에 대한 고유 식별자이다. ( 단일 key와 복수 key 개념이 존재한다.)
5. PartiQL
    - SQL 구문을 사용하여 DynamoDB데이터 작업을 코드화할 수 있는 SQL 호환 쿼리이다.</code></pre><h2 id="dynamodb-생성하기-이전에-준비사항">DynamoDB 생성하기. 이전에 준비사항</h2>
<h3 id="aws-cli-install">AWS CLI Install</h3>
<pre><code class="language-bash">curl &quot;https://awscli.amazonaws.com/AWSCLIV2.pkg&quot; -o &quot;AWSCLIV2.pkg&quot;
sudo installer -pkg AWSCLIV2.pkg -target /</code></pre>
<p>설치를 완료했다면 AWS IAM 계정을 생성하여 
Access Key와 Secret Access Key를 받은 뒤 </p>
<pre><code class="language-bash">aws configure</code></pre>
<p>명령어를 consolse에 입력한 뒤 위에서 받은 key를 넣어주고 
region과 ouput format을 지정한다(region : us-east-1, format : json)</p>
<h3 id="python-boto3-installpythod-code로-table-생성을-위함">Python boto3 install(pythod code로 Table 생성을 위함.)</h3>
<pre><code class="language-bash">pip install boto3==1.6.19</code></pre>
<p>해당 작업을 완료한 뒤 </p>
<pre><code class="language-python">import boto3

# boto3 is the AWS SDK library for Python.
# We can use the low-level client to make API calls to DynamoDB.
client = boto3.client(&#39;dynamodb&#39;, region_name=&#39;us-east-1&#39;)

try:
    resp = client.create_table(
    TableName=&quot;Books&quot;,
    # Declare your Primary Key in the KeySchema argument
    KeySchema=[
    {
    &quot;AttributeName&quot;: &quot;Author&quot;,
    &quot;KeyType&quot;: &quot;HASH&quot;
    },
    {
    &quot;AttributeName&quot;: &quot;Title&quot;,
    &quot;KeyType&quot;: &quot;RANGE&quot;
    }
    ],
    # Any attributes used in KeySchema or Indexes must be declared in AttributeDefinitions
    AttributeDefinitions=[
    {
    &quot;AttributeName&quot;: &quot;Author&quot;,
    &quot;AttributeType&quot;: &quot;S&quot;
    },
    {
    &quot;AttributeName&quot;: &quot;Title&quot;,
    &quot;AttributeType&quot;: &quot;S&quot;
    }
    ],
    # ProvisionedThroughput controls the amount of data you can read or write to DynamoDB per second.
    # You can control read and write capacity independently.
    ProvisionedThroughput={
    &quot;ReadCapacityUnits&quot;: 1,
    &quot;WriteCapacityUnits&quot;: 1
    }
    )
    print(&quot;Table created successfully!&quot;)
except Exception as e:
    print(&quot;Error creating table:&quot;)
    print(e)</code></pre>
<p>를 실행해주면 성공적으로 table이 생성된것을 확인할 수 있다.</p>
<p>이후 json file을 하나 생성해준다.</p>
<pre><code class="language-json">[
    {
        &quot;Statement&quot;: &quot;INSERT INTO Books value {&#39;Author&#39;: &#39;Antje Barth&#39;, &#39;Title&#39;: &#39;Data Science on AWS&#39;,&#39;Category&#39;: &#39;Technology&#39;, &#39;Formats&#39;: { &#39;Hardcover&#39;: &#39;J4SUKVGU&#39;, &#39;Paperback&#39;: &#39;D7YF4FCX&#39; } }&quot;
    },
    {
        &quot;Statement&quot;: &quot;INSERT INTO Books value {&#39;Author&#39;: &#39;Julien Simon&#39;, &#39;Title&#39;: &#39;Learn Amazon SageMaker&#39;,&#39;Category&#39;: &#39;Technology&#39;, &#39;Formats&#39;: { &#39;Hardcover&#39;: &#39;Q7QWE3U2&#39;,&#39;Paperback&#39;: &#39;ZVZAYY4F&#39;, &#39;Audiobook&#39;: &#39;DJ9KS9NM&#39; } }&quot;
    },
    {
        &quot;Statement&quot;: &quot;INSERT INTO Books value {&#39;Author&#39;: &#39;James Patterson&#39;, &#39;Title&#39;: &#39;Along Came a Spider&#39;,&#39;Category&#39;: &#39;Suspense&#39;, &#39;Formats&#39;: { &#39;Hardcover&#39;: &#39;C9NR6RJ7&#39;,&#39;Paperback&#39;: &#39;37JVGDZG&#39;, &#39;Audiobook&#39;: &#39;6348WX3U&#39; } }&quot;
    },
    {
        &quot;Statement&quot;: &quot;INSERT INTO Books value {&#39;Author&#39;: &#39;Dr. Seuss&#39;, &#39;Title&#39;: &#39;Green Eggs and Ham&#39;,&#39;Category&#39;: &#39;Children&#39;, &#39;Formats&#39;: { &#39;Hardcover&#39;: &#39;GVJZQ7JK&#39;,&#39;Paperback&#39;: &#39;A4TFUR98&#39;, &#39;Audiobook&#39;: &#39;XWMGHW96&#39; } }&quot;
    },
    {
        &quot;Statement&quot;: &quot;INSERT INTO Books value {&#39;Author&#39;: &#39;William Shakespeare&#39;, &#39;Title&#39;: &#39;Hamlet&#39;, &#39;Category&#39;: &#39;Drama&#39;, &#39;Formats&#39;: { &#39;Hardcover&#39;: &#39;GVJZQ7JK&#39;,&#39;Paperback&#39;: &#39;A4TFUR98&#39;, &#39;Audiobook&#39;: &#39;XWMGHW96&#39; } }&quot;
    }
]</code></pre>
<p>해당 파일이 생성완료되면</p>
<pre><code class="language-bash">aws dynamodb batch-execute-statement --statements file://partiqlbatch.json</code></pre>
<p>명령어를 통해 json data를 db에 insert한다.</p>
<p>이후 python code </p>
<pre><code class="language-python">import boto3

dynamodb = boto3.client(&#39;dynamodb&#39;, region_name=&#39;us-east-1&#39;)

resp = dynamodb.execute_statement(
    Statement=&quot;SELECT * FROM Books WHERE Author = &#39;Antje Barth&#39; AND Title = &#39;Data Science on AWS&#39;&quot;
)

print(resp[&#39;Items&#39;])</code></pre>
<p>을 실행하면 조회해오는것을 알 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GenieACS Server 정리]]></title>
            <link>https://velog.io/@kim_think_rae/GenieACS-Server-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@kim_think_rae/GenieACS-Server-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 26 Apr 2023 04:52:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/42e7d596-1316-4a03-91be-b88e6c5a6efd/image.png" alt="">
TR-069는 ACS(Auto Configuration Server)와 CPE(Customer Premises Equipment) 장치(예: 모뎀, 라우터 및 셋톱 박스) 간의 통신을 위해 설계된 원격 관리 프로토콜입니다.
TR-069의 주요 목표는 인터넷 서비스 공급자(ISP)와 네트워크 관리자가 CPE 장치를 원격으로 관리, 구성 및 문제를 해결할 수 있도록 하는 것입니다. 
이는 장치 배포를 단순화하고 현장 기술 지원의 필요성을 줄이며 일관된 사용자 경험을 보장하는 데 도움이 됩니다.</p>
<p>TR-069의 주요 기능은 다음과 같습니다.
장치 프로비저닝: TR-069는 CPE 장치가 네트워크에 처음 연결할 때 자동 구성을 허용합니다. 이렇게 하면 장치 설정이 간소화되고 수동 구성 작업이 줄어듭니다.</p>
<p>원격 관리: TR-069는 펌웨어 업데이트, 구성 변경, 진단 및 장치 상태 모니터링을 포함하여 CPE 장치의 원격 관리를 가능하게 합니다. 이를 통해 네트워크 관리자는 
   물리적으로 액세스하지 않고도 장치를 유지 관리하고 최적화할 수 있습니다.</p>
<p>3.성능 모니터링: 이 프로토콜은 실시간 성능 모니터링 및 통계 수집을 지원하여 최종 사용자에게 영향을 미치기 전에 문제를 감지하고 해결하도록 돕습니다.</p>
<p>4.이벤트 기반 통신: TR-069는 CPE 장치가 재부팅 또는 구성 변경과 같은 특정 이벤트에 대한 응답으로 ACS 서버와의 통신을 시작하는 이벤트 기반 모델을 사용합니다. </p>
<p>5.이는 불필요한 통신 오버헤드를 줄이고 네트워크 효율성을 향상시킵니다.</p>
<p>6.보안: TR-069는 SSL/TLS 암호화 및 인증을 포함한 여러 보안 메커니즘을 통합하여 ACS와 CPE 장치 간의 통신 기밀성과 무결성을 보장합니다.</p>
<p>TR-069는 광대역 네트워크에서 원격 CPE 장치 관리를 위한 사실상의 표준이 되었습니다. 많은 ISP와 네트워크 관리자는 장치를 원격으로 관리하기 위해 TR-069에 의존하고 
있으며 Genie ACS와 같은 상용 및 오픈 소스의 다양한 TR-069 호환 ACS 서버를 사용할 수 있습니다.</p>
<h2 id="api">API</h2>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/67c25c54-02fc-46f2-8ed4-14faec3f5c63/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/82de030a-b8a0-4e10-8ade-d9d2d0e1ea48/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/5ca46b97-b268-4d43-b410-f7490aee9e12/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/1f95467c-e51d-4398-8dcc-8d3c3fc3ce83/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[차세대 기업보안 세미나(4/19)]]></title>
            <link>https://velog.io/@kim_think_rae/%EC%B0%A8%EC%84%B8%EB%8C%80-%EA%B8%B0%EC%97%85%EB%B3%B4%EC%95%88-%EC%84%B8%EB%AF%B8%EB%82%98419</link>
            <guid>https://velog.io/@kim_think_rae/%EC%B0%A8%EC%84%B8%EB%8C%80-%EA%B8%B0%EC%97%85%EB%B3%B4%EC%95%88-%EC%84%B8%EB%AF%B8%EB%82%98419</guid>
            <pubDate>Wed, 19 Apr 2023 09:57:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/707772f4-b6f2-4e2f-9fe2-04a634dd203a/image.jpg" alt=""></p>
<p>4월 19일 양재에서 진행한 차세대 기업보안 세미나를 다녀오고 여러 가지 알게 된 사실들을 정리해보고자 한다.</p>
<p>기존 on premise 환경에서 cloud 환경으로 많이 넘어가고 있다고 한다.</p>
<blockquote>
<p>on premise : 물리 서버로 제공되는 환경.
private cloud : 단일 조직 전용 클라우드 컴퓨팅 환경
public cloud : 공개된 인터넷 환경에서 제공되는 컴퓨팅 환경
hybrid cloud : 위 두 개의 방식을 섞어 장단점을 살리고 줄여낸 환경</p>
</blockquote>
<p>이전에 출근을 기본으로 하여 회사에서 업무를 진행했으나, 요즘은 원격근무가 많아지는 추세이니 보안 위협이 생겼다고 한다.</p>
<p>즉, 사설망으로 된 네트워크를 이용하여 외부 공격에 대해 조금 강했으나 요즘은 그게 아니기에 외부 공격에 위협이 있다는 이야기이다.</p>
<p>그중에 NAS, SMB이 특히 취약하다고 한다.</p>
<blockquote>
<p>NAS: 네트워크 결합 스토리지로써 LAN으로 연결하는 외장 하드디스크이다.
SMB: 네트워크 파일 공유 프로토콜.</p>
</blockquote>
<p>본격적인 설명에 앞서 Zero Trust Architecture에 대해 가볍게 짚고 넘어가 보자.</p>
<p>ZeroTrustArchitecture란 기업 환경에 각 요소의 워크플로나 접근 정책 등에 제로트러스트(컨셉)을 적용하기 위한 사이버 보안계획이라고 한다.
즉, 앞으로 설명할 것은 각 기업체의 솔루션은 어떤 보안계획을 가졌는지에 대해 이야기하는 거라고 보면 될 것 같다.</p>
<p>기업체별로 여러 가지 보안계획들을 수립했고 많이들 채택한 방법이 인증의 방식을 다양화시킨 것 같다.
어떤 발표자분의 이야기가 기억에 남는다.</p>
<p>유럽에서는 카드발급을 하게 되면 카드가 우체통으로 배송이 온다고 하는데 이때 배송 온 카드를 범죄자가 탈취하여 마음대로 쓸 때가 있다고 한다. 이런 사기가 발생하는 이유는 이러하다. PIN 번호 역시 우체통으로 배송이 온다고 하는데 이때 배송 온 카드를 범죄자가 탈취하여 마음대로 쓸 때가 있다고 한다. 이런 사기가 발생하는 이유는 이러하다. PIN 번호 역시 우체통으로 오기 때문에 중간에 탈취하면 방법이 없다.</p>
<p>이렇게 숨겨야 할 정보가 쉽게 노출되면 피해가 발생할 수 있다. 그런데 요즘 한국에도 숨겨야 할 정보가 한곳에 있는 경우가 있는데 핸드폰에 모든 보안 정보가 있다.
즉, 공격자는 핸드폰만 공략하면 되는 것이다.</p>
<p>이런 일들을 방지하기 위해서 개발되고 있는 솔루션 중 하나는 핸드폰으로 카드 결제할 시에 카드를 핸드폰에 tag 하여 인증하는 방식이다. 이렇게 되면 핸드폰을 공략해낸다 해도 금융 피해를 줄일 수 있다는 것이다.</p>
<p>이래서 요즘 인증방식이 여러 가지로 늘어나는구나 싶었다. 여러 기업의 제로 트러스트 아키텍처에 포함되어있던 내용들 중 하나가 인증방식에 다양화였다.</p>
<p>PLC로 구축되어있는 환경에 공장들도 위협에 노출되어있다고 한다. PLC로 만들어진 프로그램의 비밀번호는 1개에다가 접속했을 때 누가 접속했는지도 모른다고 한다</p>
<blockquote>
<p>그냥 들어도 위험해 보인다.</p>
</blockquote>
<p>이것에 대한 솔루션 역시 pin 번호를 실시간으로 발급하여 접근하는 식으로 해결했다고 한다.</p>
<p>조금 간략하게 내용들을 생각해보면 인증을 다양화하고 권한을 세부적으로 나누거나 환경을 격리하며 문제를 해결해 나아가고 있는 듯 하다.</p>
<blockquote>
<p>어렵다... 그래도 좋은 세미나였따....</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] WAS 에서 다른 Server의 접근하여 명령어 사용하기]]></title>
            <link>https://velog.io/@kim_think_rae/JAVA-WAS-%EC%97%90%EC%84%9C-%EB%8B%A4%EB%A5%B8-Server%EC%9D%98-%EC%A0%91%EA%B7%BC%ED%95%98%EC%97%AC-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kim_think_rae/JAVA-WAS-%EC%97%90%EC%84%9C-%EB%8B%A4%EB%A5%B8-Server%EC%9D%98-%EC%A0%91%EA%B7%BC%ED%95%98%EC%97%AC-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 18 Apr 2023 04:23:47 GMT</pubDate>
            <description><![CDATA[<h2 id="1-ssh-keygen-생성하기">1. ssh-keygen 생성하기</h2>
<p>ssh-keygen을 사용하면 다른 Server의 비밀번호 없이 접근할 수 있다.</p>
<h3 id="키젠-생성">키젠 생성</h3>
<pre><code class="language-bash">$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/kimmyeongrae/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:</code></pre>
<blockquote>
<p>경로의 default는 $HOME/.ssh/id_rsa이고 password, password확인값을 입력해주면 성공적으로 생성된것을 볼 수 있다.(이때 password를 안쓰고싶다면 그냥 엔터치면 된다.)</p>
</blockquote>
<h3 id="key-등록">key 등록</h3>
<pre><code>ssh-copy-id user@target-server-ip</code></pre><h3 id="등록된-key-확인targetserver에서">등록된 key 확인(targetServer에서)</h3>
<pre><code class="language-bash">cat ~/.ssh/authorized_keys</code></pre>
<p>이후 ssh 접속이 password없이 가능하다.</p>
<h2 id="java-test-code">JAVA Test Code</h2>
<pre><code class="language-java">@GetMapping(&quot;/test&quot;)
    public void test(){
        try {
            String user = &quot;targetServerUserName&quot;;
            String ldapDhcpServerIp = &quot;targetServerIP&quot;;
            String[] cmd = {&quot;ssh&quot;, user + &quot;@&quot; + ldapDhcpServerIp, &quot;systemctl&quot;, &quot;status&quot;, &quot;slapd&quot;, &quot;&amp;&amp;&quot;, &quot;systemctl&quot;, &quot;status&quot;, &quot;isc-dhcp-server&quot;};

            ProcessBuilder processBuilder = new ProcessBuilder(cmd);
            Process process = processBuilder.start();

            BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

            String line;
            while ((line = stdoutReader.readLine()) != null) {
                System.out.println(line);
            }          
            process.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }</code></pre>
<p>이렇게하면 target서버에 원하는 명령어를 던지고 받아낼 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JwtToken login 시 sha 256 적용 ]]></title>
            <link>https://velog.io/@kim_think_rae/JwtToken-login-%EC%8B%9C-sha-256-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@kim_think_rae/JwtToken-login-%EC%8B%9C-sha-256-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Wed, 05 Apr 2023 00:18:34 GMT</pubDate>
            <description><![CDATA[<p>기존 Login시에 sha256으로 인코딩한 뒤 db에 암호화 되어있는 pwd와 매치시켰던 로직을 jwtToken 로직에 적용하고자 한다.</p>
<pre><code class="language-java">@Transactional
    public TokenInfo loginMember(String userId, String password)  {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance(&quot;SHA-256&quot;);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        sha.update(password.getBytes());
        byte[] digest = sha.digest();
        password = Base64.getEncoder().encodeToString(digest);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        return jwtTokenProvider.generateToken(authentication);
    }</code></pre>
<p>Service에 있는 loginMember method이고 전달받은 password를 SHA-256알고리즘으로 인코딩 하고 password를 넘겨주면 된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ngnix 설치 및 배포(react) for rocky]]></title>
            <link>https://velog.io/@kim_think_rae/ngnix-%EC%84%A4%EC%B9%98-for-rocky</link>
            <guid>https://velog.io/@kim_think_rae/ngnix-%EC%84%A4%EC%B9%98-for-rocky</guid>
            <pubDate>Tue, 04 Apr 2023 06:46:00 GMT</pubDate>
            <description><![CDATA[<h1 id="ngnix-설치">ngnix 설치</h1>
<h2 id="ngnix-란-">ngnix 란 ?</h2>
<p>HTTP, Reverse proxy 를 지원하는 웹 서버 프로그램. 러시아에서 개발되었고 미국에서 운영중이다.</p>
<p>ngnix 는 기본적으로 rocky linux에 설치되어있는것 같다.</p>
<p>하지만 최신 버전을 설치해주자.</p>
<pre><code class="language-bash">dnf update 
dnf module list nginx ( nginx version 확인)
dnf -y install nginxㅎ</code></pre>
<p>이후 nginx 활성화 후 시작</p>
<pre><code class="language-bash">systemctl enable nginx
systemctl start nginx</code></pre>
<p>이렇게 하면 서버가 실행될 때 마다 nginx 가 실행된다.</p>
<p>nginx의 구성을 변경 후 적용할 때 서버연결을 끊지않고 다음 명령어를 사용하면 적용할 수 있다.</p>
<pre><code class="language-bash">systemctl reload nginx</code></pre>
<p>ngnix가 구동되고 있는 server ip로 접속해보면 
<img src="https://velog.velcdn.com/images/kim_think_rae/post/eb23f051-0343-48b6-b904-4440655d8f32/image.png" alt="">
기본 페이지를 볼 수 있다.</p>
<hr/>

<h3 id="콘텐츠">콘텐츠</h3>
<ul>
<li>/usr/share/nginx/html: 기본적으로 이전에 본 기본 Nginx 페이지로만 구성된 실제 웹 콘텐츠는 /usr/share에서 제공됩니다. /nginx/html 디렉토리. 이것은 Nginx 구성 파일을 변경하여 변경할 수 있습니다.</li>
</ul>
 <hr>


<h3 id="서버구성">서버구성</h3>
<ul>
<li>/etc/nginx: Nginx 구성 파일위치.<ul>
<li>/etc/nginx/nginx.conf: 기본 Nginx 구성 파일. </li>
<li>/etc/nginx/conf.d/: 이 디렉토리에는 Nginx 내에서 호스팅되는 웹사이트를 정의할 수 있는 서버 블록 구성 파일이 포함되어 있습니다. 일반적인 접근 방식은 각 웹사이트를 your_domain.conf와 같이 웹사이트의 도메인 이름을 따라 명명된 별도의 파일에 포함하는 것입니다.
서버 로그</li>
<li>/var/log/nginx/access.log: 웹 서버에 대한 모든 요청은 Nginx가 다르게 구성되지 않는 한 이 로그 파일에 기록.</li>
</ul>
</li>
</ul>
<ul>
<li>/var/log/nginx/error.log: 모든 Nginx 오류가 이 로그에 기록.</li>
</ul>
<hr>

<p>npm run build로 react project를 build한다.</p>
<p>이후 server에 
nginx.conf 파일을 열어 설정을 수정한다.</p>
<pre><code class="language-bash">vim /etc/nginx/nginx.conf


 39         listen       3000 default_server;
 40         listen       [::]:3000 default_server;
 41         server_name  _;
 42         #root         /usr/share/nginx/html/build;
 43         root          /home/rocky/frontServer/build;</code></pre>
<p>여기에 있는 설정값을 내 입맛대로 수정한다.
root 는 file을 의미하고 listen은 port 번호를 이야기한다.</p>
<p>local에 있는 file을 scp로 server에 upload 한다.</p>
<pre><code class="language-bash">sudo scp -i key.pem gazi1/build.zip rocky@ip:/home/rocky/frontServer</code></pre>
<p>파일을 unzip 한뒤 재시작하면 성공적으로 배포가된것을 확인할 수 있다.</p>
<pre><code class="language-bash">systemctl restart nginx</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 트랜잭션 관리]]></title>
            <link>https://velog.io/@kim_think_rae/JPA-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@kim_think_rae/JPA-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 04 Apr 2023 01:36:38 GMT</pubDate>
            <description><![CDATA[<p>my-batis를 사용할때는 transaction을 관리하며 update나 delete query가 예상 외의 작업을 진행했을때 rollback을 진행했던 기억이 있다.
JPA ORM을 사용시에는 어떻게 해야할까 ?</p>
<blockquote>
<p>트랜잭션(transaction) : 논리적 작업단위.</p>
</blockquote>
<h3 id="transaction">@Transaction</h3>
<p>Transaction 을 자동으로 관리해준다. 예를 들어,</p>
<pre><code class="language-java">@Transaction
public void test(){
    repo.insert();
     repo.update();
}</code></pre>
<p>test 함수가 실행되기전 트랜잭션이 시작되고 성공적으로 함수가 종료되면 commit한다.<br>insert query가 성공적으로 이루어지고 update에서 exception 이 발생하면 rollback하여 원자성을 보장한다.</p>
<hr>

<h3 id="transactionreadonly--true">@Transaction(readOnly = true)</h3>
<p>readOnly로 설정했다면 해당 Transaction 안에서는 insert, delete, update query가 불가능하다.</p>
<hr>

<h3 id="transactionisolation--isolationdefault">@Transaction(isolation = Isolation.DEFAULT)</h3>
<p>Transaction의 격리수준을 설정할 수 있다. 
ex) </p>
<ul>
<li>DEFAULT</li>
<li>READ_UNCOMMITTED (level 0)</li>
<li>READ_COMMITTED (level 1)</li>
<li>REPEATABLE_READ (level 2)</li>
<li>SERIALIZABLE (level 3)</li>
</ul>
<hr/>

<h3 id="transcationrollbackforexceptionclass-transcationnorollbackforexceptionclass">@Transcation(rollbackFor=Exception.class) @Transcation(noRollbackFor=Exception.class)</h3>
<p>runtime Exception 발생시 rollback한다. 혹은, 특정 예외 발생 시 rollback을 하지 않도록 할 수 있다.</p>
<hr/>

<h3 id="transactiontimeout10">@Transaction(timeout=10)</h3>
<p>설정한 시간안에 transaction 이 종료되지 않았다면 rollback한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT token 발급하기]]></title>
            <link>https://velog.io/@kim_think_rae/JWT-token-%EB%B0%9C%EA%B8%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kim_think_rae/JWT-token-%EB%B0%9C%EA%B8%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 16:30:24 GMT</pubDate>
            <description><![CDATA[<h2 id="jwt-token-이란-">JWT token 이란 ?</h2>
<p>Json Web Token은 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준으로이다. </p>
<h3 id="구성">구성</h3>
<ul>
<li>헤더 : </li>
<li>페이로드 : 페이로드는 몇몇 클레임(claim) 표명(assert)을 처리하는 JSON을 보관하고 있다.</li>
<li>서명 : Base64로 복화한 key를 저장하고 있다.</li>
</ul>
<h2 id="로직">로직</h2>
<p>로그인 이후에 성공한다면 해당 계정정보가 담긴 JWT Token을 발급받고 </p>
<p>jwt의 인증 유효사항을 ServletFilter를 이용해 관리할 수 있을거라고 생각했다.</p>
<blockquote>
<h3 id="servletfilter란-">servletFilter란 ?</h3>
<p>dispatcher servlet에 도달하기 전에 servletFilter가 위치해 있어, dispatcher servlet에 향하거나 거쳐 돌아온 요청들에 부가작업을 진행할 수 있다.
개념만 보았을때는 interceptor와 비슷하지만 interceptor는 spring container 안에 있고 dispatcher servlet 이후에 위치해 있다.</p>
</blockquote>
<p>먼저 gradle에 의존성부터 추가해준다.</p>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;


implementation &#39;io.jsonwebtoken:jjwt-api:0.11.5&#39;
implementation &#39;io.jsonwebtoken:jjwt-impl:0.11.5&#39;
implementation &#39;io.jsonwebtoken:jjwt-jackson:0.11.5&#39;</code></pre>
<p>주의해야할 것은 starter-security 를 depedencies의 추가하면 자동으로 DefaultAuthenticationEventPublisher가 Bean으로 등록된다. 따라서 인증이 없는 컨트롤러 테스트가 다 무용지물이 돼버리니 참고하자.</p>
<h3 id="jwttokenprovider">JwtTokenProvider</h3>
<p>JWT Token에 access/refresh token을 생성/관리 해주는 class 이다.</p>
<pre><code class="language-java">
@Slf4j
@Component
public class JwtTokenProvider {
    private final Key key;

    private final long accessTokenValidTime = Duration.ofMinutes(30).toMillis();
    private final long refreshTokenValidTime = Duration.ofDays(14).toMillis();


    /**
     * @param secretKey
     * 지정한 secretKey를 base64로 디코딩하여 저장한다.
     * 이 decoding 한 key는 token을 암호화, 복호화할 때 사용된다.
     */
    public JwtTokenProvider(@Value(&quot;${jwt.secret}&quot;) String secretKey) {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }


    /**
     * @param authentication
     *  유저 정보를 받고 accessToken 과 refreshToken을 생성하는 메소드이다.
     *  기본 설정시간은 accessTokenValidTime, refreshTokenValidTime에 설정된시간 으로 적용되고
     *  각 시간은 accessToken은 30분 refreshToken은 14일이다.
     *  그렇게 적용된 시간은 Member객체에 추가하여 반환한다.
     *  각 객체에 서명은 HS256으로 암호화 한다.
     * @return TokenInfo 객체
     */
    public TokenInfo generateToken(Authentication authentication) {
        String authorities = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(&quot;,&quot;));
        long now = (new Date()).getTime();

        Date accessTokenExpiresIn = new Date(now + this.accessTokenValidTime);

        String accessToken = Jwts.builder()
                .setSubject(authentication.getName())
                .claim(&quot;auth&quot;, authorities)
                .setExpiration(accessTokenExpiresIn)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
        String refreshToken = Jwts.builder()
                .setExpiration(new Date(now + this.refreshTokenValidTime))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();

        return TokenInfo.builder()
                .grantType(&quot;Bearer&quot;)
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .build();
    }

    /**
     * @param accessToken
     * JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드이다.
     * accessToken에 claims(사용자 ID, 전자 메일 주소, 역할 또는 권한, 인증 또는 부여 프로세스와 관련된
     * 기타 속성과 같은 정보가 있다.)을 파싱(복호화)하여 권한정보들을 List객체에 답아놓고 해당 List에 있는 값들로
     * 계정 정보를 생성한 뒤 반환한다.
     * @return token
     */
    public Authentication getAuthentication(String accessToken) {
        Claims claims = parseClaims(accessToken);

        if (claims.get(&quot;auth&quot;) == null) {
            throw new RuntimeException(&quot;권한 정보가 없는 토큰입니다.&quot;);
        }

        Collection&lt;? extends GrantedAuthority&gt; authorities =
                Arrays.stream(claims.get(&quot;auth&quot;).toString().split(&quot;,&quot;))
                        .map(SimpleGrantedAuthority::new)
                        .toList();

        // UserDetails 객체를 만들어서 Authentication 리턴
        UserDetails principal = new User(claims.getSubject(), &quot;&quot;, authorities);
        return new UsernamePasswordAuthenticationToken(principal, &quot;&quot;, authorities);
    }


    /**
     * @param token
     * 토큰 정보를 검증하는 메서드
     *
     * @return boolean
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info(&quot;Invalid JWT Token&quot;, e);
        } catch (ExpiredJwtException e) {
            log.info(&quot;Expired JWT Token&quot;, e);
        } catch (UnsupportedJwtException e) {
            log.info(&quot;Unsupported JWT Token&quot;, e);
        } catch (IllegalArgumentException e) {
            log.info(&quot;JWT claims string is empty.&quot;, e);
        }
        return false;
    }

    private Claims parseClaims(String accessToken) {
        try {
            return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
        } catch (ExpiredJwtException e) {
            return e.getClaims();
        }
    }
}
</code></pre>
<h3 id="jwtauthenticationfilter">JwtAuthenticationFilter</h3>
<pre><code class="language-java">@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilter {
    private final JwtTokenProvider jwtTokenProvider;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException, ServletException, IOException {

        // 1. Request Header 에서 JWT 토큰 추출
        String token = resolveToken((HttpServletRequest) request);

        // 2. validateToken 으로 토큰 유효성 검사
        if (token != null &amp;&amp; jwtTokenProvider.validateToken(token)) {
            // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext 에 저장
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }

    // Request Header 에서 토큰 정보 추출
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(&quot;Authorization&quot;);
        if (StringUtils.hasText(bearerToken) &amp;&amp; bearerToken.startsWith(&quot;Bearer&quot;)) {
            return bearerToken.substring(7);
        }
        return null;
    }
}</code></pre>
<p>이후 filter를 적용하기 위한 </p>
<h3 id="securityconfig">SecurityConfig</h3>
<pre><code class="language-java">@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private final JwtTokenProvider jwtTokenProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()
                .requestMatchers(&quot;/member/login&quot;).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}</code></pre>
<ol>
<li>토큰 기반 인증과 같은 안전한 매커니즘을 위해 HTTP기본 인증을 비활성화 한다.</li>
<li>CSRF(Cross-Site-RequestForgery)를 비활성화한다.</li>
<li>session을 사용하지 않겟다는 의미</li>
<li>permitAll은 모든 사용자의 요청을 허용한다는 의미이고 그 외에 요청은 허용하지 않는다는 의미이다.</li>
<li>생성한 필터를 적용한다.</li>
<li>사용할 PasswordEncoder를 빈에 등록한다.</li>
</ol>
<p>사용할 Member entity에 UserDetails를 implements해주고 해당 interface를 상속하기위해 override할 method들을 override해준다.</p>
<pre><code class="language-java">@Table(name = &quot;MEMBER&quot;)
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@EqualsAndHashCode(of=&quot;userId&quot;)
@ToString
@DynamicUpdate
@DynamicInsert
public class MemberEntity implements UserDetails {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;SEQ_NO&quot;)
    private Long seqNo;
    @Column(name = &quot;USER_ID&quot;, unique = true, nullable = false)
    private String userId;
    @Column(name = &quot;USER_PWD&quot;, nullable = false)
    private String userPwd;
    @Column(name = &quot;USER_NAME&quot;, nullable = false)
    private String userName;
    @Column(name = &quot;EMAIL&quot;, nullable = false)
    private String email;
    @Column(name = &quot;PHONE&quot;, nullable = false)
    private String phone;
    @Column(name = &quot;CREATE_DATE&quot;)
    private LocalDateTime createDate;
    @Column(name = &quot;MODIFY_DATE&quot;)
    private LocalDateTime modifyDate;
    @Column(name = &quot;STATUS&quot;, columnDefinition = &quot;varchar(1) default &#39;Y&#39;&quot;, nullable = false)
    @Check(constraints = &quot;(STATUS IN (&#39;Y&#39;, &#39;N&#39;))&quot;)
    private String status;
    @Column(name = &quot;CREATE_ID&quot;, nullable = false)
    private String createName;
    @Column(name = &quot;MODIFY_ID&quot;, nullable = false)
    private String modifyName;
    @Column(name = &quot;DEPARTMENT&quot;)
    private String department;
    @Column(name = &quot;LIFE_DATE&quot;, nullable = false)
    private LocalDateTime lifeDate;

    @Column(name = &quot;AUTHORITY&quot;, nullable = false)
    private String authority;

    @PrePersist
    protected void onCreate(){
        this.modifyDate = LocalDateTime.now();
        this.createDate = LocalDateTime.now();
        this.status = &quot;Y&quot;;
    }
    @PreUpdate
    protected void onUpdate(){
        modifyDate = LocalDateTime.now();
    }
    public Member toDefaultDto(){
        return Member.builder()
                .userId(this.userId)
                .userPwd(this.userPwd)
                .createDate(this.createDate)
                .modifyDate(this.modifyDate)
                .status(this.status)
                .email(this.email)
                .phone(this.phone)
                .createName(this.createName)
                .modifyName(this.modifyName)
                .department(this.department)
                .lifeDate(this.lifeDate)
                .seqNo(this.seqNo)
                .userName(this.userName)
                .authority(this.authority)
                .build();
    }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        ArrayList&lt;SimpleGrantedAuthority&gt;list = new ArrayList&lt;SimpleGrantedAuthority&gt;();
        list.add(new SimpleGrantedAuthority(this.authority));
        return list;
    }

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

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

    @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>
<h3 id="member-service">Member Service</h3>
<pre><code class="language-java">/**
     * @param userId
     * @param password
     * authenticationToken객체를 생성합니다. 이때 생성한 객체는 &quot;인증&quot;을 거친 객체는 아니며 이를 authenticationManagerBuilder에 전달할 때
     * 인증이 진행됩니다. 이떼 CustomUserDetailService에서 만든 loadUserByUsername 메소드가 실행됩니다.
     * @return TokenInfo
     */
    @Transactional
    public TokenInfo loginMember(String userId, String password){
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        return jwtTokenProvider.generateToken(authentication);
    }</code></pre>
<h3 id="customuserdetailservice">CustomUserDetailService</h3>
<p>해당 클래스는 memberRepository로 멤버 id로 정보를 조회해 온다.</p>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MemberEntity entity = memberRepository.findByUserId(username);
        if(entity == null) throw new UsernameNotFoundException(&quot;해당하는 유저를 찾을 수 없습니다.&quot;);
        return createUserDetails(entity);
    }

    private UserDetails createUserDetails(MemberEntity member) {
        return User.builder()
                .username(member.getUsername())
                .password(passwordEncoder.encode(member.getPassword()))
                .roles(member.getAuthority())
                .build();
    }
}</code></pre>
<h3 id="membercontroller">MemberController</h3>
<pre><code class="language-java">@PostMapping(&quot;/login&quot;)
    public HashMap&lt;String, Object&gt; loginMember(@RequestBody @Validated Member m){
        response = new HashMap&lt;String, Object&gt;();
        String userId = m.getUserId();
        String userPwd = m.getUserPwd();
        TokenInfo tokenInfo = ms.loginMember(userId, userPwd);
        if(tokenInfo != null){
            response.put(&quot;status&quot;, &quot;success&quot;);
            response.put(&quot;Member&quot;, tokenInfo);
        }else{
            response.put(&quot;status&quot;, &quot;false&quot;);
        }
        System.out.println(response);
        return response;
    }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot / gradle rocky linux 배포하기.]]></title>
            <link>https://velog.io/@kim_think_rae/Spring-Boot-gradle-rocky-linux-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kim_think_rae/Spring-Boot-gradle-rocky-linux-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 01:05:27 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kim_think_rae/post/497a9d74-05a9-4114-88bf-c01d6146232f/image.png" alt="">
먼저 bootJar 실행하여 jar file을 생성한다.</p>
<pre><code>scp 생성된.jar -i key.pem  계정명@serverIp:전송할 경로</code></pre><p>서버에서 java 설치</p>
<pre><code>sudo dnf install java-17-openjdk-devel</code></pre><p>jar 파일은 내부 톰캣이 있기에 따로 톰캣을 설치하지 않아도 된다. </p>
<p>jar 파일 실행하는 방법</p>
<pre><code>java -jar filename.jar &amp;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[JPA default 값 활용하기 (dnnamic Insert)]]></title>
            <link>https://velog.io/@kim_think_rae/JPA-default-%EA%B0%92-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-dnnamic-Insert</link>
            <guid>https://velog.io/@kim_think_rae/JPA-default-%EA%B0%92-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0-dnnamic-Insert</guid>
            <pubDate>Mon, 20 Mar 2023 05:59:56 GMT</pubDate>
            <description><![CDATA[<p>먼저 문제가된 컬럼 2개를 보자.</p>
<pre><code>@Column(name = &quot;tr_status&quot;, columnDefinition = &quot;varchar(10) default &#39;start&#39;&quot;)
    @Check(constraints = &quot;(tr_status IN (&#39;start&#39;, &#39;done&#39;, &#39;error&#39;))&quot;)
    private String trStatus;
@Column(name = &quot;startTransCoding&quot;, columnDefinition = &quot;datetime default current_timestamp&quot;)
    private LocalDateTime startTransCoding;</code></pre><p>해당 데이터가 null 이면 default 값을 주려고 설정해준 제약 조건들인데 
repo save method를 호출하여 데이터를 삽입할때
해당 컬럼값에 null 이들어가는것이다.</p>
<p>db table에 제약조건은 제대로 걸려있는것을 확인했는데 왜 문제가 발생했을까 ? 
이유는 간단했다.</p>
<pre><code>into
        media (filename, finish_trans_coding, ingest_time, progress, start_trans_coding, tr_status, user_id) 
    values
        (?, ?, ?, ?, ?, ?, ?)</code></pre><h3 id="jap-는-값이-null이던-아니던-다-불러와서-삽입하고-있는것이였다">JAP 는 값이 null이던 아니던 다 불러와서 삽입하고 있는것이였다.</h3>
<p>알아서 null 값은 걸러서 insert 해줄거라고 생각했지만 아니였다.. 
해당 문제는 @DynamicInsert 어노테이션으로 해결했다.</p>
<p>@DynamicInsert 적용 후 jpql</p>
<pre><code>    into
        media (filename, user_id) 
    values
        (?, ?)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[NHN Cloud Nas Mounting ]]></title>
            <link>https://velog.io/@kim_think_rae/NHN-Cloud-Nas-Mounting</link>
            <guid>https://velog.io/@kim_think_rae/NHN-Cloud-Nas-Mounting</guid>
            <pubDate>Fri, 17 Mar 2023 06:20:41 GMT</pubDate>
            <description><![CDATA[<p>먼저 필요한 패키지를 다운한다.</p>
<pre><code>dnf install nfs-utils</code></pre><p>이후 마운트하려는 디렉토리를 생성한다.</p>
<pre><code>mkdir /mnt/nas_mount</code></pre><p>그리고 마운팅
mount -t nfs &lt;nasIP&gt;:/&lt;nas&gt; &lt;Mount&gt;</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringBoot Background Thread 생성 (servlet 이용)]]></title>
            <link>https://velog.io/@kim_think_rae/SpringBoot-Background-Thread-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@kim_think_rae/SpringBoot-Background-Thread-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Tue, 07 Mar 2023 07:52:30 GMT</pubDate>
            <description><![CDATA[<h1 id="event-listener">Event Listener</h1>
<p>어떠한 이벤트가 발생하면 호출되어 처리하는 객체를 말한다.</p>
<p>Listner를 사용하기 위해서는 인터페이스를 구현하는 클래스를 만들어야한다.</p>
<h2 id="listener-인터페이스의-종류">Listener 인터페이스의 종류</h2>
<h3 id="servletcontextlistener">ServletContextListener</h3>
<p>웹 어플리케이션의 시작과 종료시 자동으로 발생되는 이벤트를 수행하기 위한 메소드를 정의한 인터페이스이다.</p>
<h4 id="contextinitializedservletcontextevent-sce--void">contextInitialized(ServletContextEvent sce) : void</h4>
<p>웹 컨테이너가 처음 구동될 때 실행되는 메소드이다.</p>
<h4 id="contextdestoryedservletcontextevent-sce">contextDestoryed(ServletContextEvent sce)</h4>
<p>웹 컨테이너가 종료될 때 실행되는 메소드 </p>
<h3 id="servletcontextattributelistener">ServletContextAttributeListener</h3>
<p>컨테이너에 저장된 속성 값들의 변화가 있을 때 수행하기 위한 메소드를 정의한 인터페이스이다. </p>
<h3 id="httpsessionlistener">HttpSessionListener</h3>
<p>HTTP 세션이 활성화 되거나 비활성화 되려할 때 혹은 속성 값들이 추가, 삭제, 변경될 경우 수행하기 위한 인터페이스 이다.</p>
<h3 id="httpsessionattributelistener">HttpSessionAttributeListener</h3>
<p>HTTP 세션에 대한 속성 값이 변경되었을 경우 수행하기 위한 인터페이스</p>
<h3 id="httpsessionactivationlistener">HttpSessionActivationListener</h3>
<p>세션에 대한 내용이 새로 생성되어 세션이 활성화 되었을 때 발생하는 이벤트를 수행하기 위한 인터페이스</p>
<h3 id="httpsessionbindinglistener">HttpSessionBindingListener</h3>
<p>클라이언트의 세션 정보에 대한 바인딩이 이루어졌을 경우 감지되는 이벤트를 수행하기 위한 인터페이스</p>
<p>내가 개발하려는 것은 spring server가 실행될 때 fileWatch 기능이 있는 Thread 를 생성하여 백그라운드에 계속 남겨두는 것 이다.</p>
<p>즉, web application 실행 event를 받는 listner안에 thread 내용을 구현하면될 것 이다.</p>
<p>따라서 ServletContextListener interface를 구현한 class 를 생성할 것 이다.</p>
<pre><code>

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
import lombok.extern.slf4j.Slf4j;

@WebListener
@Slf4j
public class FileWatch implements ServletContextListener, Runnable{
    private Thread thread;
    private boolean isShutdown = false;

    private ServletContext sc;


    public void startFileWatch(){
        if(thread == null)
            thread = new Thread(this, &quot;Watch Thread&quot;);

        if(!thread.isAlive())
            thread.start();
    }


    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();

        while(currentThread == thread &amp;&amp; !this.isShutdown){
            try{
                log.info(&quot;file watch is running&quot;);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        log.info(&quot;fileWatch end&quot;);
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) { // 생성자, 즉 web application 이 실행될때 실행된다.
        log.info(&quot;contextInitialized call&quot;);
        startFileWatch();
        ServletContextListener.super.contextInitialized(sce);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) { // 소멸자, web application이 삭제될 때 실행된다.
        log.info(&quot;contextDestroyed call&quot;);
        this.isShutdown = true;
        try {
            thread.join();
            thread = null;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        ServletContextListener.super.contextDestroyed(sce);
    }
}</code></pre><p>이후 servlet 컨테이너에 등록하기위해 applcation 실행 class에 ServletComponentScan Annotation을 사용한다.</p>
<pre><code>@SpringBootApplication
@ServletComponentScan
public class NHNStorageApplication {

    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
//                .addApplicationListener(new ApplicationPidFileWriter());
    }

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

<h2 id="결론">결론</h2>
<p>다른 방법도 찾는도중
@PostConstruct annotation의 존재를 알았다.</p>
<h3 id="postconstruct-annotation">@PostConstruct annotation</h3>
<p>객체가 인스턴스화된 직후 서비스에 투입되기 전에 호출되어야 하는 클래스의 메서드를 표시하는 데 사용되는 Java 주석입니다. 개체를 사용하기 전에 필요한 초기화를 수행하기 위한 콜백 메서드로 자주 사용됩니다.</p>
<p>내겐 이 방법이 더 좋은것 같아서 
Thread 는 @PostConstruct annotation 을 이용해 생성하기로 결론냈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react API호출시 반환값이 이상할때]]></title>
            <link>https://velog.io/@kim_think_rae/react-API%ED%98%B8%EC%B6%9C%EC%8B%9C-%EB%B0%98%ED%99%98%EA%B0%92%EC%9D%B4-%EC%9D%B4%EC%83%81%ED%95%A0%EB%95%8C</link>
            <guid>https://velog.io/@kim_think_rae/react-API%ED%98%B8%EC%B6%9C%EC%8B%9C-%EB%B0%98%ED%99%98%EA%B0%92%EC%9D%B4-%EC%9D%B4%EC%83%81%ED%95%A0%EB%95%8C</guid>
            <pubDate>Sat, 04 Mar 2023 09:32:23 GMT</pubDate>
            <description><![CDATA[<p>먼저 api 호출부를 보자.</p>
<pre><code>export default async function loginApi(userId, userPwd){    
    const response = await axios.post(
        &#39;/member/login&#39;,
        {
            userId : userId,
            userPwd : userPwd
        }
    ).catch((e) =&gt; {
        console.log(e);
        alert(&quot;로그인 실패&quot;);
    })

    return response.data;
}</code></pre><p>간단하게 test하기위해 만든 login api 호출부이다. 여기서 내가 반환한 response 를 호출부에서 받아 사용하려고 했는데 문제가 undefined 값이 오는걸 확인했다.</p>
<p>이유는 간단하다. 함수내부에 axios를 await(동기) 처리해준다. 이렇게 하면 
response에 결과값이 들어오는건 맞다.</p>
<p>하지만 함수 호출부에서 이녀석을 await로 호출해주지 않으면 reponse 값을 받기전에 비동기로 함수를 반환해버린다. 따라서 호출부에도 await를 사용해주면 된다.</p>
<h3 id="해결-전">해결 전</h3>
<pre><code>function handleLogin(userId, userPwd){
    var member = loginApi(userId, userPwd);
    console.log(member);
    if(member.status == &quot;success&quot;){
      setUser(member.Member.userId);
    }
  }</code></pre><hr/>

<h3 id="해결-후">해결 후</h3>
<pre><code>async function handleLogin(userId, userPwd){
    var member = await loginApi(userId, userPwd);
    console.log(member);
    if(member.status == &quot;success&quot;){
      setUser(member.Member.userId);
    }
  }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Router 와 Switch Componet / BackApi]]></title>
            <link>https://velog.io/@kim_think_rae/Router-%EC%99%80-Switch-Componet-BackApi</link>
            <guid>https://velog.io/@kim_think_rae/Router-%EC%99%80-Switch-Componet-BackApi</guid>
            <pubDate>Fri, 03 Mar 2023 15:23:44 GMT</pubDate>
            <description><![CDATA[<h2 id="page이동에-효율적인-방법이-없나-">Page이동에 효율적인 방법이 없나 ?</h2>
<p>State 변경에 의한 page 변경시에 효율적이거나 많이 쓰고있는 방법이 없나 몰색하던 도중 Router와 Switch 태그에 대해 알게되었다.</p>
<p>Router 컴포넌트 url을 맵핑시켜 원하는 Page에 이동하게 하는것이다.</p>
<p>Switch를 사용하는 이유는 기존에 Route 컴포넌트만을 사용했을때는 ErrorPage를 보여줄 때 path값을 비우면 모든 url과 mapping 되는 문제가 발생한다고 한다.</p>
<p>근데 이거 웬일 ? 문제가 생겨서 보니 Switch 는 React 6 version 이후 사라졌다고 한다. 대신 Routes를 사용한다고 한다.</p>
<blockquote>
<p>에라 어짜피 기능은 둘다 똑같겠지 함 해보자 !  </p>
</blockquote>
<p>거두절미하고 나의 App.js 를 보자</p>
<pre><code>return (
    &lt;Router&gt;
      &lt;Routes&gt;
        &lt;Route exact element={&lt;Login/&gt;} /&gt;
        &lt;Route path=&quot;/main&quot; element={&lt;MainContent/&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/Router&gt;
 );</code></pre><p>사용법은 따로 설명할 필요도 없이 굉장히 직관적이고 명료하다 ! 다음부터도 이렇게 사용해야지 ! </p>
<hr/>


<p>필자는 React로 page를 만들 때 느낀점이 axios(or fetch)를 사용할시에 component가 있는 파일에 같이 선언해주었는데 굉장히 더럽고 가독성이 떨어져서 다음 프로젝트 진행시에는 한 파일에 api를 몰아넣고 관리하면 더 깔끔하게 할 수 있지않을까 생각했고 사수GPT님께서 좋은 방법이라고 칭찬해주셨기에 이번에는 api를 한곳에 몰아넣고 진행하려고 한다.</p>
<hr>



]]></description>
        </item>
        <item>
            <title><![CDATA[JPA Repository Query 작성법 및 JPA 전반적인 정리]]></title>
            <link>https://velog.io/@kim_think_rae/JPA-Repository-Query-%EC%9E%91%EC%84%B1%EB%B2%95</link>
            <guid>https://velog.io/@kim_think_rae/JPA-Repository-Query-%EC%9E%91%EC%84%B1%EB%B2%95</guid>
            <pubDate>Wed, 01 Mar 2023 09:40:00 GMT</pubDate>
            <description><![CDATA[<p>repository 가 제공하는 save 메소드를 사용중 문제가 발생했다.</p>
<p>save진행시 null값들을 제외하지않고 업데이트 해버려서 내가 생각하는 결과와는 달랐다. 따라서 직접 update Query를 작성하고 사용해야했다.</p>
<pre><code>public interface MemberRepository extends JpaRepository&lt;MemberEntity, String&gt; {
    @Modifying
    @Transactional
    @Query(value=&quot;update Member m set m.status = &#39;N&#39; where m.user_id = :id&quot;, nativeQuery = true)
    int deleteMember(@Param(&quot;id&quot;) String id);
}

</code></pre><p>update query는 @Modifying 어노테이션을 사용한다.
콜론(:)은 메소드의 매개변수를 사용할때 사용한다.</p>
<p>natvieQuery를 true 값으로 해줘야 이 query가 jpql이 아닌 native query라고 알게된다. </p>
<p>transactional 어노테이션은 트랜잭션을 자동으로 관리해주게끔 해준다.</p>
<p>int 형을 사용하면 어떤 행에 영향이 갔는지 갯수를 반환한다.</p>
<p>근데 이 코드를 chat-gpt에게 보여드리니 이친구가 sql injection 공격에 취약하니 jpql 또는 Criteria API을 사용하여 쿼리를 작성하는 것이 권장된다고 한다.</p>
<h2 id="sql-injection">sql injection</h2>
<p>sql 삽입공격은 흔히 이뤄지는 공격으로써 입력데이트럴 필터링하여 방어할 수 있다고한다. </p>
<blockquote>
<p>sql 삽입공격에 대한 예제를 보았는데 사용자가 query에대해 직접 접근할 수 없는데 jpa에서 이 문제가 발생할 수 있나 ?? ... 흠 ... 잘 모르겟다 일단 사수 gpt님께서 있다고 하시니 수정해야겟다.</p>
</blockquote>
<h2 id="jpql">JPQL</h2>
<p>JPQL은 엔티티 객체를 조회하는 객체지향 쿼리이다. 테이블을 대상으로 쿼리하는것이 아니라 엔티티를 대상으로 쿼리한다.</p>
<blockquote>
<p>적으면서 생각해본건대 이러면 외부에서 db에 대한구조를 알 수 없으니 보안에서 강한건가 싶다. -&gt; 알아보니 맞는말이라더라</p>
</blockquote>
<p>JPQL은 결국 SQL으로 변환된다.
JPA에서 제공하는 메소드 호출만으로 섬세한 쿼리 작성이 어렵다는 문제에서 JPQL이 탄생된 것이다.</p>
<h2 id="criteria-api">Criteria api</h2>
<p>객체지향 쿼리 빌더라고 한다. 이걸 사용하면 코드로 jpql을 작성할 수 있고 컴파일 단계에서 sql에러를 잡아낼 수 있는 장점이 있다고한다.</p>
<h2 id="결론">결론</h2>
<h3 id="jpql을-사용하자">jpql을 사용하자.</h3>
<p>jpql을 사용하니 sql injection예제에 있는 내용을 적어도 문제가 발생하지 않는다.</p>
<p>근데 이 문제는 생각해보니 내가 userId로 먼저 조회해보고 query를 날리니 생길수가 없는 문제다... 그래도 이런 문제가 생길 수 있는지 알아서 다행이다.... </p>
]]></description>
        </item>
    </channel>
</rss>