<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Appkr.memo(new Story())</title>
    <description>appkr&apos;s blog to record day-to-day life &amp; thoughts.</description>
    <link>https://blog.appkr.dev/</link>
    <atom:link href="https://blog.appkr.dev/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 03 Dec 2023 19:23:47 +0900</pubDate>
    <lastBuildDate>Sun, 03 Dec 2023 19:23:47 +0900</lastBuildDate>
    <generator>Jekyll v4.3.2</generator>
    
      <item>
        <title>내가 경험한 제품 기획</title>
        <description>&lt;p&gt;세상은 두 부류의 사람으로 나눌 수 있습니다: &lt;strong&gt;자본가&lt;/strong&gt;와 &lt;strong&gt;노동자&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;기업의 제무제표를 잘 들여다보면, 이 둘의 역할이 보입니다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-11-05-samsung.png&quot;&gt;&lt;img src=&quot;/images/2023-11-05-samsung.png&quot; alt=&quot;삼성전자&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;그림은 &lt;a href=&quot;//dart.fss.or.kr&quot;&gt;금감원 전자공시시스템&lt;/a&gt;에 공시된 삼성전자의 2022년 재무상태표입니다. 숫자가 너무 커서 읽기가 어려운데, 2022년 말 기준으로 삼성전자의 자산 규모는 448조원입니다. 이 중 93조원은 부채로 조달했고, 355조원은 자기자본으로 조달했네요. 2022년 동안의 매출은 302조원이고, 순이익이 55조원입니다.&lt;/p&gt;

&lt;p&gt;기업이란 뜻 있는 기업가가 &lt;strong&gt;자본가&lt;/strong&gt;로부터 투자&lt;small class=&quot;text-muted&quot;&gt;자본&lt;/small&gt;를 받거나 차용&lt;small class=&quot;text-muted&quot;&gt;부채&lt;/small&gt;하여 사업에 필요한 자산을 구매하여 수익 활동을 하는 것입니다. 기업은 기계장치와 같은 고정자산만으로 수익을 낼 수 없습니다. 당연히 &lt;strong&gt;노동자&lt;/strong&gt;가 필요하죠. 노동자는 노동력 제공의 댓가로 근로 소득을 얻습니다.&lt;/p&gt;

&lt;p&gt;기업 활동의 결과로 노동자는 근로 소득을 받아 가계를 유지하고, 주주는 자본 이득을, 채권자는 이자 수익을 얻습니다. 근로자는 근로소득세를 납부하고, 기업은 법인세를, 주주는 주식소득세를, 채권자는 이자소득세를 납부합니다. 이것이 제가 배운 가계, 기업, 정부가 돌아가는 원리입니다.&lt;/p&gt;

&lt;p&gt;필자는 개발자로 전업하기 전에 스마트카드&lt;small class=&quot;text-muted&quot;&gt;T-money 카드&lt;/small&gt;, GSM/GPRS 휴대폰 설계 및 제조&lt;small class=&quot;text-muted&quot;&gt;벨웨이브&lt;/small&gt;, 범용 반도체 설계 및 제조&lt;small class=&quot;text-muted&quot;&gt;코아로직&lt;/small&gt; 등 임베디드 시스템 산업에서 제품 매니저로 10년간 일했습니다. &lt;strong&gt;가치 있는 제품, 즉 소비자가 기꺼이 돈을 내고 구매할 멋진 제품을 발굴하고, 그 제품을 시장에 딜리버리할 때까지의 전 과정을 책임지는 것이 제품 매니저의 역할&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;p&gt;가치 있는 제품을 발굴하고 딜리버리하는 제품 매니저의 본질은 같지만, 소프트웨어 회사의 제품 매니저와는 하는 일이 다랐습니다. 이 포스트에서는 하드웨어 회사의 제품 매니저로서 일하던 당시의 시대 상황, 풀어야 했던 문제들에 대해 써보려 합니다.&lt;/p&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;h2 id=&quot;대-격변의-시대&quot;&gt;대 격변의 시대&lt;/h2&gt;

&lt;p&gt;필자가 임베디드 시스템 산업에서 제품 매니저로 일하던 2002년부터 2011년까지는 대 격변기였습니다: 스마트폰, WCDMA&lt;small class=&quot;text-muted&quot;&gt;3G&lt;/small&gt; 등 &lt;strong&gt;모바일 혁명&lt;/strong&gt;과 AWS, MS, 애플, 구글을 중심으로 &lt;strong&gt;클라우드 혁명&lt;/strong&gt;이 일어났던 시기였습니다.&lt;/p&gt;

&lt;p&gt;필자가 일했던 휴대폰 설계 및 제조 회사는 역사 속으로 사라졌습니다. 당시엔 GPRS/CDMA 등 2G 통신망에서 각 회사마다 고유한 운영체제와 애플리케이션을 개발하던 시절이었습니다 &lt;small class=&quot;text-muted&quot;&gt;Feature Phone&lt;/small&gt;. 국내엔 삼성, LG 뿐만아니라 팬텍, VKMobile, 벨웨이브와 같은 회사들이 플레이하고, 해외에선 노키아아 전세계를 장악했었죠. 중국, 유라시아, 남미 시장을 공략하던 벨웨이브도 UMTS &lt;small class=&quot;text-muted&quot;&gt;Universal Mobile Telecommunication System&lt;/small&gt;, 즉 3G 통신의 물결을 준비해야 했고, 개발 실패와 제품 경쟁력 저하로 문을 닫았습니다.&lt;/p&gt;

&lt;p&gt;멀티미디어 반도체를 설계하는 코스닥에도 등록된 회사로 옮겼는데요. 인수한 모회사와&lt;small class=&quot;text-muted&quot;&gt;인수한 회사는 삼성전자와 관련된 그룹&lt;/small&gt;의 역학 관계, HLOS &lt;small class=&quot;text-muted&quot;&gt;High-Level OS, Symbian, WinMo, Linux/Android 등&lt;/small&gt; 개발 실기(失期)와 글로벌 시장에서의 경쟁력 저하 등으로 중국계 회사에 팔렸다가 지금은 문을 닫았습니다.&lt;/p&gt;

&lt;p&gt;사실 외부적인 요인들이 더 컸죠. 스티브잡스의 &lt;strong&gt;아이폰&lt;/strong&gt;은 전세계를 충격에 빠뜨렸습니다: 블랙베리, 윈도모바일, 노키아 뿐만아니라, MP3 Player, PMP, 차량용 네비 산업까지 초토화시켰으니까요. 게다가 &lt;strong&gt;안드로이드&lt;/strong&gt; 플랫폼은 하드웨어를 표준화시켜버렸습니다: 과거엔 각자의 운영체제로 각자의 특화된 애플리케이션을 담아 매력적인 폼팩터로 고객의 지갑을 열었습니다만, 안드로이드는 우리 플랫폼을 구동시키려면 요런 저런 하드웨어 사양으로 설계해야 해~라며 그간의 관행을 완전 뒤집어 버렸죠.&lt;/p&gt;

&lt;p&gt;주) 노키아의 사업 철수로 쏟아져 나온 개발자들의 게임 스타트업을 차리면서 핀란드의 GDP 구성이 바뀌었다. 안드로이드 출시로 직장을 잃은 또는 자발적으로 퇴사한 개발자들이 앱 스토어의 시대를 열었다.&lt;/p&gt;

&lt;h2 id=&quot;2010-국내-팹리스-업계의-상황&quot;&gt;2010 국내 팹리스 업계의 상황&lt;/h2&gt;

&lt;p&gt;폐업한 회사라 이하의 자료는 공개해도 괜찮다고 판단했습니다.&lt;/p&gt;

&lt;div class=&quot;grid-container&quot;&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-1.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-1&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-1.png&quot; alt=&quot;2023-11-05-fabless-1&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-2.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-2&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-2.png&quot; alt=&quot;2023-11-05-fabless-2&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-3.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-3&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-3.png&quot; alt=&quot;2023-11-05-fabless-3&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-4.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-4&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-4.png&quot; alt=&quot;2023-11-05-fabless-4&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-5.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-5&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-5.png&quot; alt=&quot;2023-11-05-fabless-5&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-fabless-6.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-fabless-6&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-fabless-6.png&quot; alt=&quot;2023-11-05-fabless-6&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;그림을 보면 당시 국내 팹리스 &lt;small class=&quot;text-muted&quot;&gt;제조 기반 없이 반도체 설계만 전문으로 하는 회사&lt;/small&gt; 산업의 상황을 들여다 볼 수 있습니다: 그간에 저가로만 경쟁하던 차이완 &lt;small class=&quot;text-muted&quot;&gt;차이나 + 타이완&lt;/small&gt;들의 기술 경쟁력이 높아지면서 선진 제조업체들과의 틈새에 낀 &lt;strong&gt;넛 크래커&lt;/strong&gt; &lt;small class=&quot;text-muted&quot;&gt;넛 크래커(nut-cracker)는 호두를 양쪽에서 눌러 까는 호두까기 기계를 말하는데 한 나라가 선진국보다는 기술과 품질 경쟁에서, 후발 개발도상국에 비해서는 가격 경쟁에서 밀리는 현상을 지칭할 때 쓰임&lt;/small&gt; 상황이었습니다.&lt;/p&gt;

&lt;div class=&quot;grid-container&quot;&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-corelogic-1.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-corelogic-1&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-corelogic-1.png&quot; alt=&quot;2023-11-05-corelogic-1&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-corelogic-2.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-corelogic-2&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-corelogic-2.png&quot; alt=&quot;2023-11-05-corelogic-2&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-corelogic-3.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-corelogic-3&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-corelogic-3.png&quot; alt=&quot;2023-11-05-corelogic-3&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-corelogic-4.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-corelogic-4&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-corelogic-4.png&quot; alt=&quot;2023-11-05-corelogic-4&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&quot;grid-item&quot;&gt;
        &lt;a href=&quot;/images/2023-11-05-corelogic-5.png&quot; data-toggle=&quot;lightbox&quot; data-title=&quot;2023-11-05-corelogic-5&quot;&gt;
            &lt;img src=&quot;/images/2023-11-05-corelogic-5.png&quot; alt=&quot;2023-11-05-corelogic-5&quot; /&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;이 상태에서 틈새를 찾고, 서두에 말한 기업의 존재 이유를 달성할 제품을 발굴하여, 턴어라운드를 달성하는 것이 제품 매니저의 역할이었죠.&lt;/p&gt;

&lt;p&gt;이때쯤 제품 매니저 업무를 잠시 내려 놓고, 대략 1년 동안 &lt;small class=&quot;text-muted&quot;&gt;보안이 중요한 일이라&lt;/small&gt; 골방에 갇혀서 턴어라운드 전략을 수립하고 실행했었습니다. 그림에서 보듯이 1) 미래의 시장 흐름, 당사의 역량등을 고려해 진입할 사업을 선정하고, 2) 후보 업체를 발굴하여 LOI&lt;small class=&quot;text-muted&quot;&gt;Letter Of Intent, 인수의향서&lt;/small&gt;를 보내고, 3) 변호사, 회계사와 업체를 방문하고 실사하고, 4) 인수 여부를 결정하는 일이었죠. 제품 매니저는 도메인 전문가라 이런 일도 하게 되는 것 같아요(경영 컨설턴트로 일했던 경험이 큰 도움이 됐습니다).&lt;/p&gt;

&lt;h2 id=&quot;제품-매니저의-업무&quot;&gt;제품 매니저의 업무&lt;/h2&gt;

&lt;p&gt;요즘은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jira&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Slack&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Figma&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Google Suite&lt;/code&gt; 와 같은 도구를 이용해서 업무를 관리하지만, 저 때는 폴더, 이메일, 그룹웨어, MS Office 등이 최선이었습니다. 그림의 두번재 레인의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YYYYMM_OOOO&lt;/code&gt;로 된 폴더가 프로젝트였고, 각 프로젝트 하위에는 산출물들이 보일겁니다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-11-05-product-manager.png&quot;&gt;&lt;img src=&quot;/images/2023-11-05-product-manager.png&quot; alt=&quot;폴더&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;제품 매니저의 최종 결과물은 &lt;strong&gt;제품 기획서&lt;/strong&gt;와 &lt;strong&gt;제품&lt;/strong&gt;입니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(기술 지식이 필요하므로) 엔지니어 출신이 제품을 기획한다&lt;/li&gt;
  &lt;li&gt;(제조가 수반되므로) 제품 원가 개념을 잘 알아야 한다
    &lt;ul&gt;
      &lt;li&gt;천문학적 개발비가 든다&lt;/li&gt;
      &lt;li&gt;출시되면 제품 수정이 어렵다&lt;/li&gt;
      &lt;li&gt;프로젝트를 잘 관리해야 한다: 돈-사람-시간&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;하드웨어의 제품 수명은 3~5년이다
    &lt;ul&gt;
      &lt;li&gt;3~5년 뒤를 예측해서 제품을 기획한다&lt;/li&gt;
      &lt;li&gt;메가 트렌드를 봐야 한다&lt;/li&gt;
      &lt;li&gt;경쟁사의 제품 로드맵을 정확히 꿰고 있어야 한다&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;제품-기획서-훑어보기&quot;&gt;제품 기획서 훑어보기&lt;/h2&gt;

&lt;p&gt;아래는 모바일TV 전용 제품 기획서 풀 버전입니다. &lt;small class=&quot;text-muted&quot;&gt;기구, 폼팩터, 재질, GUI, 기본 탑재 애플리케이션, 메뉴 트리 구성 등 추가적인 부분들이 더 있지만&lt;/small&gt; 휴대폰 회사의 제품 기획서도 이와 비슷합니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023-11-05-hawk.png&quot; alt=&quot;모바일TV 전용 제품 기획서 풀 버전&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;panel panel-default&quot; style=&quot;width:100%; max-width: 600px; margin: 1em auto;&quot;&gt;
  &lt;div class=&quot;panel-body text-center&quot;&gt;
    &lt;a href=&quot;/files/2023-11-05-hawk.pdf&quot;&gt;
      &lt;i class=&quot;material-icons&quot;&gt;open_in_browser&lt;/i&gt;
      브라우저에서 슬라이드 열기
    &lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;이런 내용들이 담겨 있을 겁니다.&lt;/p&gt;

&lt;h3 id=&quot;기획-의도-및-기대-효과-왜-p4&quot;&gt;기획 의도 및 기대 효과 (“왜?”, p4)&lt;/h3&gt;

&lt;p&gt;모바일TV 애플리케이션용 A/V 디코딩에 최적화된 ASSP &lt;small class=&quot;text-muted&quot;&gt;Application Specific Standard Product&lt;/small&gt; 시장에서 오랫동안 선전했던 기존 제품의 시장 경쟁력이 떨어지고, 기존 제품 출시 이후 신규로 나온 시장&lt;small class=&quot;text-muted&quot;&gt;일본의 ISDB-T 1Seg, 중국의 CMMB 규격&lt;/small&gt;까지 커버하는 초저가 제품으로 기획했네요. 고객의 제품 적용 편의 및 고객의 개발 기간을 단축을 위해 풀 모듈로 패키징합니다. 7억원의 NPV &lt;small class=&quot;text-muted&quot;&gt;Net Present Value, 순현재가치&lt;/small&gt;, 20%의 IRR&lt;small class=&quot;text-muted&quot;&gt;Internal Rate of Return, 내부수익율&lt;/small&gt;을 기대합니다.&lt;/p&gt;

&lt;h3 id=&quot;목표-시장-선정과-시장성-어디서-왜-p5p8&quot;&gt;목표 시장 선정과 시장성 (“어디서?”, “왜?”, p5~p8)&lt;/h3&gt;

&lt;p&gt;이 제품은 기획 의도에서 목표 시장에 대한 힌트가 있어서 목표 시장 선정 측면에서는 수월했었습니다.&lt;/p&gt;

&lt;p&gt;메가트렌드, &lt;a href=&quot;https://ko.wikipedia.org/wiki/5_%EC%84%B8%EB%A0%A5_%EB%AA%A8%ED%98%95&quot;&gt;5 Forces&lt;/a&gt;의 역학 관계, 내부 기술 역량 등 다양한 변수를 고려해서 이익을 낼 수 있는 시장을 선정합니다. 시장을 크게 잡으면 제품이 커지고, 따라서 개발비/기간이 올라갑니다. 시장이 너무 작으면 기대 이익이 적어서 개발비를 회수하지 못합니다. 이 기획에서는 Phone용 모바일 TV, 대형 TV 시장을 제외한 중간 사이즈의 디바이스를 목표 시장으로 하고 있네요. 데이터, 분석력, … 중요합니다만, 이 지점에서 제품 매니저의 &lt;strong&gt;촉&lt;/strong&gt;과 이 제품은 된다라는 &lt;strong&gt;자신감&lt;/strong&gt;이 큰 역할을 합니다.&lt;/p&gt;

&lt;p&gt;당시 Mobile TV 애플리케이션용 A/V 디코딩에 최적화된 ASSP 풀 모듈 시장 규모를 23.7백만개(3년 합계)로 예측했네요.&lt;/p&gt;

&lt;h3 id=&quot;제품-기능명세-무엇을-p9&quot;&gt;제품 기능명세 (“무엇을?”, p9)&lt;/h3&gt;

&lt;p&gt;그래서 뭘 만들건데?라는 질문에 대한 답입니다.&lt;/p&gt;

&lt;p&gt;IP &lt;small class=&quot;text-muted&quot;&gt;Intellectual Property, 설계 자산&lt;/small&gt;를 하나 더 추가하면, 공략할 수 있는 시장 범위가 늘어납니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SDIO&lt;/code&gt;&lt;small class=&quot;text-muted&quot;&gt;Secure Digital I/O&lt;/small&gt;를 하나 더 추가함으로써, WiFi를 탑재하기를 원하는 고객에게도 제품을 팔 수 있게 됩니다. 대신 5만 Gate Count가 추가되고 그로 인해 제품 한 개당 웨이퍼 면적이 커져 제품당 원가가 0.78Cent 증가합니다. 결정이 필요한 지점이겠죠?&lt;/p&gt;

&lt;h3 id=&quot;개발-계획-p10&quot;&gt;개발 계획 (p10)&lt;/h3&gt;

&lt;p&gt;대략적인 개발 일정과 제품 개발비를 추정해 봅니다. 개발비 24.7억원, 최초로 판매 가능한 상태까지는 1년 정도를 잡았네요.&lt;/p&gt;

&lt;p&gt;제품 매니저가 독단적으로 정했을까요? 당연히 개발팀과 티키타가했겠죠? 세상에 1원에 만들 수 있다고 공격적으로 말하면 사업성이 안 나오는 제품은 없을 겁니다. 한편 개발하는데 10년이 걸린다고 보수적으로 말하면 사업성이 나오는 제품은 없을 겁니다. 기획 시점의 일정 목표, 개발비등은 그냥 목표입니다. 당연히 정확하지않고, 일을 진행하면서 조정해야 합니다. 조정하더라도 항상 목표는 꼭 필요하죠.&lt;/p&gt;

&lt;h3 id=&quot;제품-판가-p11&quot;&gt;제품 판가 (p11)&lt;/h3&gt;

&lt;p&gt;마케팅 과목에서 4P &lt;small class=&quot;text-muted&quot;&gt;Product, Price, Place, Promotion&lt;/small&gt;를, 경제학에서 수요공급법칙을 배우며 가격이란 걸 배웠죠. 교과서에는 가격을 결정하는 방법도 참 여러 가지였던 것 같아요. 이 제품은 표준 가격을 3.5달러로 잡았었네요.&lt;/p&gt;

&lt;h3 id=&quot;매출-및-이익-계획-p12p13&quot;&gt;매출 및 이익 계획 (p12~p13)&lt;/h3&gt;

&lt;p&gt;제품 수명 주기 동안 총 7백만개를 팔아서 286억원의 매출을 하겠다고 했군요. 위에서 공략 가능 시장 규모를 23.7백만개로 잡았으니 30%의 시장을 점유하겠다는 계획입니다. 기존 제품을 사용하던 고객 위주로 이 매출을 달성하겠다는 계획이군요.&lt;/p&gt;

&lt;p&gt;기존에 타사의 제품을 쓰다가 어느날 갑자기 우리 제품으로 갈아탄다? 사실 하드웨어는 러닝 체인지라는 것이 매우 어렵습니다. 게다가 고객 제품은 여전히 수명이 남아 있을 수 있고, 우리의 제품 출시로 인해 신제품을 설계할 것이라는 보장도 없습니다. 이 부분은 필자의 전문 분야가 아닌 영업팀의 영역입니다. 자동차, 보험 영업이 어렵다지만, 전방 시장도 하드웨어인 반도체 시장도 만만치 않은 것 같더군요.&lt;/p&gt;

&lt;h3 id=&quot;사업성-분석-p14p15&quot;&gt;사업성 분석 (p14~p15)&lt;/h3&gt;

&lt;p&gt;이상의 모든 계획으로부터 돈을 벌 수 있을까요? 서두에 언급한 기업의 역할을 다할 수 있을까요?&lt;/p&gt;

&lt;h3 id=&quot;시장-진입-및-경쟁-전략-p16p17&quot;&gt;시장 진입 및 경쟁 전략 (p16~p17)&lt;/h3&gt;

&lt;p&gt;싸워서 터지지 않을 묘안을 제시해야합니다. 앞서도 언급했지만 &lt;small class=&quot;text-muted&quot;&gt;개발비가 커서&lt;/small&gt; 다양한 시장을 공략할 수 있도록 제품을 좀 크게 만들고, 일부 기능을 (비)활성하도록 패키징을 달리하며 다른 제품인 것처럼 판매하는 것이 당시의 관례였습니다.&lt;/p&gt;

&lt;p&gt;이 제품은 이런 관례를 버리고, 전용 제품으로 만들어 가성비를 추구하는 것이 컨셉이었습니다. 게다가 고객의 하드웨어 보드 실장 공간을 줄여 더 예쁜 폼 팩터를 설계할 수 있는 자유도도 제공하고자 했습니다.&lt;/p&gt;

&lt;h3 id=&quot;핵심-성공-요인-위험-요소-위험-회피-계획-p18&quot;&gt;핵심 성공 요인, 위험 요소, 위험 회피 계획 (p18)&lt;/h3&gt;

&lt;p&gt;시장엔 항상 불확실성이 존재합니다. 조직도 마찬가지입니다.&lt;/p&gt;

&lt;h2 id=&quot;결론&quot;&gt;결론&lt;/h2&gt;

&lt;p&gt;기획서 내의 모든 문장은 왜?라는 챌린지를 받습니다. 하루 종일 조사하고 팀들과 인터뷰하고 밤새 정리해서 회의실에 들어가면 깨지기 일쑤였습니다.&lt;/p&gt;

&lt;p&gt;제품 매니저 혼자서 이상의 모든 결정을 하지 않습니다. 치열하게 논쟁하고 조율하는 과정을 거쳐서 모두의 합의를 이끌어 내야 합니다. 화두를 던지고, 그 과정을 온전하게 이끌고 가야 하는 사람이 제품 매니저입니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;세상에 쉬운 일은 없다.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;style&gt;
    /* 기본 스타일 */
    .grid-container {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 10px;
    }

    .grid-item {
        text-align: center;
        padding: 10px;
        border: 1px solid #ccc;
    }

    /* 좁은 화면에서 레이아웃 변경 */
    @media screen and (max-width: 768px) {
        .grid-container {
            grid-template-columns: repeat(1, 1fr);
        }
    }
&lt;/style&gt;

</description>
        <pubDate>Sun, 05 Nov 2023 09:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/learn-n-think/hw-product-planning/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/learn-n-think/hw-product-planning/</guid>
        
        <category>기획자</category>
        
        <category>경력개발</category>
        
        
        <category>work-n-play</category>
        
        <category>learn-n-think</category>
        
      </item>
    
      <item>
        <title>내가 경험한 DDD, Hexagonal</title>
        <description>&lt;p&gt;최근에 &lt;a href=&quot;https://flex.team/&quot;&gt;플렉스&lt;/a&gt; 로 이직을 했다. &lt;a href=&quot;https://vroongcorp.com/&quot;&gt;부릉(舊 메쉬코리아)&lt;/a&gt; 에서 7년간 배우고 적용했던 DDD &lt;small class=&quot;text-muted&quot;&gt;Domain Driven Design, 도메인 주도 설계&lt;/small&gt;와 Hexagonal Architecture Pattern&lt;small class=&quot;text-muted&quot;&gt;육각형 구조&lt;/small&gt;에 대해 한판 정리하면서, 지난 시간을 회고해 본다.&lt;/p&gt;

&lt;p&gt;2016년 마흔이 넘은 나이에 하던 일을 그만 두고 직업 개발자로 전업을 위한 준비를 하던 시절이었다. 한 통의 전화를 받고 티타임을 하러 갔다가, 우선 시간을 벌며 다음 기회를 보자는 심산으로 입사하기로 약속했다.&lt;/p&gt;

&lt;p&gt;입사하기 전까지는 전혀 몰랐다. 공동설립자 대부분이 과학고 등 특수고를 나와서 미국에서 대학을 졸업했고, 국제 정보 올림피아드에서 우승한 이력도 있는 천재 개발자 집단이었다(당시 대표는 개발자 아님). 국내 대학을 나오지 않았기에 고등학교 동료나 동료들의 친구들로 초기 팀업을 했다. 팀 또한 어마어마했다. 그런데 첫 서비스를 실패했다. 내가 합류했던 시점은 새 서비스로 피봇팅하던 때였다. 그 서비스가 지금의 사명과도 같은 부릉이다.&lt;/p&gt;

&lt;p&gt;나는 운이 참 좋은 사람이다. 합류할 때부터 DDD를 시전하며 Agile 방식으로 일하고 있었고, 부릉 서비스도 당연히 DDD 사상이 들어갔다. 직업 개발자로 전업을 하기 위한 전략으로 &lt;a href=&quot;https://www.yes24.com/Product/Goods/33320248&quot;&gt;개발 서적을 한권 집필&lt;/a&gt; 했기에 해당 웹 프레임워크를 자유롭게 다룰 수 있고 어찌저찌 원하는 서비스를 만들 수 있는 수준이었지만, 괜찮은 팀에서 괜찮은 동료들로부터 소프트웨어 설계를 배운 경험은 전무했다.&lt;/p&gt;

&lt;p&gt;급하게 DDD를 배울 수 있는 책을 찾았고, 처음 접한 책이 범균님의 &lt;a href=&quot;https://www.yes24.com/Product/Goods/27750871&quot;&gt;DDD START! (현재는 절판되고 신버전 출시됨)&lt;/a&gt; 라는 책이었다. 그동안 내가 만든 소프트웨어가 얼마나 엉망이었는지 심각하게 각성하는 계기가 됐다. 이어서 Evans와 Vernon의 책도 섭렵했다. 그 때부터 지하철로 출퇴근하며 소프트웨어 고전들을 하나씩 읽어나갔고, 배운 내용은 실무에 차근차근 적용해 나갔다.&lt;/p&gt;

&lt;p&gt;입사부터 2년 동안은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHP&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt;로 &lt;small class=&quot;text-muted&quot;&gt;Legacy&lt;/small&gt; 서비스를 개발하고 운영했다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt;은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Active Record&lt;/code&gt; 패턴으로 구현한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ORM&lt;/code&gt;을 사용하고 있어서 DDD를 적용하는데 고전했던 기억이 난다. 그 뒤엔 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Java&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Spring&lt;/code&gt;으로 &lt;small class=&quot;text-muted&quot;&gt;MSA&lt;/small&gt; 서비스들을 개발하고 운영했다. MSA로 전환할 때 DDD의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bounded Context&lt;/code&gt; 개념이 큰 도움이 되었다. 최근 몇 년 동안은 Layered 대신 Hexagonal로 애플리케이션을 구성했고, 외부 세상과 애플리케이션을 분리함으로써 얻는 잇점들도 경험했다.&lt;/p&gt;

&lt;p&gt;공동 설립자들도 활발하게 실무 개발에 참여했다. 회사의 직원이 200명이던 시절, CTO 타이틀을 달고도 실무 개발하는 장면을 목격하고, &lt;em&gt;“더 중요한 일을 하셔야 할 분이 개발 좀 그만하시라”&lt;/em&gt; 고 말했던 적이 있다. 입사하기 전에 잘난 척했지만, 결국 우물안 개구리였다는 사실을 깨닫는데 오래 걸리지 않았다. 이 분들은 실무 개발 지식 뿐만아니라, 문제 해결 능력도 뛰어나고, 부릉 서비스 초기에 거의 매일 자정까지 야근하는 나보다 더 열심히 일했다.&lt;/p&gt;

&lt;p&gt;가장 많이 타 본 차의 운전 스타일을 닮는다고 한다. 난 아버지 차를 가장 많이 타 봤고, 아버지의 운전 스타일을 그대로 닮았다. 내가 개발자로 일하는 방식, 구현 스타일은 부릉의 공동 설립자 및 동료 개발자들의 영향을 많이 받았다. 이 포스트를 통해 감사의 마음을 한번 더 표현한다.&lt;/p&gt;

&lt;p&gt;입사 당시 100명 남짓이었고, 퇴사할 때 공동 설립자를 제외하고 나보다 사번이 높은 직원은 두세 명에 불과했다. 부릉 서비스 시작 시점에 하루 천 개도 안되는 배송 신청을 받았다면, 퇴사할 땐 하루 30만개의 배송을 소화했다. 배송 기사, 상점, 관리자의 트래픽을 받는 Public API는 분당 15만 요청을 처리하는 수준이었다. 연 4천억원 매출, 매출원가 90%를 제하면 10%가 마진, 백엔드 15명, 웹 3명, 안드로이드 3명, 닷넷 2명, QA 2명, PO 3명, 디자인 2명, 회사는 hy(한국야쿠르트)에 인수 등이 퇴사 시점의 스냅샷이다 - 현재는 조직, 사업, 서비스가 매우 안정적이란 얘기를 하고 싶었다. 더군다나 2022년 미국발 금융 위기로 바닥을 찍어 봤기에 이제 올라갈 일만 남았다.&lt;/p&gt;

&lt;p&gt;운 좋게 J-curve를 그리며 트래픽을 쳐맞는 성장을 경험했지만, 월급 외에 주식 보상에서는 오히려 운이 없었다. 배움과 성장의 비용이라 생각하고 잊어버렸다.&lt;/p&gt;

&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;이 포스트는 필자가 이해하는 Hexagonal과 DDD를 간략히 설명하는 것이 목적입니다. 이 포스트에 사용한 예제 코드는 &lt;a href=&quot;https://github.com/appkr/hexagonal-example&quot;&gt;https://github.com/appkr/hexagonal-example&lt;/a&gt; 에서 볼 수 있습니다. 이 포스트의 내용을 정답이 아닙니다, 다시 한번 강조드리지만 세상에 정답은 없습니다.&lt;/p&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;h2 id=&quot;hexagonal&quot;&gt;Hexagonal&lt;/h2&gt;

&lt;p&gt;Hexagonal의 핵심을 토비님이 설명해주셨습니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;헥사고날의 핵심은 가운데 헥사곤 안에 들어있는 애플리케이션과 외부 세상을 분리하는게 출발입니다. 헥사고날을 만든 Cockburn은 시스템을 사용하는 사용자와 시스템이 사용하는 네트워크, DB를 외부에 있는 것이라고 보고 이를 어댑터라고 모아 불렀습니다. &lt;a href=&quot;https://www.facebook.com/tobyilee/posts/10226233229911751?comment_id=1336598547048666&amp;amp;reply_comment_id=266120746391633&quot;&gt;🔗&lt;/a&gt;&lt;/p&gt;
  &lt;footer&gt;&lt;cite&gt;Toby Lee&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;주) 애플리케이션을 사용하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;driving adapter&lt;/code&gt;와 애플리케이션이 사용하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;driven adapter&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-hexagonal.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-hexagonal.png&quot; alt=&quot;hexagonal&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;UML의 기초는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has-a&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is-a&lt;/code&gt;입니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;has-a&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-&amp;gt;&lt;/code&gt;로 표시합니다 &lt;small class=&quot;text-muted&quot;&gt;이 문맥에서는 “사용한다” 라고 읽어도 좋습니다&lt;/small&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is-a&lt;/code&gt;는 속이 빈 화살표, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-|&amp;gt;&lt;/code&gt;로 표시합니다 &lt;small class=&quot;text-muted&quot;&gt;“구현한다” 라고 읽습니다&lt;/small&gt;. 이것만 알아도 대부분의 UML을 읽을 수 있습니다. 그림은 이렇게 읽을 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;① &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebAdapter&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputPort&lt;/code&gt;를 사용합니다&lt;/li&gt;
  &lt;li&gt;② &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputPort&lt;/code&gt;를 구현합니다&lt;/li&gt;
  &lt;li&gt;③ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Entity&lt;/code&gt;를 사용합니다&lt;/li&gt;
  &lt;li&gt;④ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OutputPort&lt;/code&gt;를 사용합니다&lt;/li&gt;
  &lt;li&gt;⑤ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PersistenceAdapter&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OutputPort&lt;/code&gt;를 구현합니다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이상의 기본 이해를 바탕으로 다음과 같은 폴더 구조를 도출했습니다. 이 구조는 정답은 아니고, 일례일 뿐입니다.&lt;/p&gt;

&lt;p&gt;아래 폴더 구조 및 네이밍은 그림과는 조금 다릅니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputPort&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProductUsecase&lt;/code&gt;라 불렀습니다&lt;/li&gt;
  &lt;li&gt;그림의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProductService&lt;/code&gt;라 불렀습니다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PersistenceAdapter&lt;/code&gt;는 구현 기술 명칭을 포함한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProductJpaRepository&lt;/code&gt;로 이름 지었습니다&lt;/li&gt;
  &lt;li&gt;그림에서 큰 바깥쪽 원을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application&lt;/code&gt;이란 패키지로, 안쪽의 작은 원을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domain&lt;/code&gt; 패키지로 명명했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;├── HexagonalExampleApplication
├── adapter
│   ├── inbound
│   │   └── rest
│   │       └── ProductController
│   └── outbound
│       ├── kafka
│       │   └── ProductMessageProducer
│       └── jpa
│           └── ProductJpaRepository
└── application
    ├── domain
    │   └── Product
    ├── port
    │   ├── inbound
    │   │   └── ProductUsecase
    │   └── outbound
    │       ├── MessagePort
    │       └── ProductRepository
    └── ProductService
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;서브 도메인이 여러 개인 모노리식 애플리케이션을 개발한다면 이런 구조로도 확장할 수 있을 겁니다.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;├── bootstrap
│   ├── CommerceApplication
│   └── config
├── order
│   ├── adapter
│   └── application
├── product
│   ├── adapter
│   └── application
├── delivery
│   ├── adapter
│   └── application
└── payment
    ├── adapter
    └── application
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;실무-사례&quot;&gt;실무 사례&lt;/h3&gt;

&lt;p&gt;폴더 구조만 보고 머릿 속에 그림이 잘 안그려질 수 있을 것 같아, 실무 사례를 가져왔습니다.&lt;/p&gt;

&lt;p&gt;왼쪽의 폴더 구조만 보고도 뭘하는 애플리케이션인지 알아차렸을겁니다. 총 네 가지의 유스케이스를 제공하는 서비스입니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;(1) 지오코드: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(주소) -&amp;gt; 좌표&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;(2) 역지오코드: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(좌표) -&amp;gt; 주소&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;(3) 주소 검색: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(검색어) -&amp;gt; 주소[]&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;(4) 주소 정제: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(더러운 주소) -&amp;gt; 깨끗한 주소&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-neogeo.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-neogeo.png&quot; alt=&quot;neogeo case&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;④ 이 프로젝트에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;domain&lt;/code&gt;을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application&lt;/code&gt;과 같은 폴더 레벨로 위치시켰습니다&lt;/li&gt;
  &lt;li&gt;③ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.in&lt;/code&gt;은 외부 세계의 요청을 받고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.out&lt;/code&gt;은 외부 세계에 요청을 하기 위한 인터페이스입니다&lt;/li&gt;
  &lt;li&gt;① &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.in.rest&lt;/code&gt;에는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@RestController&lt;/code&gt;가 있어서 HTTP 프로토콜로 클라이언트의 요청을 받아서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.in&lt;/code&gt;에 위치한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 구동&lt;small class=&quot;text-muted&quot;&gt;driving&lt;/small&gt;합니다; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.in.grpc&lt;/code&gt;에는 gRPC 서비스 구현체가 있어서 gRPC 프로토콜로 클라이언트의 요청을 받아서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 구동합니다&lt;/li&gt;
  &lt;li&gt;② &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;의 구현체인 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Service&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.out&lt;/code&gt;의 여러 인터페이스를 사용하고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.out&lt;/code&gt;의 구현체는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.out&lt;/code&gt;에 위치합니다. 여기서 의존성 역전이 발생합니다.
    &lt;ul&gt;
      &lt;li&gt;파일 저장을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws&lt;/code&gt; S3 저장 기능을 구현했습니다&lt;/li&gt;
      &lt;li&gt;주소 정제를 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;etpost&lt;/code&gt; 엔진과 TCP 통신을 합니다&lt;/li&gt;
      &lt;li&gt;영문 주소 번역을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;google&lt;/code&gt; 번역 서비스를 이용합니다&lt;/li&gt;
      &lt;li&gt;(역)지오코드 및 검색을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kakao&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;naver&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skt&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;routo&lt;/code&gt; 등과 통신하는 구현했습니다&lt;/li&gt;
      &lt;li&gt;주소 정제 실패 로그를 쌓고 슬랙 메시지를 보내는 리포팅 기능은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reporter&lt;/code&gt;에 구현했습니다&lt;/li&gt;
      &lt;li&gt;벌크 요청에 대해 비동기로 처리하고 처리 결과를 사용자에게 알리기 위해 이메일을 선택했고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vroong&lt;/code&gt;에 부릉의 공통 메일 전송 MSA와 통신하는 기능을 구현했습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;⑤ 우리 마음대로 제어할 수 없는 외부 서비스의 장애가 내부로 전파되는 것을 막기 위해 회로 차단기 패턴을 적용한 모습을 보여줍니다. 아울러, 외부 서비스로부터 받은 응답은 모두 부패방지계층 &lt;small class=&quot;text-muted&quot;&gt;ACL, Anti-corruption Layer&lt;/small&gt;을 적용하고 갑작스런 API 변경으로부터 우리 서비스를 보호하도록 완충 역할을 제공했습니다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-delivery-mechanism.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-delivery-mechanism.png&quot; alt=&quot;rest &amp;amp; grpc&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;위 그림의 왼쪽은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rest&lt;/code&gt;, 오른쪽은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grpc&lt;/code&gt;로 똑같은 지오코드 유스케이스를 호출하는 컨트롤러입니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;① 둘이 똑같은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 주입받아 사용합니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rest&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grpc&lt;/code&gt;를 통해 제출받은 데이터 형식은 서로 다를 수 있습니다. 그것을 컨트롤러에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;가 받을 수 있는 형식으로 치환합니다.&lt;/li&gt;
  &lt;li&gt;② 마찬가지로 응답 형식이 서로 다를 수 있습니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;로부터 받은 모델을 각 딜리버리 메커니즘에 맞도록 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Mapper&lt;/code&gt;가 맵핑합니다. 맵핑 로직은 기술 종속적이기때문에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter&lt;/code&gt;에서 처리하는 것이 적절하다고 판단했습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-redoc.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-redoc.png&quot; alt=&quot;redoc&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;이상의 복잡성은 모두 감추어져 있습니다. 클라이언트가 마주하는 API는 그림과 같은 모양입니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providers&lt;/code&gt; 쿼리 파람을 잘 보면, 배열로 제출합니다. 가령 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providers=KAKAO,NAVER,SKT&lt;/code&gt;와 같이 제출했다면, 카카오 지오코드가 실패하면(결과 값이 없거나, 장애로 인해 오류 응답을 받으면), 네이버로 폴백합니다. 네이버도 실패하면, SKT로 넘어갑니다. 각 서비스에서 지오코드라는 유스케이스를 위해 카카오, 네이버 등을 직접 연동하고 호출하던 것을 이 서비스가 추상화해서 공통 서비스로 제공하고 있습니다. 적용 우선 순위 또는 객체의 조립 공식을 클라이언트가 선택할 수 있도록 유연함도 제공하고 있을 뿐더러, 외부 서비스의 충격으로부터 우리 서비스를 보호하고 있습니다.&lt;/p&gt;

&lt;p&gt;이상한 용례이긴하지만, 서버에 CLI로 접속해서 이 애플리케이션을 이용해 지오코드를 결과를 얻을 수 있을까요? 물론이죠, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.in.cli&lt;/code&gt;를 구현하고, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 그대로 사용하면 됩니다. 스케쥴 태스크, 카프카 컨수머 등등이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.in&lt;/code&gt;에 위치하면 됩니다. 새로운 지오 서비스 프로바이더를 추가할 수 있을까요? &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;adapter.out.newgeoprovider&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;port.out&lt;/code&gt;의 스펙대로 구현하고 객체를 조립하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Factory&lt;/code&gt; 클래스의 switch 구문에 추가해주면 됩니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;필자는 실용 주의자입니다, 해서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt;를 사용하는 클래스의 테스트 불편함만 없다면 가끔 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Usecase&lt;/code&gt; 인터페이스를 생략하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Service&lt;/code&gt; 구체 구현체만 작성하기도 합니다&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;ddd&quot;&gt;DDD&lt;/h2&gt;

&lt;p&gt;DDD의 핵심은 소프트웨어의 복잡성 제어입니다. Evans의 책 제목에 정확히 그렇게 써 있습니다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;애플리케이션은 시간이 지나면서 필연적으로 복잡하지기 마련입니다. 복잡도가 올라가면 개발자의 뇌부하가 높아지고, 버그에 노출되기 쉬우며, 유지보수 비용이 증가하게 됩니다. 이런 문제점을 해결하는 방법 중의 하나가 DDD입니다. 범균님의 소프트웨어의 복잡성에 관한 &lt;a href=&quot;https://www.inflearn.com/course/lecture?courseSlug=%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9E%85%EB%AC%B8&amp;amp;unitId=13426&quot;&gt;인프런 객체지향 강의 섹션1(무료)&lt;/a&gt; 시청을 강력히 권장합니다.&lt;/p&gt;

&lt;p&gt;애플리케이션의 복잡성 정복에 DDD가 어떤 도움을 주는 지 필자 나름의 의식의 흐름대로 풀어 보겠습니다.&lt;/p&gt;

&lt;p&gt;과거에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clang&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perl&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classic PHP&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;classic ASP&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JSP&lt;/code&gt;로 웹 애플리케이션을 만들던 시절이 있었습니다 &lt;small class=&quot;text-muted&quot;&gt;&lt;a href=&quot;https://github.com/appkr/AMLite/blob/master/amadmin.cgi#L1211&quot;&gt;필자가 90년 말에 가지고 놀던 perl 코드&lt;/a&gt; &lt;a href=&quot;https://blog.appkr.dev/learn-n-think/history-lesson-on-web/&quot;&gt;clang으로 짠 웹&lt;/a&gt;&lt;/small&gt;. 고작 일천줄 수준인데 전혀 읽히지 않습니다. 일만줄 코드는 천재들이나 작성할 수 있다고 생각했습니다, 왜냐하면 일만줄의 작업 컨텍스트를 머릿속에 넣고 있어야 하니까요. 객체지향을 배우고 신세계를 경험했습니다. 클래스 내부의 데이터와 메서드, 협력하는 몇 개의 협력 클래스만 기억하면 천재 개발자가 아니어도 제법 큰 프로그램도 짤 수 있었으니까요. 게다가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RubyOnRails&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt;과 같은 MVC 패턴을 접하고는 생산성은 더 올라갔습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;클래스(메모리에 올라가면 “객체”, 이하 두 용어를 혼용해서 사용함)란 데이터와 그 데이터를 조작하는 함수를 캡슐화해 놓은 겁니다&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;그런데, 잘 생각해보자구요. 우리가 애플리케이션을 작성하면서 만나는 데이터, 즉 상태를 가진 클래스가 어떤 것이 있었죠? 컨트롤러, 서비스, 리포지토리 전부 상태가 없습니다. 상태가 없으니 서비스 컨테이너에 싱글톤으로 등록하고 필요할 때 꺼내써도 멀티 스레드 환경에서도 동시성 이슈가 생기지 않는겁니다. 이 녀석들이 멤버 필드로 다른 클래스를 가지고 있지만, 그 클래스들은 협력 클래스일 뿐이고, 앞서 언급한 싱글톤 클래스들은 메서드로 받은 원시타입 또는 DTO를 다른 협력 클래스의 메서드에 전달하는 역할만 하잖아요. 그렇게 따지고 보면 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DTO&lt;/code&gt;를 제외하고 데이터, 즉 상태를 가진 클래스는 도메인 객체가 전부입니다.&lt;/p&gt;

&lt;p&gt;(백엔드) 애플리케이션이란 결국 비즈니스 객체의 상태를 관리하는 것이고, 그 상태를 제어하는 창구를 한 곳으로 모아 두면 상태가 잘못 되는 일은 없지 않을까요? DDD에는 보편언어 &lt;small class=&quot;text-muted&quot;&gt;Ubiquitous Language&lt;/small&gt;, 바운디드 컨텍스트, 컨텍스트 맵과 같은 전략적 패턴을 주장하고 있지만, 실천적인 전술적 패턴을 많이 제공하고 있습니다.&lt;/p&gt;

&lt;p&gt;그 실천적 패턴들은 결국 도메인의 상태를 잘 관리하기 위한 것이고, 애그리거트 루트 &lt;small class=&quot;text-muted&quot;&gt;Aggregate Root&lt;/small&gt;에만 그 진입점을 만들라고 가이드합니다. 그리고 도메인에 대한 지식을 여기 저기 흩어 놓지 말고, 도메인 모델 안에 집약하라고 제언합니다. (모델이 뭐냐고 질문하시는 분이 있어서) DDD에서는 모델을 식별자가 있어 식별자로 객체간 동일성을 식별하는 1) 엔티티, 객체의 값으로 서로간의 동일성을 식별하는 2) 밸류로 구분합니다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23_evans.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23_evans.png&quot; alt=&quot;evans&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;그림에서 보듯이 꽤 많은 실천 패턴들을 제안하고 있는데, 제가 그럴 깜냥이 안되거니와, 이 포스트에서 전부 설명할 수는 없습니다. 책을 사서 보실 것을 추천합니다. 지금 당장 전부 볼 수는 없으니, 실무 사례를 통해 이해도를 높여 보려 합니다.&lt;/p&gt;

&lt;h3 id=&quot;실무-사례-1&quot;&gt;실무 사례&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-ddd-style-code.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-ddd-style-code.png&quot; alt=&quot;evans&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;클릭하면 큰 그림을 볼 수 있습니다&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;위에서 봤던 그림이랑 다른 주소 서비스입니다. 왼쪽이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Service&lt;/code&gt;, 오른쪽이 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AggregateRoot Entity&lt;/code&gt;(이하 “모델”이란 용어와 혼용합니다) 입니다. 새 주소를 등록하는 유스케이스입니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Service&lt;/code&gt;는 다음과 같은 일을 합니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;50줄&lt;/strong&gt;: 데이터베이스 트랜잭션을 관리합니다; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Transactional&lt;/code&gt; 애너테이션을 애플리케이션 안에서 사용하는 부분에 대한 의견이 갈릴 수 있습니다. 다시 한번 강조하지만 저는 실용주의자입니다, 갑자기 Quarkus 프레임웍으로 갈아탈 가능성은 없다고 봅니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;52줄&lt;/strong&gt;: 클라이언트가 제출한 DTO로부터 도메인 모델을 만듭니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;54줄&lt;/strong&gt;: 이미 등록된 주소인지 유효성 검사를 합니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;57줄&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DecoratingVisitor&lt;/code&gt;객체를 도메인 모델에게 전달하고, 전달받은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DecoratingVisitor&lt;/code&gt;를 구동하여 모델 자신의 상태를 변경하라고 모델에게 메시지를 전달합니다; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DecoratingVisitor&lt;/code&gt;는 현재 모델의 데이터 중, 꼭 필요하지만 비어있는 필드의 값을 주소 정제 엔진을 통해 구해와서 채워줍니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;59줄&lt;/strong&gt;: 데코레이팅하는 과정에서 주소 정제 엔진이 현재 모델이 가진 주소를 정제할 수 없는 경우에 에러 목록을 반환하고, 에러가 한 건이라도 있으면 예외를 던집니다; 도메인 모델의 상태에 대한 일종의 불변식이라 할 수 있습니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;62줄&lt;/strong&gt;: 도메인 모델의 상태를 영속화합니다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;66줄&lt;/strong&gt;: 어떤 필드가 어떤 값&lt;small class=&quot;text-muted&quot;&gt;before&lt;/small&gt;에서 어떤 값&lt;small class=&quot;text-muted&quot;&gt;after&lt;/small&gt;으로 변경되었는지를 애플리케이션 이벤트에 담아 변경 로그를 기록합니다&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;응용 서비스가 복잡하다면 응용 서비스에서 도메인 로직을 구현하고 있을 가능성이 높다. 응용 서비스가 도메인 로직을 일부 구현하면 코드 중복, 로직 분산 등 코드 품질에 안 좋은 영향을 줄 수 있다. 
응용 서비스는 트랜잭션 처리도 담당한다. 응용 서비스는 도메인의 상태 변경을 트랜잭션으로 처리해야 한다. (후략) &lt;a href=&quot;https://www.yes24.com/Product/Goods/108431347&quot;&gt;🔗&lt;/a&gt;&lt;/p&gt;
  &lt;footer&gt;&lt;cite&gt;도메인 주도 개발 시작하기 - DDD 핵심 개념 정리부터 구현까지 (2022, 최범균) p.203 발췌&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;위에서 언급한 응용 서비스에 도메인 로직을 구현하는 스타일을 &lt;a href=&quot;https://www.inflearn.com/questions/117315/%EB%B9%84%EC%A7%80%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81%EA%B5%AC%ED%98%84-entity-vs-service&quot;&gt;트랜잭션 스트립트 패턴&lt;/a&gt; 이라 합니다 &lt;small class=&quot;text-muted&quot;&gt;Patterns of Enterprise Application Architecture (2003, Martin Fowler)&lt;/small&gt;. 트랜잭션 스크립트 스타일이 구시대적이고 잘못됐다고 말하는 것은 아니니 오해없으시길 바랍니다.&lt;/p&gt;

&lt;p&gt;다만 도메인 모델에 도메인의 지식을 캡슐화하는 방식과 대별되는 방식 중 하나입니다. 트랜잭션 스크립트 스타일로 애플리케이션을 작성하면 도메인 모델은 표현력이 빈약해집니다 &lt;small class=&quot;text-muted&quot;&gt;&lt;a href=&quot;https://www.baeldung.com/java-anemic-vs-rich-domain-objects&quot;&gt;Anemic domain model&lt;/a&gt; &lt;/small&gt;. 도메인 모델이 데이터만 있고 로직은 없어서 DB의 컬럼을 수직으로 세워놓은 클래스가 될 가능성이 큽니다. 더군다나, 서비스에서 모델의 데이터 구조를 꺼내서 조작하고 다시 넣는 행위를 해야하므로 &lt;a href=&quot;https://github.com/GuidoDechamps/Paperboy#rules-violated&quot;&gt;Tell Don’t Ask&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_of_Demeter&quot;&gt;Law Of Demeter&lt;/a&gt; 설계 원칙을 위반하게 됩니다.&lt;/p&gt;

&lt;h2 id=&quot;test&quot;&gt;Test&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/images/2023-10-23-test.png&quot;&gt;&lt;img src=&quot;/images/2023-10-23-test.png&quot; alt=&quot;test&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;① &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddressUsecase&lt;/code&gt;가 테스트 대상입니다&lt;/li&gt;
  &lt;li&gt;② 테스트 대상이 의존하는 객체중 모의 객체로 대체할 대상입니다&lt;/li&gt;
  &lt;li&gt;③ 모의 객체의 동작을 정의합니다&lt;/li&gt;
  &lt;li&gt;④ 테스트하려는 메서드를 호출합니다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@SpringBootTest&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@MockBean&lt;/code&gt;을 쓰지 않고도 테스트를 작성할 수 있습니다만… 그러기 위해서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JpaRepository&lt;/code&gt;를 한번 더 감싼 클래스로 기존 구현을 전부 변경해야 하고, 테스트 컨텍스트에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Port&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Factory&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~Visitor&lt;/code&gt;를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Bean&lt;/code&gt;으로 등록하고 테스트 대상 클래스를 new up 해야 했을 겁니다. 이 지점에서도 역시 실용주의를 선택했습니다.&lt;/p&gt;

&lt;h2 id=&quot;closing-remarks&quot;&gt;Closing Remarks&lt;/h2&gt;

&lt;p&gt;필자는 닭 잡는데 소잡는 칼을 쓰는 것을 매우 경계합니다. 한번 쓰고 버리는 애플리케이션을 만든다면, 또는 일회성으로 남의 서비스를 개발한다면, 빠르게 개발하는 방법을 택할겁니다. 그렇지만 내가 계속 유지보수해야할 서비스라면 소프트웨어의 제 2가치를 추구할겁니다.&lt;/p&gt;

&lt;p&gt;소프트웨어의 제 1가치는 현재의 요구사항을 충족하는 것이고, 제 2가치는 미래의 요구사항을 충족하는 것입니다. 현재의 구현이 미래의 요구사항 변경을 반영하기 어려운 구조라면 좋은 소프트웨어라 할 수 없습니다. 가치있는 소프트웨어를 만드는 방법은 여러 가지인데, Hexagonal과 DDD도 괜찮은 선택지라 생각합니다.&lt;/p&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;

&lt;p&gt;참고하면 좋은 내용들입니다. 포스트 중간에 링크로 담은 내용은 제외했습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/&quot;&gt;DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://reflectoring.io/spring-hexagonal/&quot;&gt;Hexagonal Architecture with Java and Spring&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tynerblain.com/blog/2007/01/22/how-to-write-good-use-case-names/&quot;&gt;How to Write Good Use Case Names – 7 Tips&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://thedomaindrivendesign.io/what-is-strategic-design/&quot;&gt;What is Strategic Design ?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://thedomaindrivendesign.io/what-is-tactical-design/&quot;&gt;What is Tactical Design ?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6w7SQ_1aJ0A&quot;&gt;(NHN FORWARD 22) DDD 뭣이 중헌디?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 22 Oct 2023 09:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/learn-n-think/ddd-hexagonal/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/learn-n-think/ddd-hexagonal/</guid>
        
        <category>ddd</category>
        
        <category>architecture</category>
        
        
        <category>work-n-play</category>
        
        <category>learn-n-think</category>
        
      </item>
    
      <item>
        <title>라라벨 마이크로서비스 예제 3부</title>
        <description>&lt;p&gt;모노리틱 서비스 구조에서 마이크로 서비스 구조로 전환할 때 사용자 인증을 어떻게 통합할지에 대한 내용을 계속 이어갑니다. 이번 포스트에서는 아래 그림처럼 한 마이크로 서비스가 다른 마이크로 서비스를 호출하는 경우를 살펴볼 겁니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-17-uaa.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;UAA 연동 시나리오&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;전체 예제 코드는 &lt;a href=&quot;https://github.com/appkr/laravel-msa-example&quot;&gt;https://github.com/appkr/laravel-msa-example&lt;/a&gt;에 있습니다.&lt;/p&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;div class=&quot;panel panel-default&quot; style=&quot;width:100%; margin: auto;&quot;&gt;
  &lt;div class=&quot;panel-body text-center&quot;&gt;
     &lt;a&gt;&lt;i class=&quot;material-icons&quot;&gt;info&lt;/i&gt; 이하 본문에서 줄번호가 있는 코드 박스는 모바일 뷰에서 깨집니다. 데스크탑 브라우저를 권장합니다.&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;6-구현3-clientcredentials-그랜트&quot;&gt;6 구현#3 ClientCredentials 그랜트&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloAPI&lt;/code&gt;를 직접 사용할 수 있습니다. 또는 라라벨 서비스의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GuzzleClient&lt;/code&gt;가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloAPI&lt;/code&gt;를 사용할 수도 있습니다. 이 예제에서는 후자만 다루긴합니다만, 여튼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExampleAPI&lt;/code&gt; 뿐만아니라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloAPI&lt;/code&gt;도 보호해야 합니다. 이때 서버간 통신을 하게되는데, 사용할 그랜트가 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientCredentials&lt;/code&gt;인거죠.&lt;/p&gt;

&lt;h3 id=&quot;6-1-hello-proxy&quot;&gt;6-1 Hello Proxy&lt;/h3&gt;

&lt;p&gt;Hello 리소스는 라라벨 서비스의 도메인이 아닙니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;가 직접 접근할 수 있음에도 불구하고, 편의를 위해 라라벨 서비스가 게이트웨이 역할을 대신해주는 거죠. 이런 이유로 프록시라는 용어를 썼습니다.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// routes/api.php&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenAuthenticate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HelloController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Http/Controllers/HelloController.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;App\Http\Controllers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\ExternalApi\HelloApiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Http\JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HelloController&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Controller&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$helloApiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;HelloApiClient&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$helloApiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;helloApiClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$helloApiClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JsonResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;helloApiClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// src/Infra/ExternalApi/HelloApiClient.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra\ExternalApi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Psr\Http\Client\ClientExceptionInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HelloApiClient&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;GuzzleClient&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;TokenProvider&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenProvider&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;http://localhost:8080/hello&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;Authorization&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bearer &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sendRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientExceptionInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;17&lt;/code&gt; line: 스프링 서비스(==&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HelloAPI&lt;/code&gt;)와 통신하기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GuzzleClient&lt;/code&gt;를 주입 받는다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;18&lt;/code&gt; line: Hello 리소스를 요청할 때 스프링 서비스에 제출할 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 얻기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenProvider&lt;/code&gt;를 주입받는다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;27&lt;/code&gt; line: Authorization 요청 헤더를 셋팅한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;31&lt;/code&gt; line: HelloAPI를 호출한다&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;6-2-uaatokenprovider&quot;&gt;6-2 UaaTokenProvider&lt;/h3&gt;

&lt;p&gt;간결함을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenProvider&lt;/code&gt; 인터페이스는 생략합니다. &lt;a href=&quot;https://github.com/appkr/laravel-msa-example/blob/master/src/Infra/JhipsterUaa/TokenResponse.php&quot;&gt;TokenResponse도 생략하니 예제 코드를 참고&lt;/a&gt;바랍니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// src/Infra/JhipsterUaa/UaaTokenProvider.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra\JhipsterUaa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\Arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Psr\Http\Client\ClientExceptionInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UaaTokenProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;GuzzleClient&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;TokenKeyProvider&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenKeyProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getTokenResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TokenResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;access_token_path&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;application/x-www-form-urlencoded&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;Authorization&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;basic &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getBasicAuthHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Query&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
            &lt;span class=&quot;s1&quot;&gt;&apos;grant_type&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;client_credentials&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;]));&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sendRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientExceptionInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromJsonString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; 
            &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getBasicAuthHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$clientId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;client_id&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$clientSecret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;client_secret&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;base64_encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$clientId&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$clientSecret&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;23&lt;/code&gt; line: UAA 서버에서 받은 토큰을 파싱하기 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt;를 주입 받는다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;45&lt;/code&gt; line: UAA 서버에 토큰을 요청한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;49-52&lt;/code&gt; line: UAA 서버에서 받은 응답 파싱을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenResponse&lt;/code&gt; 모델에게 위임한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55-60&lt;/code&gt; line: basic 인증 스킴으로 클라이언트 정보를 제출한다(이렇게 제출할 수도 있다 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://internal:internal@localhost:9999/oauth/token&lt;/code&gt;). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base64_encode(&quot;internal:internal&quot;)&lt;/code&gt;의 결과값은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aW50ZXJuYWw6aW50ZXJuYWw=&lt;/code&gt;이다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;서비스 프로바이더도 수정해야 합니다.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Providers/OAuth2ServiceProvider.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;App\Providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\JhipsterUaa\UaaTokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Config\Repository&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConfigRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Foundation\Application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\Arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\ServiceProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2ServiceProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServiceProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerTokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerTokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 생략 */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* 생략 */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ConfigRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;oauth2.jhipster&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;timeout&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UaaTokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;현재까지의 구현을 클래스 다이어그램으로 살펴볼까요?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-17-uml1.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;구현 중간 점검&lt;/small&gt;&lt;/div&gt;

&lt;h3 id=&quot;6-3-cacheabletokenprovider&quot;&gt;6-3 CacheableTokenProvider&lt;/h3&gt;

&lt;p&gt;HelloAPI를 요청할 때마다 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientCredentials&lt;/code&gt; 그랜트로 토큰을 얻는 것은 네트워크 IO가 매번 발생해서 비효율적입니다. 2부에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt;에서 얻은 공개키를 캐시하고 사용했듯이, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenProvider&lt;/code&gt;도 똑같이 구현했습니다. 다만 2부에서 24시간 동안 캐시했다면, 여기서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt; 클레임까지만 캐시를 유지하도록 구현합니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra\JhipsterUaa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Carbon\Carbon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Cache\Repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheableTokenProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CACHE_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;oauth2.token&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TokenProvider&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Repository&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getAccessToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getTokenResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TokenResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rememberForever&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CACHE_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;이번에도 서비스 프로바이더는 수정해야 합니다만, 간결함을 위해 생략했으니, &lt;a href=&quot;https://github.com/appkr/laravel-msa-example/blob/master/app/Providers/OAuth2ServiceProvider.php&quot;&gt;예제 코드를 참고&lt;/a&gt;바랍니다.&lt;/p&gt;

&lt;h2 id=&quot;7-정리&quot;&gt;7 정리&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;마이크로 서비스를 구성할 때 가장 먼저 고려해야할 모듈은 사용자 인증(&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UAA&lt;/code&gt;)이다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UAA&lt;/code&gt;에서 얻은 토큰을 제출함으로써 마이크로 서비스의 보호된 리소스를 사용할 수 있다&lt;/li&gt;
  &lt;li&gt;마이크로 서비스는 클라이언트가 제출한 토큰을 검증하여 유효할 때만 요청을 처리한다&lt;/li&gt;
  &lt;li&gt;JWT를 토큰으로 사용하면 여러 가지 면에서 편리하다&lt;/li&gt;
  &lt;li&gt;마이크로 서비스끼리 통신하는 경우에도 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UAA&lt;/code&gt;로 부터 얻은 토큰을 제출해야 하며, 토큰의 유효성을 검증하고, 유효할 때만 요청을 처리한다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;끝.&lt;/p&gt;
</description>
        <pubDate>Wed, 17 Feb 2021 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/laravel-msa-example-part-3/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/laravel-msa-example-part-3/</guid>
        
        <category>PHP</category>
        
        <category>Laravel</category>
        
        <category>MSA</category>
        
        <category>Oauth2</category>
        
        
        <category>work-n-play</category>
        
      </item>
    
      <item>
        <title>라라벨 마이크로서비스 예제 2부</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://blog.appkr.dev/work-n-play/laravel-msa-example/&quot;&gt;지난 포스트&lt;/a&gt;에 이어 모노리틱 서비스 구조에서 마이크로 서비스 구조로 전환할 때 사용자 인증을 어떻게 통합할지에 대한 내용을 이어갑니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-15-uml2.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;UAA 연동: HttpMiddleware를 이용한 Password 그랜트&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;전체 예제 코드는 &lt;a href=&quot;https://github.com/appkr/laravel-msa-example&quot;&gt;https://github.com/appkr/laravel-msa-example&lt;/a&gt;에 있습니다.&lt;/p&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;div class=&quot;panel panel-default&quot; style=&quot;width:100%; margin: auto;&quot;&gt;
  &lt;div class=&quot;panel-body text-center&quot;&gt;
     &lt;a&gt;&lt;i class=&quot;material-icons&quot;&gt;info&lt;/i&gt; 이하 본문에서 줄번호가 있는 코드 박스는 모바일 뷰에서 깨집니다. 데스크탑 브라우저를 권장합니다.&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;4-구현1&quot;&gt;4 구현#1&lt;/h2&gt;

&lt;h3 id=&quot;4-1-프로젝트-셋업&quot;&gt;4-1 프로젝트 셋업&lt;/h3&gt;

&lt;p&gt;라라벨 프로젝트를 새로 만듭니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;laravel new laravel-msa-example
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;php artisan &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Laravel Framework 8.21.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;예제 소스 코드에는 PHP7.3, Nginx, MySQL 등의 런타임 환경을 docker-compose로 구동하도록 했습니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;tree docker
&lt;span class=&quot;c&quot;&gt;# docker&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ├── custom-php.ini      # xdebug, opcache 등 설정&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ├── docker-compose.yml&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ├── init.sql            # (최초 실행시 자동) 데이터베이스 생성&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# └── laravel.conf        # Nginx 설정&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;최종 폴더 구조는 이렇습니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
├── app                                 &lt;span class=&quot;c&quot;&gt;# 라라벨 제공 폴더&lt;/span&gt;
├── hello-service                       &lt;span class=&quot;c&quot;&gt;# HelloAPI (Spring, non-laravel)&lt;/span&gt;
├── src
│ ├── Infra                             &lt;span class=&quot;c&quot;&gt;# 인프라 레이어&lt;/span&gt;
│ │ ├── ExternalApi
│ │ │ └── HelloApiClient.php            &lt;span class=&quot;c&quot;&gt;# HelloAPI&lt;/span&gt;
│ │ ├── JhipsterUaa                     &lt;span class=&quot;c&quot;&gt;# UAA 연동 코드&lt;/span&gt;
│ │ │ ├── CacheableTokenKeyProvider.php
│ │ │ ├── CacheableTokenProvider.php
│ │ │ ├── TokenKey.php
│ │ │ ├── TokenKeyResponse.php
│ │ │ ├── TokenResponse.php
│ │ │ ├── UaaTokenKeyProvider.php
│ │ │ └── UaaTokenProvider.php
│ │ ├── Token.php                       &lt;span class=&quot;c&quot;&gt;# 토큰 모델&lt;/span&gt;
│ │ ├── TokenException.php
│ │ ├── TokenExtractor.php              &lt;span class=&quot;c&quot;&gt;# Authorization 요청 헤더에서 토큰 추출하는 유틸 &lt;/span&gt;
│ │ ├── TokenKeyProvider.php            &lt;span class=&quot;c&quot;&gt;# 공개 키를 조회하기 위한 인터페이스&lt;/span&gt;
│ │ ├── TokenParser.php                 &lt;span class=&quot;c&quot;&gt;# 토큰 유효성 검증기&lt;/span&gt;
│ │ └── TokenProvider.php               &lt;span class=&quot;c&quot;&gt;# (OAuth2 서버로부터) 토큰을 얻기 위한 인터페이스&lt;/span&gt;
│ ├── Model
│ │ └── Example.php                     &lt;span class=&quot;c&quot;&gt;# Example 도메인&lt;/span&gt;
│ └── Service
│     ├── Dto
│     │ ├── ExampleDto.php
│     │ ├── ExampleList.php
│     │ ├── ExampleSearchParam.php
│     │ └── Page.php
│     ├── ExampleService.php
│     └── Mapper
│         └── ExampleMapper.php
└── tests
  ├── Infra
  │ ├── JWTTest.php
  │ ├── TokenExtractorTest.php
  │ ├── TokenKeyProviderTest.php
  │ ├── TokenParserTest.php
  │ └── TokenProviderTest.php
  └── Unit
      └── ExampleTest.php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;전체 클러스터를 구동하고 작동 여부를 검증합니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker/docker-compose.yml up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;open http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;UAA는 별도로 작동시키고 작동을 검증합니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 최초 한번만 실행&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget https://github.com/appkr/msa-starter/raw/master/jhipster-uaa.zip &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; unzip jhipster-uaa.zip 
  &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;jhipster-uaa

&lt;span class=&quot;c&quot;&gt;# 구동할 때마다 실행; 중지하려면 Ctrl + c&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./gradlew clean bootRun
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; http://localhost:9999/management/health
&lt;span class=&quot;c&quot;&gt;# { &quot;status&quot; : &quot;UP&quot; }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-2-exampleapi&quot;&gt;4-2 ExampleAPI&lt;/h3&gt;

&lt;p&gt;Example 리소스에 대해 등록, 목록 조회 두 개의 API만 구현했습니다; 스키마 마이그레이션, 시더, 모델, 폼 리퀘스트, 서비스 레이어 등 기타 코드는 생략합니다.&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// routes/api.php&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;examples&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExampleController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;createExample&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExampleController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;listExamples&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Http/Controllers/ExampleController.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;App\Http\Controllers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;App\Http\Requests\CreateExampleRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;App\Http\Requests\ListExampleRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Service\ExampleService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Http\JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExampleController&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Controller&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ExampleService&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exampleService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$exampleService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createExample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;CreateExampleRequest&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JsonResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$dto&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createExample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toDto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listExamples&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ListExampleRequest&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;JsonResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;listExamples&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toDto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;작동을 검증합니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept:application/json&apos;&lt;/span&gt; http://localhost:8000/api/examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;이문세 5집&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalElements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalPages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;5-구현2-password-그랜트&quot;&gt;5 구현#2 Password 그랜트&lt;/h2&gt;

&lt;h3 id=&quot;5-1-httpmiddleware&quot;&gt;5-1 HttpMiddleware&lt;/h3&gt;

&lt;p&gt;현재는 엔드포인트만 알고 있다면 아무나 ExampleAPI를 사용할 수 있는 상태입니다. Authorization HTTP 헤더에 UAA에서 받은 토큰을 제출했을 때만, Example 리소스를 사용할 수 있도록 HttpMiddleware를 구현하고 적용해 보겠습니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Http/Middleware/TokenAuthenticate.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;AppP\Http\Middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;App\Models\User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenExtractor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Closure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Http\JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Ramsey\Uuid\Uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenAuthenticate&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TokenParser&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenParser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Closure&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenExtractor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;extract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tokenNotProvided&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenParser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;message&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),],&lt;/span&gt; 
                &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getStatusCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;\Exception&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;JsonResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;message&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Unknown exception&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,],&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setUserResolver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getUserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
                &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getUserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; 
                &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Uuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Uuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;NIL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;26&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenExtractor&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; 객체에서 JWT &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 추출하는 역할을 한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;28&lt;/code&gt; line: 클라이언트가 토큰을 제출하지 않았다면, 예외가 발생한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenParser::parse&lt;/code&gt; 메서드는 JWT 토큰을 받아서 파싱하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Token&lt;/code&gt;모델을 반환하는 역할을 한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32&lt;/code&gt; line: 이상의 과정에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenException&lt;/code&gt;이 발생할 수 있으며, 이때는 사용자에게 4xx 응답코드와 예외 메시지를 응답한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;40&lt;/code&gt; line: 이상의 과정이 순조롭게 진행되었다면, UserResolver 클로저를 등록한다; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;43&lt;/code&gt;줄을 보면 파싱된 토큰에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;userName: UUIDInterface&lt;/code&gt; 클레임 값을 조회하고 사용하는 모습을 볼 수 있다&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// routes/api.php&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;Route&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;examples&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenAuthenticate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 생략 ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;작동을 검증합니다. 필요한 클래스들이 아직 없어서, 예외가 발생할텐데요. 예외를 발생시키지 않도록 최소한의 클래스와 메서드만 선언하고 테스트한다면 아래와 같은 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept:application/json&apos;&lt;/span&gt; http://localhost:8000/api/examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;400&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;Bad Request&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;X-RateLimit-Limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;60&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;X-RateLimit-Remaining&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;59&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Token was not provided&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-2-tokenextractor&quot;&gt;5-2 TokenExtractor&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Request&lt;/code&gt; 객체에서 JWT &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 추출하는 책임을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenExtractor&lt;/code&gt;에게 부여했습니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// src/Infra/TokenExtractor.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenExtractor&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Request&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;?string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;hasHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;authorization&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$authHeader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;authorization&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;preg_replace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/bearer\s/i&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$authHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;16&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorization&lt;/code&gt; 요청 헤더를 구한후, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bearer &lt;/code&gt; 스킴 문자열을 버리는 코드입니다&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-3-tokenparser&quot;&gt;5-3 TokenParser&lt;/h3&gt;

&lt;p&gt;1부 3절에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;firebase/php-jwt&lt;/code&gt; 패키지를 설치해습니다. &lt;a href=&quot;https://github.com/firebase/php-jwt#example-with-rs256-openssl&quot;&gt;공개키를 이용하여 JWT를 파싱하는 방법은 링크를 참고&lt;/a&gt;합니다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenParser&lt;/code&gt;는 원본 JWT 토큰을 받아서 파싱하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Token&lt;/code&gt;모델을 반환하는 책임을 부여했습니다. &lt;a href=&quot;https://github.com/appkr/laravel-msa-example/blob/master/src/Infra/Token.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Token&lt;/code&gt; 모델은 예제 코드를 참고&lt;/a&gt;합니다&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// src/Infra/TokenExtractor.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Firebase\JWT\BeforeValidException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Firebase\JWT\ExpiredException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Firebase\JWT\SignatureInvalidException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenParser&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TokenKeyProvider&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenKeyProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Token&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromTokenString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;$jwtString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;SignatureInvalidException&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;invalidSignature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;BeforeValidException&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;beforeValid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExpiredException&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expired&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UnexpectedValueException&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;invalidToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;15&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt; 주입한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;22&lt;/code&gt; line: 토큰을 파싱 및 객체 생성 책임을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Token&lt;/code&gt; 클래스에 위임함; 원본 토큰 문자열과 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt;로 조회한 공개키를 인자로 전달한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;26-34&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;firebase/php-jwt&lt;/code&gt; 패키지의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\Firebase\JWT\JWT::decode&lt;/code&gt; 메서드에서 던진 예외를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenException&lt;/code&gt;으로 치환한다&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;예외&lt;/th&gt;
      &lt;th&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SignatureInvalidException&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;공개 키가 유효하지 않을 때&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BeforeValidException&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iat&lt;/code&gt;(Issued at) 값보다 현재 시각이 과거일 때&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExpiredException&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt;(Expiration time) 값보다 현재 시각이 미래일 때&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UnexpectedValueException&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;토큰이 변조되었거나 유효하지 않을 때&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;현재까지의 구현을 클래스 다이어그램으로 살펴볼까요?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-15-uml1.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;구현 중간 점검&lt;/small&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-4-tokenkeyprovider&quot;&gt;5-4 TokenKeyProvider&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt;는 UAA 서버로부터 공개 키를 받아오는 역할을 합니다. 간결함을 위해 인터페이스 구현, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/oauth2.php&lt;/code&gt; 설정 등의 내용은 생략합니다. &lt;a href=&quot;https://github.com/appkr/laravel-msa-example/blob/master/src/Infra/JhipsterUaa/TokenKeyResponse.php&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyResponse&lt;/code&gt; 모델도 생략하니 예제 코드를 참고&lt;/a&gt;바랍니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra\JhipsterUaa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Psr7\Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\Arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Psr\Http\Client\ClientExceptionInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UaaTokenKeyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;GuzzleClient&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getTokenKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TokenKeyResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;GET&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;token_key_path&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sendRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientExceptionInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenKeyResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromJsonString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30&lt;/code&gt; line: == &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new Request(&apos;GET&apos;, &apos;/oauth/token_key&apos;));&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;33&lt;/code&gt; line: UAA에 요청하고 받은 응답을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$response&lt;/code&gt; 변수에 담았다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;37&lt;/code&gt; line: 응답을 파싱하는 책임을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyResponse&lt;/code&gt;에 위임했다&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-5-oauth2serviceprovider&quot;&gt;5-5 OAuth2ServiceProvider&lt;/h3&gt;

&lt;p&gt;런타임에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenParser&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt; 등의 객체를 정상적으로 주입하기 위해서는 서비스 프로바이더에 객체 조립 공식을 등록해야 합니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Providers/OAuth2ServiceProvider.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;App\Providers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\JhipsterUaa\UaaTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleHttp\Client&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Config\Repository&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConfigRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Foundation\Application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\Arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Support\ServiceProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2ServiceProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServiceProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;registerTokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ConfigRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;oauth2.jhipster&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;timeout&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UaaTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenParser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenParser&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;   
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;30&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UaaTokenKeyProvider&lt;/code&gt; 생성자가 요구하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GuzzleClient,&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config: string[]&lt;/code&gt;을 주입했다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;37&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenParser&lt;/code&gt; 생성자가 요구하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt;를 주입했다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;뿐만아니라 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/app.php&lt;/code&gt;의 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;providers&lt;/code&gt; 배열에 방금 만든 서비스 프로바이더를 등록해줘야 합니다. 오랜만에 하는 라라벨 프로젝트라 이 부분을 놓치고 삽질을 했는데요, &lt;a href=&quot;https://modernpug.slack.com/archives/C0MGYSQ0L/p1612766800053200&quot;&gt;슬랙에서 여러 분들께서 같이 봐주셔서 해결&lt;/a&gt;했습니다.&lt;/p&gt;

&lt;p&gt;작동하는지 검증해볼까요? UAA를 포함한 전체 클러스터가 작동하고 있다고 가정합니다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ RESPONSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;--data&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;username=user&amp;amp;password=user&amp;amp;grant_type=password&amp;amp;scope=openid&quot;&lt;/span&gt; http://web_app:changeit@localhost:9999/oauth/token&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$RESPONSE&lt;/span&gt; | jq .access_token | xargs&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept:application/json&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: bearer &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; http://localhost:8000/api/examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1.1&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;ne&quot;&gt;OK&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/json&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;X-RateLimit-Limit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;60&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;X-RateLimit-Remaining&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;59&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;*&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:[{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\u&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;c774&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\u&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;bb38&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\u&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;c138 5&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\u&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;c9d1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalElements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalPages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;5-5-cacheabletokenkeyprovider&quot;&gt;5-5 CacheableTokenKeyProvider&lt;/h3&gt;

&lt;p&gt;현재까지 구현은 공개 키가 필요할 때마다 매번 UAA에 HTTP 요청을 해야 합니다. 한번 받은 공개 키는 24시간 동안 캐시에 저장하고 꺼내쓸 수 있도록 구현을 변경했습니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// src/Infra/JhipsterUaa/CacheableTokenKeyProvider.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Appkr\Infra\JhipsterUaa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Cache\Repository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheableTokenKeyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CACHE_KEY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;oauth2.token_key&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CACHE_TTL_SECONDS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 24시간&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;TokenKeyProvider&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;kt&quot;&gt;Repository&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$delegate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getTokenKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;TokenKeyResponse&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cacheRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;remember&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CACHE_KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CACHE_TTL_SECONDS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getTokenKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$delegate: UaaTokenKeyProvider&lt;/code&gt;로부터 받은 응답을 24시간 동안 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;oauth2.token_key&lt;/code&gt;라는 캐시 키의 값으로 저장한다&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;36&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$delegate: UaaTokenKeyProvider&lt;/code&gt;에게 위임한다; 데코레이터 패턴을 적용함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;데코레이터가 정상 작동할 수 있도록 서비스 프로바이더에 객체 조립 공식을 변경 등록해야 합니다.&lt;/p&gt;

&lt;div class=&quot;language-php linenos highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// app/Providers/OAuth2ServiceProvider.php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Appkr\Infra\JhipsterUaa\CacheableTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Illuminate\Contracts\Cache\Repository&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 생략 ...&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2ServiceProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServiceProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 생략 ...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;registerTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenKeyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ConfigRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;oauth2.jhipster&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GuzzleClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;base_uri&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;timeout&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$innerProvider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UaaTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$httpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$app&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CacheRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CacheableTokenKeyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$innerProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$cacheRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;25&lt;/code&gt; line: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CacheableTokenKeyProvider&lt;/code&gt; 생성자가 필요로하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TokenKeyProvider&lt;/code&gt; 타입으로 기존에 사용하던 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UaaTokenKeyProvider&lt;/code&gt;를 주입하고, 캐시 저장소를 사용할 수 있도록 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CacheRepository&lt;/code&gt;를 주입했다&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Password 그랜트가 완성됐습니다. 클래스 다이어그램을 살펴볼까요?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-15-uml2.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;UAA 연동: HttpMiddleware를 이용한 Password 그랜트&lt;/small&gt;&lt;/div&gt;
&lt;!--
## 6 구현#3 ClientCredentials 그랜트 

### 6-1 TokenProvider 구현
TBD

## 7 요약
--&gt;
&lt;p&gt;3부에서 이어집니다&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Feb 2021 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/laravel-msa-example-part-2/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/laravel-msa-example-part-2/</guid>
        
        <category>PHP</category>
        
        <category>Laravel</category>
        
        <category>MSA</category>
        
        <category>Oauth2</category>
        
        
        <category>work-n-play</category>
        
      </item>
    
      <item>
        <title>라라벨 마이크로서비스 예제</title>
        <description>&lt;p&gt;모노리틱&lt;small class=&quot;text-muted&quot;&gt;monolithic&lt;/small&gt; 서비스 구조에서 마이크로 서비스 구조로 전환할 때 가장 먼저 고려해야할 모듈은 사용자 인증입니다, 아래 그림처럼 말이죠.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-13-monolithic.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;모노리틱 서비스 구조&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-13-microservice.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;마이크로 서비스 구조&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;이 포스트에서는 라라벨 프레임워크로 마이크로 서비스를 만들때 UAA&lt;small class=&quot;text-muted&quot;&gt;User Account and Authentication&lt;/small&gt; 서버와 연동하는 사례를 설명합니다. 기존의 모노리틱 서비스를 마이크로 서비스로 이전하는 방법은 설명하지 않습니다. 그리고, UAA는 &lt;a href=&quot;https://www.jhipster.tech/using-uaa/&quot;&gt;JhipsterUAA&lt;/a&gt;를 이용합니다; &lt;a href=&quot;https://www.keycloak.org/&quot;&gt;Keycloak&lt;/a&gt;, &lt;a href=&quot;https://laravel.com/docs/8.x/passport&quot;&gt;Laravel Passport&lt;/a&gt; 등의 OAuth2 서비스를 사용해도 유사한 방법으로 연동할 수 있습니다.&lt;/p&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;h2 id=&quot;1-큰-그림&quot;&gt;1 큰 그림&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-13-microservice2.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;마이크로 서비스 콤포넌트&lt;/small&gt;&lt;/div&gt;

&lt;h3 id=&quot;용어&quot;&gt;용어&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;인증 서버: OAuth2 서버와 같은 의미로 사용함&lt;/li&gt;
  &lt;li&gt;리소스 서버: 마이크로 서비스와 같은 의미로 사용함&lt;/li&gt;
  &lt;li&gt;리소스: REST 컨텍스트에서 도메인을 표현하는 방법&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;각-콤포넌트-설명&quot;&gt;각 콤포넌트 설명&lt;/h3&gt;

&lt;h4 id=&quot;client&quot;&gt;Client&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;curl, postman, 웹/모바일 애플리케이션, 서버의 HTTP Client(e.g. Guzzle, RestTemplate)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;uaa&quot;&gt;UAA&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jhipster.tech/using-uaa/&quot;&gt;JhipsterUAA&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Port 9999&lt;/li&gt;
  &lt;li&gt;단순함을 위해 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authority&lt;/code&gt; 등의 서브 도메인은 위 그림에서 생략함&lt;/li&gt;
  &lt;li&gt;단순함을 위해 인증을 위한 엔드포인트 外의 리소스 엔드포인트는 아래 표에서 생략함&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;엔드포인트&lt;/th&gt;
      &lt;th&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /oauth/token&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;토큰 획득 및 갱신&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /oauth/token_key&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;토큰 인코드, 디코드를 위한 공개키 조회&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;그랜트&lt;/th&gt;
      &lt;th&gt;client_id&lt;/th&gt;
      &lt;th&gt;client_secret&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Password&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web_app&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changeit&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ClientCredentials&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;JhipsterUAA는 아래 명령으로 설치하고 구동할 수 있습니다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget https://github.com/appkr/msa-starter/raw/master/jhipster-uaa.zip
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;unzip jhipster-uaa.zip &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;jhipster-uaa &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew clean bootRun
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;laravel&quot;&gt;Laravel&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Example 도메인&lt;/li&gt;
  &lt;li&gt;인증/인가된 사용자만 ExampleAPI 사용 가능&lt;/li&gt;
  &lt;li&gt;Port 8000&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예제 소스코드를 복제했다면 아래 명령으로 Laravel 서비스를 구동할 수 있습니다&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker/docker-compose.yml up
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;open http://localhost:8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;spring&quot;&gt;Spring&lt;/h4&gt;
&lt;ul&gt;
  &lt;li&gt;Hello 도메인&lt;/li&gt;
  &lt;li&gt;인증/인가된 사용자만 HelloAPI 사용 가능&lt;/li&gt;
  &lt;li&gt;Port 8080&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예제 소스코드를 복제했다면 아래 명령으로 Spring 서비스를 구동할 수 있습니다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;hello-service &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./gradlew clean bootRun
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;큰 그림을 요약하고, 전체 작동 플로우를 정리하면 아래 시퀀스 다이어그램과 같습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-13-microservice3.svg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;마이크로 서비스 시퀀스&lt;/small&gt;&lt;/div&gt;

&lt;h3 id=&quot;-로그인-및-토큰-획득&quot;&gt;① 로그인 및 토큰 획득&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web_app&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changeit&lt;/code&gt; 사용자 계정과 Password 그랜트를 이용하여 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UAA&lt;/code&gt;로부터 JWT&lt;small class=&quot;text-muted&quot;&gt;Json Web Token&lt;/small&gt;를 발급 받는다
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ RESPONSE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;s1&quot;&gt;&apos;http://localhost:9999/oauth/token&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Content-Type: application/x-www-form-urlencoded&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept: application/json&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Authorization: Basic  d2ViX2FwcDpjaGFuZ2VpdA==&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--data-urlencode&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;grant_type=password&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--data-urlencode&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;username=user&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--data-urlencode&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;password=user&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--data-urlencode&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;scope=openid&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;access_token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsib3BlbmlkIl0sImV4cCI6MTYxMjQ5ODAyMywiaWF0IjoxNjEyNDk3NzIzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiREJmM0g0NHRlVFhOVmNEWjBBeWJTQUY3dFBZIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.Y4py8MMHJdMwbXxpocdQPrMEevRnl2nkEUuXw6UeRmtB7qWDFADmzO-xQz8Hkvmz2LT0U4gPSHvRSOEWGuZ_Nack8MRU7ICWGRl53WZyiPTzt7ucLcO0w1eOBUPFxsHQHffyZ4XzrDJWlqWadhzlnxw9oJUcvi8aAKkBtBSVOCslxYMkBNzs7vjxbLHNQIAjZbb1YetBzpAQOq8RJCdSQNDcMSZ9eZG705ucaFBv3LgDf5sxK47Yqqqk3oCMDdyMXB2MW2bsFivpf6BZd3ydfHrCgcrU5y2Vl2g6fG6PsADkaJ4Fv31UdVj0QG-kX8fgj9GL7MjZUyI-bWruiLNTgw&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;token_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bearer&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;refresh_token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsib3BlbmlkIl0sImF0aSI6IkRCZjNINDR0ZVRYTlZjRFowQXliU0FGN3RQWSIsImV4cCI6MTYxMzEwMjUyMywiaWF0IjoxNjEyNDk3NzIzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoicDhWQVB5aWhHU3NGYXlma1pMWnd1MURLSHBrIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.gD4uoRwk_kbX19uUBxnUdvmu3KDZBrTLWeY0h6lj-UVvhKaUF7BNH1AzwSfX10y-tYjdfElk7_m7x6Vo9gG93HMUPtnSQPPNknI6KcE4sFRRs-Wk15o74S7ukiSjvdtq9Bx2u7t6EEd3e1UHHcRYLFwxJUND53YRM7VR38QWhJgxGW6aA6EK4Rz7fgqFaylK8xWlFjhYoFl3w_VBErqDZWyprKRl9IDDrd6xCo-5RIauuLGGvMTim9IhedaSwTDN7fJz68tyJinfejIjEVjnw90MPCAKzwYY2As30i7AdIPzUrvJPHSjhlZnZ9lSU6_BufQUw1caBQe-vSbtXx0pMw&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;expires_in&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;299&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;scope&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;openid&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1612497723&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;jti&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DBf3H44teTXNVcDZ0AybSAF7tPY&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-exampleapi-사용&quot;&gt;② ExampleAPI 사용&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt;는 발급 받은 JWT로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt; 서비스의 Example 리소스를 조회하고 변경한다
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$RESPONSE&lt;/span&gt; | jq .access_token | xargs&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept:application/json&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: bearer &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; http://localhost:8000/api/examples
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
    &lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;title&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;이문세 5집&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2021-02-05T02:18:46.000000Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;updated_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d593f9ef-d089-44fe-abe7-05c30219bb4c&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;page&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalElements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;totalPages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;number&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;-laravel---helloapi-사용&quot;&gt;③ Laravel -&amp;gt; HelloAPI 사용&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt; 서비스는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;internal&lt;/code&gt; 클라이언트 계정과 ClientCredentials 그랜트를 이용하여 UAA로부터 JWT를 발급 받는다&lt;/li&gt;
  &lt;li&gt;발급 받은 JWT로 HelloAPI 리소스를 조회한다&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Accept:application/json&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Authorization: bearer &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;ACCESS_TOKEN&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; http://localhost:8000/api/hello
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello from non-laravel service&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;-토큰-유효성-검사&quot;&gt;④ 토큰 유효성 검사&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laravel&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Spring&lt;/code&gt; 서비스는 클라이언트가 제출한 JWT의 유효성 검사에 필요한 공개키를 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UAA&lt;/code&gt;로부터 조회한다&lt;/li&gt;
  &lt;li&gt;받은 공개 키로 토큰 유효성을 검증하는 방법은 구현할 때 설명할 예정&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;s1&quot;&gt;&apos;http://localhost:9999/oauth/token_key&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SHA256withRSA&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-----BEGIN PUBLIC KEY-----&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlo/L8mU9Isiihp1ksxeOrJzPn4915xZC/pnbO+ur/ccZL23BYHP/wUxpWZy8Gh94+GK8/gcjVEk66acg4Gk7NH0uQGxdrq8WDMywPIAawekwiQJd6l/yVNXIDhuk0LzcgmU+1ESyeTNdlx84Z0X3HI6w8SH6OE4RBcr9rGfIt0ytXmHj1P4zxmJt/YhZyyyUq0WGuBq31UaQTOiJa0rp1kDKSMN0Hvz4UmkYtUvgtqUujrqNcWkSEummO8WyuhnCs+zAaF2KA5XSalEXFNiILwFPtQFxqIQrjjiWcI61vvTxtor4zI5r4X6aDteYIJidAzYwkIiuacnLWX5ziL3j+wIDAQAB&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-----END PUBLIC KEY-----&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;공개 키의 본문은 64글자 단위로 줄 바꿈해야 함; 따라서 이런 모양으로 포맷팅해서 사용해야 함&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;-----BEGIN&lt;/span&gt; PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlo/L8mU9Isiihp1ksxeO
rJzPn4915xZC/pnbO+ur/ccZL23BYHP/wUxpWZy8Gh94+GK8/gcjVEk66acg4Gk7
NH0uQGxdrq8WDMywPIAawekwiQJd6l/yVNXIDhuk0LzcgmU+1ESyeTNdlx84Z0X3
HI6w8SH6OE4RBcr9rGfIt0ytXmHj1P4zxmJt/YhZyyyUq0WGuBq31UaQTOiJa0rp
1kDKSMN0Hvz4UmkYtUvgtqUujrqNcWkSEummO8WyuhnCs+zAaF2KA5XSalEXFNiI
LwFPtQFxqIQrjjiWcI61vvTxtor4zI5r4X6aDteYIJidAzYwkIiuacnLWX5ziL3j
+wIDAQAB
&lt;span class=&quot;nt&quot;&gt;-----END&lt;/span&gt; PUBLIC KEY-----
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;2-oauth2-이해하기&quot;&gt;2 OAuth2 이해하기&lt;/h2&gt;

&lt;p&gt;OAuth2에 대한 지식이 부족하기도 하거니와, 방대한 주제라 블로그 포스트에서 설명하기는 어렵습니다. &lt;a href=&quot;https://oauth2.thephpleague.com/authorization-server/which-grant/&quot;&gt;thephpleague의 문서&lt;/a&gt;를 차분히 읽어보실 것을 권장합니다. 이 예제에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Password&lt;/code&gt;와 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientCredentials&lt;/code&gt; 그랜트만 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Password&lt;/code&gt; 그랜트는 1st Party 클라이언트&lt;/strong&gt;, 즉 내부에서 만든 클라이언트 애플리케이션을 위한 인증 방법입니다. Facebook을 예로 들어볼까요? Facebook 입장에서 공식 앱은 1st Party 클라이언트입니다. 페북 공식 앱을 사용하기 위해선 페북 사용자 아이디와 비밀번호를 제출해야하죠? 계정 정보가 정확하면 페북 인증 서버는 공식 앱에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 발급하고, 공식 앱은 발급 받은 토큰을 제출하면서 내 타임라인 데이터를 조회하고, 조회한 데이터를 뷰에 바인딩해서 렌더링할겁니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientCredentials&lt;/code&gt; 그랜트는 주로 1st Party 서버&lt;/strong&gt;를 위한 인증 방법입니다. 예를들어 페북 친구 서비스는 중요한 리소스라 아무나 조회하거나 변경해서는 안됩니다. 친구 추천 서버가 친구 서버와 통신할 때, 페북 인증 서버에서 받은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;으로 자격증명을 제출하고 친구 목록을 얻어올 겁니다.&lt;/p&gt;

&lt;p&gt;어떤 그랜트를 쓰든 보호된 리소스를 요청할 때 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; 요청 헤더에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 제출해야 합니다, 이렇게 말이죠.&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;GET /api/protected-resources
Authorization: bearer access_token
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;3-jwt-이해하기&quot;&gt;3 JWT 이해하기&lt;/h2&gt;

&lt;p&gt;OAuth2 인증 서버는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;으로 JWT를 사용할 필요는 없습니다. 만약 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Password&lt;/code&gt; 또는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientCredentials&lt;/code&gt; 그랜트 인증 요청에 대해 JWT가 아닌 난수 토큰을 제공했다 가정해보죠. 클라언트는 마이크로 서비스의 보호된 리소스를 사용하기위해 난수 토큰을 제출할테고, 요청을 받은 마이크로 서비스를 받은 난수 토큰의 유효성을 검증해야 하는데… 아마도 마이크로 서비스가 받은 난수 토큰이 유효한지 OAuth2 인증 서버에 다시 물어봐야겠죠~ 뿐더러 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;createdBy&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;updatedBy&lt;/code&gt; 등의 필드 값을 채우려면 난수 토큰에 해당하는 사용자 정보도 조회하고 메모리에 보관하고 있어야 할 겁니다.&lt;/p&gt;

&lt;p&gt;JWT는 토큰 본문 안에 여러 가지 정보를 담은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 만들 수 있기 때문에 앞서 언급한 문제점들을 해결할 수 있습니다.&lt;/p&gt;

&lt;p&gt;1절에서 받은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;을 &lt;a href=&quot;https://jwt.io&quot;&gt;https://jwt.io&lt;/a&gt;에서 파싱해보겠습니다. 아래 그림의 왼쪽 입력 박스에 토큰을 붙여 넣으면, 오른쪽에 파싱된 정보가 나옵니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2021-02-13-jwt.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;JWT 파싱&lt;/small&gt;&lt;/div&gt;

&lt;h3 id=&quot;header&quot;&gt;HEADER&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alg&lt;/code&gt;: 알고리즘&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typ&lt;/code&gt;: 토큰 타입 (jwt OR jwk)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;payload&quot;&gt;PAYLOAD&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iat&lt;/code&gt;: 발급일&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exp&lt;/code&gt;: 만료일&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jti&lt;/code&gt;: JWT ID&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_name&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorities&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_id&lt;/code&gt;: 커스텀 클레임&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;signature&quot;&gt;SIGNATURE&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;1절 “④ 토큰 유효성 검사”에서 조회한 공개 키를 입력 박스에 넣는다; 공개 키를 넣지 않아도 파싱은 된다&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;공개 키를 넣어서 토큰 유효성이 검증되면 네트워크 구간에서 토큰이 변조되지 않았음을 확신할 수 있다&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;jwt-라이브러리-설치&quot;&gt;JWT 라이브러리 설치&lt;/h3&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;composer require firebase/php-jwt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;전체 예제 코드는 &lt;a href=&quot;https://github.com/appkr/laravel-msa-example&quot;&gt;https://github.com/appkr/laravel-msa-example&lt;/a&gt;에서 볼 수 있습니다.&lt;/p&gt;

&lt;p&gt;2부에서 이어집니다&lt;/p&gt;
</description>
        <pubDate>Sat, 13 Feb 2021 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/laravel-msa-example/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/laravel-msa-example/</guid>
        
        <category>PHP</category>
        
        <category>Laravel</category>
        
        <category>MSA</category>
        
        <category>Oauth2</category>
        
        
        <category>work-n-play</category>
        
      </item>
    
      <item>
        <title>REVISIT 폰더씨의 위대한 하루</title>
        <description>&lt;p&gt;&lt;img src=&quot;http://image.yes24.com/goods/4586334/800x0&quot; alt=&quot;폰더씨의 위대한 하루&quot; style=&quot;max-width: 400px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;이 책의 주인공 데이비드 폰더는 결혼 12 년 만에 딸 아이를 가진, 나이 45세의 그저 그런, 직장에서 인정받지 못하는 샐러리맨이다. 딸 아이의 교육비는 점점 늘어가고, 밀린 집세, 자동차 할부금… 앞으로 20년은 더 직장 생활을 해야 할 것이다. 그래서, 작년 구조조정 때, 그는 희망 퇴직원을 제출하지 않고 회사에 남기로 마음 먹었다. 그는 직장에서 일하는 동안 아내로 부터 급한 전화를 한통 받는다. “일과 시간에 개인 전화는 곤란하다고 했잖아…”, “너무 급해서 그래요.” 보험도 없는 그의 딸 아이가 편도선 수술을 해야 한단다. 또, 그날 오후에 폰더에게는 청천벽력같은 일이 벌어진다. “금요일에 오면 그 동안 밀린 월급을 줄테니 다른 회사를 알아 보는 것이 좋겠소.” 치킨 집 오픈할 자본금 없고, 업무적으로 별반 능력도 없는, 나이 40 대의 남자가 일할 꺼리라고는 대리운전기사, 맥도날드 청소 점원… 밖에 없다. 이건 남의 이야기가 아니다. 동 시대를 살아가는 우리 선배, 후배, 친구, 아버지들, 나의 자화상이다.&lt;/p&gt;

&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;폰더는 자동차를 몰고 고속도로를 달리던 중 너무 화가 난 나머지 운전대를 주먹으로 치다가 그만 교통사고가 나고 만다. 그러면서, 과거와 미래로의 시간 여행을 떠나게 된다. 시간 여행 동안 그는 7명의 사람들을 만나고, 그들로부터 큰 선물 (가르침) 을 받는다.&lt;/p&gt;

&lt;p&gt;2004 년에 후배로 부터 선물받은 이 책을 읽었을 때도, 2021년에 다시 읽었을 때도, 난 첫 번째 선물이 가장 가슴에 와 닿았었다.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;첫번째-선물-내가-선택하지-않은-인생은-없다&quot;&gt;첫번째 선물-“내가 선택하지 않은 인생은 없다.”&lt;/h2&gt;

&lt;p&gt;“자, 이제 내 말을 잘 듣게. 자네가 오늘날 그 상황으로 내몰린 것은 자네의 사고방식 때문이야. 자네의 생각이 자네의 결정을 좌우하지. 모든 결정은 하나의 선택이야. 여러 해 전 자네는 대학에 가야겠다고 선택했어. 또 전공할 과목도 선택했지. 대학을 졸업한 뒤에는 이런 저런 회사에 이력서를 보내야겠다고 자신이 선택했어. 그 중 한 회사에서 일을 해야겠다고 선택한 것도 자네야. 그렇게 취직하기 위해 돌아 다니는 동안, 자네는 파티에도 참석했고 영화 구경을 하기도 했고 스포츠 활동을 하기도 했어. 이런 모든 활동은 따지고 보면 자네가 선택한 것이지. 그런 와중에 사랑하는 여자도 만나고 또 그 여자와 결혼해야겠다고 선택했어. 그 여자와 자네는 결혼을 해서 아이를 낳고 가족을 이루겠다고 선택했어”&lt;/p&gt;

&lt;p&gt;“자네가 살고 있는 집이나 자네가 몰고 다니는 차도 자네가 선택한 거야. 스테이크를 먹을 건지 핫도그를 먹을 건지 선택함으로써 자네는 가계비용을 스스로 선택했어. 조기 퇴직을 받아들이지 않은 것도 자네의 선택이었지. 자네는 회사가 쓰러지는 한이 있더라도 끝까지 남겠다고 선택했던 거야. &lt;strong&gt;아주 오래 전부터 자네는 수많은 선택을 했고 그것이 모여서 오늘날의 상황을 만들어낸 거라네&lt;/strong&gt;. 자네는 현재의 상황을 유도한 그 길의 한가운데를 분명히 걸어 왔던 거야.”&lt;/p&gt;

&lt;p&gt;“&lt;strong&gt;앞으로 ‘그건 내 잘못이 아니야!’ 라는 말은 절대로 해서는 안되네. 이브가 선악과를 맨 먼저 한 입 베어 물은 이래 ‘그건 내 잘못이 아니야!’ 라는 말은 실패한 사람들의 묘비를 장식하는 대표적인 문구가 되었지. 자신의 현재 상황에 대하여 총체적 책임을 지지 않는 한, 그 사람에게는 앞으로 나아갈 전망이 전혀 없어. 과거가 영원히 변하지 않는다는 것은 나쁜 소식이지만, 미래가 아주 다양한 모습으로 자네 손안에 있다는 것은 좋은 소식이지.&lt;/strong&gt;” 이게 트루먼 대통령이 데이비드 폰더에게 준 선물이다.&lt;/p&gt;

&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;우리는 初心 이라는 말을 자주 쓴다. 처음 직장을 선택하고는 설레임과 기대감으로 이것 저것 일을 벌인다. 하지만, 곧 열정은 시들어 지고 불평 불만을 늘어 놓게 된다. 물론, 입사할 때는 자신이 원하는 業에 맞는 직장을 선택한 것이지, 그 구성원들을 선택한 것은 아니다. 입사할 때는 구성원들은 잘 모른다. 시간이 지날 수록 구성원들과의 갈등은 불가피해 지고, 그 갈등을 해결해 나가는 것은 개인의 역량이다. 기억하라, 내가 선택한 직장이다. 그 선택이 잘못되었다면 빨리 일어 서자. 그렇게 하는 것이, 자신의 선택을 후회하지 않으면서, 현재 열정을 불태우는 구성원들에 대한 예의이다. 회사와 이 사회에 대한 예의이다.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Years wrinkle the skin, but to give up enthusiasm wrinkles the soul.&lt;/p&gt;

  &lt;p&gt;세월은 피부에 주름살을 만들지만, 열정을 포기하는 것은 영혼에 주름살을 만든다!&lt;/p&gt;

  &lt;footer&gt;&lt;cite&gt;Samuel Ullman&lt;/cite&gt;&lt;/footer&gt;
&lt;/blockquote&gt;

&lt;p&gt;이 책을 통해 내 삶에 대한 총체적 책임감, 긍정적인 마인드, 그리고 시들하던 열정을 다시 깨우친다&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Feb 2021 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/learn-n-think/the-travelers-gift/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/learn-n-think/the-travelers-gift/</guid>
        
        <category>인생철학</category>
        
        
        <category>learn-n-think</category>
        
      </item>
    
      <item>
        <title>달러구트 꿈 백화점</title>
        <description>&lt;p&gt;&lt;img src=&quot;http://image.yes24.com/goods/90051766/800x0&quot; alt=&quot;달러구트 꿈 백화점&quot; style=&quot;max-width: 400px;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;“페니, 나는 자신의 삶을 사랑하는 방법에는 2가지가 있다고 믿는단다. 첫째, &lt;strong&gt;아무래도 삶에 만족할 수 없을 때는 바꾸기 위해 최선을 다한다&lt;/strong&gt;.”&lt;/p&gt;

&lt;p&gt;“&lt;strong&gt;자신의 삶을 있는 그대로 받아들이고 만족하는 것&lt;/strong&gt;. 두 번빼 방법은 말은 쉽지만 실행하기는 쉽지 않지. 하지만 정말 할 수 있게 된다면, 글쎄다. 행복이 허무하리만치 가까이에 있었다는 걸 깨달을 수 있지.”&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Feb 2021 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/learn-n-think/dream-store/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/learn-n-think/dream-store/</guid>
        
        <category>인생철학</category>
        
        
        <category>learn-n-think</category>
        
      </item>
    
      <item>
        <title>git log examples</title>
        <description>&lt;script id=&quot;asciicast-378873&quot; src=&quot;https://asciinema.org/a/378873.js&quot; async=&quot;&quot;&gt;&lt;/script&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;h2 id=&quot;뷰&quot;&gt;뷰&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# -p: 각 커밋별 diff&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# -2: 최근 2개 커밋&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --stat: 각 커밋별 통계&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--stat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --pretty: oneline, short, full, fuller&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;oneline
git log &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;short
git log &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;full
git log &lt;span class=&quot;nt&quot;&gt;--pretty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fuller
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --graph&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--graph&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --name-only: 각 커밋별 파일 목록&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--name-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;필터링&quot;&gt;필터링&lt;/h2&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# commitId..commitId: 커밋ID로 필터&lt;/span&gt;
git log git log b315c55..HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --since; --after: 기간으로 필터&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--since&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1.weeks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --until; --before: 기간으로 필터&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--until&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3.days
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# --author: 커미터로 필터&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;--author&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;appkr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# -S: 파일, 함수로 필터&lt;/span&gt;
git log &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; PersistentEventPublisher
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;알림&quot;&gt;알림&lt;/h2&gt;
&lt;p&gt;터미널에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt;했을 때 저랑 다르게 나오는데요. 예, 로그 기본 출력 형식은 전역 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitconfig&lt;/code&gt;에 미리 정의해뒀기때문입니다&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# ~/.gitconfig&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;format]
	pretty &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; oneline
	pretty &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; format: %C&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;auto&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;%h%Creset %C&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;auto&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;%ad%Creset %C&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;auto,green&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;%aN%Creset %C&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;auto&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;%s%Creset
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Sat, 12 Dec 2020 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/learn-and-think/git-log-examples/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/learn-and-think/git-log-examples/</guid>
        
        <category>Git</category>
        
        <category>개발자</category>
        
        
        <category>learn-and-think</category>
        
      </item>
    
      <item>
        <title>개척자와 개발자, 타입별로 어떤 팀에 배치해야 할까요?</title>
        <description>&lt;p&gt;&lt;img src=&quot;/images/2020-05-05-img-01.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;이미지 출처: https://www.pexels.com/ko-kr/photo/314703/&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;개척자 &lt;small&gt;Explorer&lt;/small&gt;&lt;/strong&gt; 타입의 사람들은 항상 새로운 기회를 추구합니다. 반복되는 일상을 금방 지루해하고, 새롭고 참신한 아이디어를 구현하고 검증하기를 좋아합니다.
반면, 엉덩이가 가벼워서 하던 일을 충분히 더 개선/최적화할 수 있음에도 불구하고, 다른 새로운 일에 빠져드는 성향이 있습니다. 따라서, 드라이퍼스 모델&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;에서 말하는 전문가가 되는 경우는 극히 드뭅니다.
한 마디로 &lt;strong&gt;대한민국에서 등산하기 가장 좋은 산을 찾는 타입&lt;/strong&gt;입니다. 수학에서는 글로벌 맥시멈이라고도 하더군요.&lt;/p&gt;

&lt;p&gt;한편, &lt;strong&gt;개발자 &lt;small&gt;Exploiter&lt;/small&gt;&lt;/strong&gt; 타입의 사람들은 불확실함을 싫어하고 안정과 완벽함을 추구합니다. 맡은 분야에 열정을 가지고 최선의 노력으로 최고의 성과를 내기를 갈구합니다.
반면, 위험을 피하는 성향때문에 더 나은 기회를 선뜻 선택하지 못하고, 현실에 머물러 있게 됩니다.
한 마디로 &lt;strong&gt;도봉산에서 가장 좋은 등산로를 찾는 타입&lt;/strong&gt;입니다. 수학에서는 로컬 맥시멈이라고 하더군요.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;여러분 개개인은 어떤 타입인가요?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;일반적으로 자리가 사람을 만든다고 처한 상황에 따라 대부분은 자신을 적응시켜 나갑니다. 그럼에도 불구하고, 반복되는 지루한 일이 주어지거나 조금이라도 더 나은 대안이 생기면 조직을 떠나는 순혈 개척자와, 반대로 팀이나 일이 자꾸 바뀌면 매우 불편해하며 조직을 떠나는 순혈 개발자가 있습니다.&lt;/p&gt;

&lt;p&gt;개인은 스스로 조정한다치고, &lt;strong&gt;조직에서는 어떤 타입의 직원을 어떤 사업/제품에 배치하면 좋을까요?&lt;/strong&gt; 개척자가 필요한 곳에 개발자를 배치하거나, 개발자가 필요한 곳에 개척자를 배치하지는 않았나요? 이 포스트에서는 BCG 매트릭스&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;와 제품 수명 주기&lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;에 대입해서 질문에 대한 답을 찾아 보겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2020-05-05-img-02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;&lt;a href=&quot;https://www.facebook.com/juwonkimatmedotcom/posts/10205634566939661&quot;&gt;개발자가 필요한 시기에 쓴 단상(斷想)&lt;/a&gt;&lt;/small&gt;&lt;/div&gt;

&lt;!--more--&gt;
&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;BCG 매트릭스&lt;/strong&gt;란 &lt;strong&gt;시장의 성장률&lt;/strong&gt;와 사업/제품의 &lt;strong&gt;상대적 시장 점유율&lt;/strong&gt;로 표현한 사분면에 사업군/제품군의 매출 또는 판매수량을 원으로 배치시켜 현재의 위치를 파악하고 전략적 의사결정을 돕기 위한 분석 도구입니다. 상대적 시장 점유율이란, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;자사의 시장 점유율 / 자사를 제외하고 1위 경쟁사의 시장 점유율&lt;/code&gt;로 계산하며, 자사가 해당 시장에서 1위이면 1보다 큰 값, 그렇지 않다면 1보다 작은 값이 나옵니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;물음표 &lt;small&gt;Question Mark&lt;/small&gt;&lt;/strong&gt;: 시장은 성장하지만, 아직은 자사의 사업/제품 경쟁력이 부족해 상대적 시장 점유율은 낮은 영역입니다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;개 &lt;small&gt;Dog&lt;/small&gt;&lt;/strong&gt;: 시장 성장률도 낮고 자사의 상대적 시잠 점유율도 낮은 영역입니다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;캐시카우 &lt;small&gt;CashCow&lt;/small&gt;&lt;/strong&gt;: 시장은 더 이상 성장하지 않거나 역성장하지만, 자사의 상대적 시장 점유율이 높아, 가장 큰 매출을 일으키는 영역입니다.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;스타 &lt;small&gt;Star&lt;/small&gt;&lt;/strong&gt;: 빠르게 계속 성장하는 시장에서 자사의 상대적 시장 점유율이 경쟁사보다 우위인 사업/제품이 위치하는 영역입니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;제가 몸담은 회사의 사업/제품을 BCG로 그려보면 좋은데, 회사의 허락을 받지 않아 인터넷에 있는 그림을 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/7/76/Folio_Plot_BCG_Matrix_Example.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;이미지 출처: 위키피디아&lt;/small&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pentrix&lt;/code&gt;는 2위 업체보다 점유율이 2배나 높네요.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pentrix&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dorlan&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bendac&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Eviron&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Lotran&lt;/code&gt; 등에서 얻은 매출로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Xantax&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Longal&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Energan&lt;/code&gt; 등에 더 투자해서 이들은 스타 영역으로 옮길 수 있습니다.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Zodial&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Braviton&lt;/code&gt; 등은 성장률이 10% 이상인 시장에 있지만, 경쟁에서 지고 있는 것으로 보입니다. 시장 성장률이 더 떨어지면 매각 또는 철수등의 전략적 의사 결정을 할 수 있을 겁니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;BCG 매트릭스&lt;/strong&gt;는 &lt;strong&gt;제품 수명 주기&lt;/strong&gt;와도 딱들어 맞습니다. 제품 수명 주기는 연구개발비/영업비 등을 투자하며 제품을 출시하는 &lt;strong&gt;도입기 &lt;small&gt;Introduction&lt;/small&gt;&lt;/strong&gt;, 매출이 점점 성장하는 &lt;strong&gt;성장기 &lt;small&gt;Growth&lt;/small&gt;&lt;/strong&gt;, 경쟁사도 많아지고 시장이 충분히 성숙되어 추가적인 제품 변형없이도 계속 사업을 유지할 수 있는 &lt;strong&gt;성숙기 &lt;small&gt;Maturity&lt;/small&gt;&lt;/strong&gt;, 경쟁력 없는 경쟁사들이 하나둘씩 빠져나가는 &lt;strong&gt;쇠퇴기 &lt;small&gt;Decline&lt;/small&gt;&lt;/strong&gt;로 구분합니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://www.thestreet.com/.image/c_limit%2Ccs_srgb%2Cq_auto:good%2Cw_1400/MTY3NTM5NDQ4MjA0NDM3Mzkw/image-placeholder-title.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;text-center&quot;&gt;&lt;small&gt;이미지 출처: https://www.thestreet.com/markets/commodities/product-life-cycle-14882534&lt;/small&gt;&lt;/div&gt;

&lt;p&gt;보통 &lt;strong&gt;도입기&lt;/strong&gt;의 제품은 BCG 매트릭스의 &lt;strong&gt;물음표&lt;/strong&gt;에 위치하고, &lt;strong&gt;성장/성숙기&lt;/strong&gt;의 제품은 &lt;strong&gt;캐시카우&lt;/strong&gt; 또는 &lt;strong&gt;스타&lt;/strong&gt;에 위치하며, &lt;strong&gt;쇠퇴기&lt;/strong&gt;의 제품은 &lt;strong&gt;캐시카우&lt;/strong&gt; 또는 &lt;strong&gt;개&lt;/strong&gt;와 매칭됩니다.&lt;/p&gt;

&lt;div class=&quot;spacer&quot;&gt;• • •&lt;/div&gt;

&lt;p&gt;자, 다시 처음으로 돌아가서 다시 질문해볼까요? &lt;strong&gt;개척자와 개발자를 어떤 사업/제품을 담당하는 팀에 배치하면 될까요?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;답은 말하지 않아도 아시겠죠? 절대 법칙은 아니지만, 제가 조직장이라면 &lt;strong&gt;물음표에 개척자&lt;/strong&gt;, &lt;strong&gt;나머지 영역에는 개발자&lt;/strong&gt;를 배치할겁니다. &lt;strong&gt;개에서는 직원을 빼서 캐시카우나 스타&lt;/strong&gt;로 보낼겁니다.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dreyfus_model_of_skill_acquisition&quot;&gt;드라이퍼스 모델&lt;/a&gt;, &lt;a href=&quot;https://brunch.co.kr/@cleancode/4&quot;&gt;한글 설명&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/BCG_%EB%A7%A4%ED%8A%B8%EB%A6%AD%EC%8A%A4&quot;&gt;BCG 매트릭스&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.thestreet.com/markets/commodities/product-life-cycle-14882534&quot;&gt;제품 수명 주기&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 05 May 2020 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/learn-n-think/work-n-play/explorer-exploiter/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/learn-n-think/work-n-play/explorer-exploiter/</guid>
        
        <category>기업철학</category>
        
        <category>조직관리</category>
        
        <category>조직행동</category>
        
        
        <category>learn-n-think</category>
        
        <category>work-n-play</category>
        
      </item>
    
      <item>
        <title>근황</title>
        <description>&lt;p&gt;유튜브로 라이브 코딩 방송을 하면서부터 블로그는 소홀해졌습니다. 블로그에 &lt;strong&gt;Youtube Video&lt;/strong&gt; 라는 사이드바 콤포넌트를 붙이려고 오랜만에 블로그 코드를 열었다가 몇 글자 끄적여 봅니다.&lt;/p&gt;
</description>
        <pubDate>Sun, 21 Jul 2019 00:00:00 +0900</pubDate>
        <link>https://blog.appkr.dev/work-n-play/youtube/</link>
        <guid isPermaLink="true">https://blog.appkr.dev/work-n-play/youtube/</guid>
        
        
        <category>work-n-play</category>
        
      </item>
    
  </channel>
</rss>
