-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
473 lines (406 loc) · 235 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
<!doctype html>
<html lang="ko"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta><title>hwiVeloper</title><link rel="manifest" href="/manifest.json"><meta name="application-name" content="hwiVeloper"><meta name="msapplication-TileImage" content="/img/favicon.svg"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="hwiVeloper"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="description" content="hwiVeloper&#39;s dev note"><meta property="og:type" content="blog"><meta property="og:title" content="hwiVeloper"><meta property="og:url" content="https://hwiveloper.github.io/"><meta property="og:site_name" content="hwiVeloper"><meta property="og:description" content="hwiVeloper&#39;s dev note"><meta property="og:locale" content="ko_KR"><meta property="og:image" content="https://hwiveloper.github.io/img/og_image.png"><meta property="article:author" content="hwiVeloper"><meta property="twitter:card" content="summary"><meta property="twitter:image:src" content="https://hwiveloper.github.io/img/og_image.png"><script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","mainEntityOfPage":{"@type":"WebPage","@id":"https://hwiVeloper.github.io"},"headline":"hwiVeloper","image":["https://hwiveloper.github.io/img/og_image.png"],"author":{"@type":"Person","name":"hwiVeloper"},"publisher":{"@type":"Organization","name":"hwiVeloper","logo":{"@type":"ImageObject","url":"https://github.com/hwiVeloper.png"}},"description":"hwiVeloper's dev note"}</script><link rel="icon" href="/img/favicon.svg"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.0.0/css/all.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/styles/atom-one-light.css"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;600&family=Source+Code+Pro"><link rel="stylesheet" href="/css/default.css"><style>body>.footer,body>.navbar,body>.section{opacity:0}</style><!--!--><!--!--><!--!--><!--!--><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/build/cookieconsent.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/lightgallery.min.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/justifiedGallery.min.css"><!--!--><!--!--><style>.pace{-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.pace-inactive{display:none}.pace .pace-progress{background:#3273dc;position:fixed;z-index:2000;top:0;right:100%;width:100%;height:2px}</style><script src="https://cdn.jsdelivr.net/npm/[email protected]/pace.min.js"></script><!--!--><!--!--><!-- hexo injector head_end start --><script>
(function () {
function switchTab() {
if (!location.hash) {
return;
}
const id = '#' + CSS.escape(location.hash.substring(1));
const $tabMenu = document.querySelector(`.tabs a[href="${id}"]`);
if (!$tabMenu) {
return;
}
const $tabMenuContainer = $tabMenu.parentElement.parentElement;
Array.from($tabMenuContainer.children).forEach($menu => $menu.classList.remove('is-active'));
Array.from($tabMenuContainer.querySelectorAll('a'))
.map($menu => document.getElementById($menu.getAttribute("href").substring(1)))
.forEach($content => $content.classList.add('is-hidden'));
if ($tabMenu) {
$tabMenu.parentElement.classList.add('is-active');
}
const $activeTab = document.querySelector(id);
if ($activeTab) {
$activeTab.classList.remove('is-hidden');
}
}
switchTab();
window.addEventListener('hashchange', switchTab, false);
})();
</script><!-- hexo injector head_end end --><meta name="generator" content="Hexo 6.3.0"></head><body class="is-2-column"><nav class="navbar navbar-main"><div class="container navbar-container"><div class="navbar-brand justify-content-center"><a class="navbar-item navbar-logo" href="/"><img src="https://github.com/hwiVeloper.png" alt="hwiVeloper" height="28"></a></div><div class="navbar-menu"><div class="navbar-start"><a class="navbar-item is-active" href="/">Home</a><a class="navbar-item" href="/archives">Archives</a><a class="navbar-item" href="/categories">Categories</a><a class="navbar-item" href="/tags">Tags</a><a class="navbar-item" href="/about">About</a></div><div class="navbar-end"><a class="navbar-item" target="_blank" rel="noopener" title="Download on GitHub" href="https://github.com/ppoffice/hexo-theme-icarus"><i class="fab fa-github"></i></a><a class="navbar-item search" title="검색" href="javascript:;"><i class="fas fa-search"></i></a></div></div></div></nav><section class="section"><div class="container"><div class="columns"><div class="column order-2 column-main is-8-tablet is-8-desktop is-8-widescreen"><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-10-28T05:42:29.000Z" title="2019. 10. 28. 오후 2:42:29">2019-10-28</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.014Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/Scribble/">Scribble</a></span><span class="level-item">9분안에 읽기 (약 1348 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/10/28/2nd-job-flipping/">두 번째 이직</a></h1><div class="content"><h1 id="또-한-번의-이직"><a href="#또-한-번의-이직" class="headerlink" title="또 한 번의 이직"></a>또 한 번의 이직</h1><p>두 번째 직장을 떠나 세 번째 직장으로 몸을 옮겼다. 5월부터 슬슬 면접을 보기 위해 이력서 최신화도 시켜 놓고 취업포털을 통해 지원서를 제출했다. 지난 번 이직이 워낙 오래걸렸던 기억이 남아있었기 때문에, 이번에도 역시 오래 걸릴 것이라 생각하고 길게 보았다. 1년까진 아니더라도 가장 잘 팔린다는 만 3년이기 때문에 어느 정도 자신이 있었다.</p>
<p>그 때 당시 이직 사유는 다음과 같다.</p>
<ul>
<li>기술영업스러운 팀의 업무<ul>
<li>이것이 나에게 좋은 자산이 될 수 있을 것이라 생각했지만, 갈수록 기술적인 부분보다 영업적인 부분의 업무가 많아지면서 이직을 결심하게 되었다. <code>가장 큰 이유</code>이다.</li>
<li>개발에 좀 더 집중하고 싶다.</li>
</ul>
</li>
<li>💰<ul>
<li>연차에 비해 낮다고 생각되는 급여 역시 나의 이직 욕구를 불태웠다.</li>
<li>폭발적인 상승은 못하더라도 비교적 만족스러울만한 수준의 급여가 필요했다.</li>
</ul>
</li>
<li>한정된 개발환경<ul>
<li>UI/UX 제품 벤더사이기 때문에 아무래도 자사 제품을 활용한 개발에만 집중할 수 밖에 없었다. 자기계발이라는 소중한 시간을 잘 활용하긴 했지만, 커리어적인 면을 보았을 때, 나에게 큰 도움이 될 수 있을까 하는 의문이 들었다.</li>
<li>원래는 <code>JAVA</code> 위주로 공부하고 개발하는 것을 원했기 때문.</li>
</ul>
</li>
</ul>
<p>특히 경력자의 면접에서 항상 첫 번째 질문이 <code>왜 이직하려고 하는가?</code>이다. 자신이 왜 이직하려는지에 대한 고찰이 면접에서 잘 드러나야 면접관들의 마음을 사로잡기 좋다고 생각한다. 물론 돈, 잦은 야근 등 여러 가지 현실적인 이유들이 있겠지만, 커리어적으로 내가 어떤 미래를 그리고 있고 그렇기 때문에 귀사에 지원하게 되었다고 어필하는 것이 베스트가 아닐까 싶다.</p>
<h2 id="서류"><a href="#서류" class="headerlink" title="서류"></a>서류</h2><p>이번 이직은 대기업 계열사 위주로 타겟을 잡았다.</p>
<p>사실 회사를 많이 넣지는 못했다. 채용공고들은 언제나 물들어올 때 훅 들어오듯이 뜨기 때문이다. 그래도 뜨는대로 당장 가도 될 정도의 기업에 지원하기 시작했다. 첫 이직 때보다 훨씬 빠르게 서류 통과와 함께 면접 제의를 받았고, 두어 곳 정도에서 면접을 봤었다.</p>
<h2 id="실무진-면접-임원면접"><a href="#실무진-면접-임원면접" class="headerlink" title="실무진 면접, 임원면접"></a>실무진 면접, 임원면접</h2><p>사실 전 회사에서 가장 큰 수확은 <code>말하는</code> 스킬이었다. 말을 잘한다 라기 보다는 말을 조리있게 잘 한 것 같다. 고객사에 가서 제품소개와 기술대응을 여러 차례 하다보니, 자연스레 상대방을 이해시키고 설득시키는 대화에 익숙했다. 한 가지 아쉬운 점은 뻔뻔하게 내가 가진 것에 대한 <code>뻥튀기</code>(허세가 아닌 약간의 포장) 하는 스킬은 여전히 부족했다. 학부 시절 모 교수님께서 나한테 항상 아쉬워하는 부분이다. 한 마디로 거짓말을 잘 못할 뿐더러 거짓말을 하면 바로 티가 나는 스타일이다.</p>
<p>그래도 이번 회사에서 솔직하게 내가 가진 그대로 보여주고 말한 것을 좋게 봐주어서 면접에 생각보다 어렵지 않게 통과되었다. 하지만 이전 경력(프리세일즈)이 한편으로는 나에게 마이너스 요소가 되었나보다. 면접에 대한 피드백을 추후에 메일로 받았었는데(이런 회사가 흔치 않다.) 내 장단점을 확실하게 파악한 면접관이었다.(현 팀장님..)</p>
<p>임원 면접은 이상하게 입이 잘 풀린다. 다소 까다로운 질문도 받아서 적잖이 당황했지만, 그래도 잘 넘긴 것 같았다. 임원 면접은 보통 기술적인 면보다는 인성적인 부분(사실 인성을 얼마나 잘 알 수 있을지는 의문이지만)을 체크하면서 본다. 실무진면접보다는 확실히 편한 느낌을 많이 받았고, 동네 아저씨들과 수다 떨러 간다는 자세로 해서 그런지 결과적으로는 성공적인 면접이었다.</p>
<h2 id="현재"><a href="#현재" class="headerlink" title="현재"></a>현재</h2><p>현재 내가 소속되어 있는 팀은 일단은 괜찮아보인다. 3개월이 조금 넘은 시점에서 다닒만하다. <code>웹</code>과 <code>모바일</code> 등 다양한 개발환경이 있고, 현재 자바와 약간의 자바스크립트 위주로 개발 업무를 맡아 진행하고 있다.</p>
<p>대기업 계열사답게 지금까지 다녔던 회사들보다 <code>복지</code>, <code>급여</code> 등 만족하지 못할 수준은 아니다. 살짝 부족한 느낌..? 사람 욕심은 끝이 없다. 😆 그래도 이곳에 몸담은 이상 다시 한 번 열심히 달려보기로 한다.</p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-04-21T09:12:00.000Z" title="2019. 4. 21. 오후 6:12:00">2019-04-21</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.029Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">18분안에 읽기 (약 2677 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/04/21/spring-boot-oauth2-authserver/">스프링부트 OAuth2 - 3. OAuth 인증 서버 구축</a></h1><div class="content"><blockquote>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">https://spring.io/guides/tutorials/spring-boot-oauth2/</a> 를 참조하여 작성하였습니다.</p>
</blockquote>
<p>개인적으로 서버사이드 프로그래밍을 하면서 가장 어렵기도 하고 귀찮은 부분이 <code>인증</code>하는 부분이라고 생각한다. 로직을 작성해 나간다기 보다는 클라이언트와 서버사이드 간의 통신을 통해 사용자를 인지하고 이를 세션 등으로 관리할 뿐 아니라, 권한 문제까지 연결되는 부분이기 때문이다. 고로 가장 민감한 부분이라고 본다.</p>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">스프링 공식 홈페이지의 OAuth2 가이드</a>를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다.</p>
<ol>
<li><a href="https://hwiveloper.github.io/2019/04/05/spring-boot-oauth2-facebook/">Facebook으로 로그인</a></li>
<li><a href="https://hwiveloper.github.io/2019/04/08/spring-boot-oauth2-github/">Github로 로그인</a></li>
<li>OAuth2 인증 서버 구축</li>
</ol>
<h1 id="인증-서버-구축"><a href="#인증-서버-구축" class="headerlink" title="인증 서버 구축"></a>인증 서버 구축</h1><p>이번 포스팅에서는 앞서 만들었던 어플리케이션을 OAuth2 인증 서버로 만든다. 앞서 구현했던 <code>facebook</code>과 <code>github</code>를 통한 인증을 사용하지만, 별도의 자체 액세스 토큰을 만들어서 인증을 수행한다. 이 토큰을 사용하여 백엔드 자원을 보호하고 타 어플리케이션과 SSO를 수행한다.</p>
<h2 id="인증-설정-다듬기"><a href="#인증-설정-다듬기" class="headerlink" title="인증 설정 다듬기"></a>인증 설정 다듬기</h2><p>인증 서버를 본격적으로 구축하기 전에 <code>github</code>와 <code>facebook</code>에 관련된 설정을 먼저 다듬어야 할 필요가 있다. <code>ssoFilter()</code>를 수정해보자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Application.java</span></span><br><span class="line"><span class="keyword">private</span> Filter <span class="title function_">ssoFilter</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">CompositeFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CompositeFilter</span>();</span><br><span class="line"> List<Filter> filters = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> </span><br><span class="line"> filters.add(ssoFilter(facebook(), <span class="string">"/login/facebook"</span>));</span><br><span class="line"> filters.add(ssoFilter(github(), <span class="string">"/login/github"</span>));</span><br><span class="line"> </span><br><span class="line"> filter.setFilters(filters);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> filter;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>ssoFilter()</code>를 <code>overloading</code>한 새로운 메서드를 작성해야 한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Filter <span class="title function_">ssoFilter</span><span class="params">(ClientResources client, String path)</span> {</span><br><span class="line"> <span class="type">OAuth2ClientAuthenticationProcessingFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2ClientAuthenticationProcessingFilter</span>(path);</span><br><span class="line"> <span class="type">OAuth2RestTemplate</span> <span class="variable">template</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2RestTemplate</span>(client.getClient(), oauth2ClientContext);</span><br><span class="line"> filter.setRestTemplate(template);</span><br><span class="line"> <span class="type">UserInfoTokenServices</span> <span class="variable">tokenServices</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserInfoTokenServices</span>(client.getResource().getUserInfoUri(), client.getClient().getClientId());</span><br><span class="line"> tokenServices.setRestTemplate(template);</span><br><span class="line"> filter.setTokenServices(tokenServices);</span><br><span class="line"> <span class="keyword">return</span> filter;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>이전에 <code>ssoFilter()</code>에 작성한 내용과 유사하지만 약간의 공통화 과정을 거친 것이라 보면 되겠다. <code>ClientResources</code>라는 오브젝트는 존재하지 않는다. 따라서 별도의 wrapper 객체를 생성한다. 이는 별도의 <code>@Beaㅜ</code>로 선언된 <code>OAuth2ProtectedResourceDetails</code>와 <code>ResourceServerProperties</code>를 통합하는 역할이라 보면 되겠다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ClientResources</span> {</span><br><span class="line"> <span class="meta">@NestedConfigurationProperty</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">AuthorizationCodeResourceDetails</span> <span class="variable">client</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AuthorizationCodeResourceDetails</span>();</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@NestedConfigurationProperty</span></span><br><span class="line"> <span class="keyword">private</span> <span class="type">ResourceServerProperties</span> <span class="variable">resource</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ResourceServerProperties</span>();</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> AuthorizationCodeResourceDetails <span class="title function_">getClient</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> client;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> ResourceServerProperties <span class="title function_">getResource</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> resource;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>wrapper 클래스는 <code>@NestedConfigurationProperty</code>를 사용하여 어노테이션 프로세서가 하나의 값을 표현하지 않고 중첩 형식을 나타내기 때문에 메타데이터에 접근하여 크롤링하도록 지시한다.</p>
</blockquote>
<p>위 wrapper 클래스 작성으로 spring 설정을 이전처럼 사용할 수 있을 뿐만 아니라 공통화를 지향한 결과이다. 마지막으로 각각 provider의 설정값을 가져오도록 <code>@Bean</code>을 생성한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("github")</span></span><br><span class="line"><span class="keyword">public</span> ClientResources <span class="title function_">github</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ClientResources</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("facebook")</span></span><br><span class="line"><span class="keyword">public</span> ClientResources <span class="title function_">facebook</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ClientResources</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="인증-서버-가용-상태로-만들기"><a href="#인증-서버-가용-상태로-만들기" class="headerlink" title="인증 서버 가용 상태로 만들기"></a>인증 서버 가용 상태로 만들기</h2><p>특별히 어마어마한 타이핑이 필요하지 않고 어노테이션 하나로 끝낼 수 있다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableOAuth2Client</span></span><br><span class="line"><span class="meta">@EnableAuthorizationServer</span> <span class="comment">// 추가</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringBootOauth2Application</span> <span class="keyword">extends</span> <span class="title class_">WebSecurityConfigurerAdapter</span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>새로운 어노테이션을 추가하게 되면 필요한 엔드포인트와 security를 어플리케이션에 로드할 것이다. 그리고 몇가지 OAuth2 클라이언트에 관한 정보를 설정해야 한다.</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># application.yml</span></span><br><span class="line"><span class="attr">security:</span></span><br><span class="line"> <span class="attr">oauth2:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">client-id:</span> <span class="string">acme</span></span><br><span class="line"> <span class="attr">client-secret:</span> <span class="string">acmesecret</span></span><br><span class="line"> <span class="attr">scope:</span> <span class="string">read,</span> <span class="string">write</span></span><br><span class="line"> <span class="attr">auto-approve-scopes:</span> <span class="string">'.*'</span></span><br></pre></td></tr></table></figure>
<p>위 작업은 <code>facebook.client*</code>, <code>github.client*</code>에 대한 작업과 동일한 것이다. <code>auto-approve-scopes</code>는 위 설정처럼 정규표현식으로 작성이 가능하다. 이번 포스팅에서 작성하는 샘플에서는 특별히 제한을 두지 않기 때문에 모든 것을 허용하지만, 실제 프로젝트나 운영 단계에서는 세부적으로 설정할 필요가 있다.</p>
<p>인증 서버 구축을 마무리하기 위해서 UI에 관련된 security 설정이 필요하다. 샘플 어플리케이션이기 때문에 많은 설정이 필요하진 않지만, oauth와 관련된 endpoint 등의 필요한 부분에 설정해 줄 필요가 있다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> http.antMatcher(<span class="string">"/**"</span>) <span class="comment">// ①</span></span><br><span class="line"> .authorizeRequests()</span><br><span class="line"> .antMatchers(<span class="string">"/"</span>, <span class="string">"/login**"</span>, <span class="string">"/webjars/**"</span>, <span class="string">"/error**"</span>).permitAll() <span class="comment">// ②</span></span><br><span class="line"> .anyRequest().authenticated() <span class="comment">// ③</span></span><br><span class="line"> .and().exceptionHandling()</span><br><span class="line"> .authenticationEntryPoint(<span class="keyword">new</span> <span class="title class_">LoginUrlAuthenticationEntryPoint</span>(<span class="string">"/"</span>)) <span class="comment">// ④</span></span><br><span class="line"> .and().logout().logoutSuccessUrl(<span class="string">"/"</span>).permitAll()</span><br><span class="line"> .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())</span><br><span class="line"> .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>① : 기본적으로 모든 요청은 보호된다.</li>
<li>② : 로그인 엔드포인트는 제외된다.</li>
<li>③ : 그 외의 모든 요청은 인증이 필요하다.</li>
<li>④ : 인증되지 않은 사용자는 <code>/</code>로 redirect 된다.</li>
</ul>
<h2 id="Access-Token-얻기"><a href="#Access-Token-얻기" class="headerlink" title="Access Token 얻기"></a>Access Token 얻기</h2><p>이제 우리가 구축한 인증 서버를 통해 Access Token을 얻을 수 있다. 가장 쉬운 방법은 “acme” 클라이언트를 통해서이다. <code>curl</code> 명령어를 사용하여 토큰을 얻어보자</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=client_credentials</span><br><span class="line"><span class="comment"># % Total % Received % Xferd Average Speed Time Time Time Current</span></span><br><span class="line"><span class="comment"># Dload Upload Total Spent Left Speed</span></span><br><span class="line"><span class="comment"># 100 146 0 117 100 29 7312 1812 --:--:-- --:--:-- --:--:-- 9125{"access_token":"c42ba0f2-543e-4275-9eb2-efb1f48fa680","token_type":"bearer","expires_in":43186,"scope":"read write"}</span></span><br></pre></td></tr></table></figure>
<p>단순히 토큰을 얻는 것만으로는 뭔가 완벽한 어플리케이션을 위해서는 부족해 보인다. 특정 유저에게 생성하도록 만들어야 한다. 스프링 어플리케이션을 구동했을 때 아마 <code>Using generated security password: ...</code> 처럼 자동 생성되는 기본 암호를 볼 수 있을 것이다. 이를 이용하여 다시 토큰을 얻어보자.</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ curl acme:acmesecret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=...</span><br><span class="line"><span class="comment"># % Total % Received % Xferd Average Speed Time Time Time Current</span></span><br><span class="line"><span class="comment"># Dload Upload Total Spent Left Speed</span></span><br><span class="line"><span class="comment"># 100 251 0 172 100 79 1577 724 --:--:-- --:--:-- --:--:-- 2302{"access_token":"629d6260-5eba-43e7-9072-7608b6b46254","token_type":"bearer","refresh_token":"bd7e65ce-6663-40b1-8307-04787221197f","expires_in":43199,"scope":"read write"}</span></span><br></pre></td></tr></table></figure>
<p>명령어의 “…” 부분에는 앞서 말한 자동생성되는 암호를 기입해주어서 <code>curl</code>명령을 날리면 역시 토큰을 받을 수 있다. 현재 테스트한 방법 외에 일반적인 소셜 로그인에서는 “인증 코드”를 받아야 한다. 즉, 이를 통해 redirect, cookie 등을 핸들링하거나 외부 provider로부터 UI를 렌더링할 수 있어야 한다.</p>
<h2 id="클라이언트-어플리케이션-생성하기"><a href="#클라이언트-어플리케이션-생성하기" class="headerlink" title="클라이언트 어플리케이션 생성하기"></a>클라이언트 어플리케이션 생성하기</h2><p>우리가 구축한 인증 서버에 필요한 client를 생성해보자. <code>ClientApplication.java</code>를 생성할 것이다. 단, 기존 <code>*Application.java</code>와 같은 패키지(또는 서브 패키지)에 위치해서는 안된다. 스프링은 기존의 Application을 구동하면서 <code>ClientApplication</code>을 하나의 설정으로 구동시킬 것이다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ClientApplication.java</span></span><br><span class="line"><span class="comment">// src/test/java/ 에 위치해있다.</span></span><br><span class="line"><span class="meta">@EnableAutoConfiguration</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableOAuth2Sso</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ClientApplication</span> {</span><br><span class="line"> <span class="meta">@RequestMapping("/")</span></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">home</span><span class="params">(Principal user)</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello, "</span> + user.getName();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">SpringApplicationBuilder</span>(ClientApplication.class)</span><br><span class="line"> .properties(<span class="string">"spring.config.name=client"</span>).run(args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>단순하게 사용자의 이름을 출력하는 페이지로 구성이 되어있다. <code>spring.config.name=client</code>라는 설정파일을 불러와서 실행될 것이다. <code>client.yml</code>파일을 생성해보자.</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># client.yml</span></span><br><span class="line"><span class="attr">server:</span></span><br><span class="line"> <span class="attr">port:</span> <span class="number">9999</span></span><br><span class="line"> <span class="attr">servlet:</span></span><br><span class="line"> <span class="attr">context-path:</span> <span class="string">/client</span></span><br><span class="line"><span class="attr">security:</span></span><br><span class="line"> <span class="attr">oauth2:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">client-id:</span> <span class="string">acme</span></span><br><span class="line"> <span class="attr">client-secret:</span> <span class="string">acmesecret</span></span><br><span class="line"> <span class="attr">access-token-uri:</span> <span class="string">http://localhost:8080/oauth/token</span></span><br><span class="line"> <span class="attr">user-authorization-uri:</span> <span class="string">http://localhost:8080/oauth/authorize</span></span><br><span class="line"> <span class="attr">resource:</span></span><br><span class="line"> <span class="attr">user-info-uri:</span> <span class="string">http://localhost:8080/me</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"> <span class="attr">main:</span></span><br><span class="line"> <span class="attr">allow-bean-definition-overriding:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure>
<p>메인이 되는 어플리케이션 설정과 비슷하지만, facebook이나 github 대신 “acme” 클라이언트로 통하도록 설정이 되어있다. 어플리케이션은 9999 포트에서 띄워져서 기존 포트와의 충돌을 방지한다. <code>server.context-path</code>의 값을 별도로 세팅하였다. 따라서 <a target="_blank" rel="noopener" href="http://localhost:9999/client">http://localhost:9999/client</a> 를 통해 확인이 가능하다. 어플리케이션을 시작하면 아래 그림처럼 두개의 포트가 띄워질 것이다.(사실 이 부분을 늦게 확인하는 바람에 어떻게 9999포트가 열린 상태인지 확인을 못했다.)</p>
<p><img src="https://i.imgur.com/5tdTOEf.png" alt="Client쪽 port가 올라온 모습"></p>
<h2 id="사용자-정보-엔드포인트-보호하기"><a href="#사용자-정보-엔드포인트-보호하기" class="headerlink" title="사용자 정보 엔드포인트 보호하기"></a>사용자 정보 엔드포인트 보호하기</h2><p>Single sign on을 위한 새로운 인증 서버를 사용하기 위해 facebook과 github를 사용하는데, 사용자가 인증할 때 생성된 쿠키로 보호된다. 로컬에 부여 된 액세스 토큰과 함께 보안을 유지하기 위해 기존의 엔드포인트를 재사용하고 새 경로로 alias를 지정할 수 있다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping({ "/user", "/me" })</span></span><br><span class="line"><span class="keyword">public</span> Map<String, String> <span class="title function_">user</span><span class="params">(Principal principal)</span> {</span><br><span class="line"> Map<String, String> map = <span class="keyword">new</span> <span class="title class_">LinkedHashMap</span><>();</span><br><span class="line"> map.put(<span class="string">"name"</span>, principal.getName());</span><br><span class="line"> <span class="keyword">return</span> map;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>기존 메서드에서 <code>Principal</code>을 <code>Map</code>으로 바꾼 이유는 브라우저에서 민감한 정보를 노출시키지 않기 위해 숨기는 것이다. 추가적으로 필요한 부분이 있다면 Map에 추가적으로 put해줌으로써 브라우저에 노출시키는 것이 가능하다.</p>
</blockquote>
<p>“/me” 경로는 어플리케이션이 리소스 서버로 선언됨으로서 access token으로 보호된다. 다음과 같이 새로운 설정 클래스를 만들어보자</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Application.java</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableResourceServer</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ResourceServerConfiguration</span> <span class="keyword">extends</span> <span class="title class_">ResourceServerConfigurerAdapter</span> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> http.antMatcher(<span class="string">"/me"</span>)</span><br><span class="line"> .authorizeRequests().anyRequest().authenticated();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><del><code>@Order</code> 어노테이션을 추가한다.</del> <code>SecurityProperties</code>의 <code>ACCESS_OVERRIDE_ORDER</code>가 <code>deprecated</code> 상태이므로 넘어간다. 어플리케이션이 security 수행 시, 엔드포인트에 대한 우선순위가 있지만 여기서는 정하지 않겠다.</p>
<h3 id="테스트"><a href="#테스트" class="headerlink" title="테스트"></a>테스트</h3><p><a target="_blank" rel="noopener" href="http://localhost:9999/client/">http://localhost:9999/client/</a> 로 접속하게 되면 <code>redirect</code>가 되면서 <code>localhost:8080</code>으로 이동할 것이다. facebook과 github 로그인 중 하나를 선택하면 인증이 시작되고 완료되면 다시 <a target="_blank" rel="noopener" href="http://localhost:9999/client/">http://localhost:9999/client/</a> 로 이동될 것이다. 그리고 인증된 사용자의 이름이 출력되면서 테스트를 마무리할 수 있다.</p>
<p><img src="https://i.imgur.com/oLVhuab.png" alt="Client Application 테스트 결과"></p>
<h2 id="마무리"><a href="#마무리" class="headerlink" title="마무리"></a>마무리</h2><p>길고 긴 <code>OAuth2</code>인증부터 서버 구축까지 마무리를 해보았다. Spring Security는 이러한 기능도 하고 있지만 더 많은 기능을 담고 있는 강력한 모듈이기 때문에 추가적인 학습이 필요하다. 사실 이 포스팅 뒤에 에러 처리 등의 자잘한 과정이 남아있지만 이번 포스팅에서는 다루지 않겠다. 아마 여기까지 따라오기만 해도 꽤나 지칠 수 있기 때문이다. 이정도면 OAuth2의 기능을 살펴보았다고 해도 좋다. 참고 URL은 포스팅 처음에 링크를 걸어두었으니 가서 참고하면 좋을 것이다.(영어긴 하지만..)</p>
<h2 id="소스코드"><a href="#소스코드" class="headerlink" title="소스코드"></a>소스코드</h2><p><a target="_blank" rel="noopener" href="https://github.com/hwiVeloper/SpringBootStudy/tree/495193fb5ca875be9b7833765ce6379971bc0ba0/spring-boot-oauth2">https://github.com/hwiVeloper/SpringBootStudy/tree/495193fb5ca875be9b7833765ce6379971bc0ba0/spring-boot-oauth2</a></p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-04-08T07:30:00.000Z" title="2019. 4. 8. 오후 4:30:00">2019-04-08</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.029Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">5분안에 읽기 (약 707 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/04/08/spring-boot-oauth2-github/">스프링부트 OAuth2 - 2. Github 인증</a></h1><div class="content"><blockquote>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">https://spring.io/guides/tutorials/spring-boot-oauth2/</a> 를 참조하여 작성하였습니다.</p>
</blockquote>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">스프링 공식 홈페이지의 OAuth2 가이드</a>를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다.</p>
<ol>
<li><a href="https://hwiveloper.github.io/2019/04/05/spring-boot-oauth2-facebook/">Facebook으로 로그인</a></li>
<li>Github로 로그인</li>
<li><a href="https://hwiveloper.github.io/2019/04/21/spring-boot-oauth2-authserver/">OAuth2 인증 서버 구축</a></li>
</ol>
<h1 id="Github로-로그인"><a href="#Github로-로그인" class="headerlink" title="Github로 로그인"></a>Github로 로그인</h1><p>이번 포스팅에서는 지난 포스팅에서 다루었던<code>Facebook으로 로그인</code>에 이어서 Github계정으로 OAuth2 인증을 하는 과정에 관한 글이다. 방식은 비슷해서 금방 구현이 가능하다.</p>
<p>우선 <code>index.html</code> 파일에 Github로 로그인 할 수 있는 링크를 추가하자.</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h1</span>></span>로그인<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container unauthenticated"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> Facebook : <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"/login/facebook"</span>></span>클릭<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> Github : <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"/login/github"</span>></span>클릭<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<p>원래는 provider(facebook, github, google 등)마다 엔드포인트에 대한 처리가 달라져야겠지만 이번 시리즈의 포스팅에서는 따로 처리하지 않아도 인증 처리와 관련된 응답에서 <code>name</code>이라는 필드를 공통적으로 가지고 있기 때문에 특별히 변경할 사항은 없다.</p>
<h2 id="Github-인증-필터-추가"><a href="#Github-인증-필터-추가" class="headerlink" title="Github 인증 필터 추가"></a>Github 인증 필터 추가</h2><p><code>Application.java</code>에서 <code>/login/github</code>에 대한 필터 추가가 필요하다 <code>ssoFilter()</code>에 추가해보자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Filter <span class="title function_">ssoFilter</span><span class="params">()</span> {</span><br><span class="line"> </span><br><span class="line"> <span class="type">CompositeFilter</span> <span class="variable">filter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CompositeFilter</span>();</span><br><span class="line"> List<Filter> filters = <span class="keyword">new</span> <span class="title class_">ArrayList</span><>();</span><br><span class="line"> </span><br><span class="line"> <span class="type">OAuth2ClientAuthenticationProcessingFilter</span> <span class="variable">facebookFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2ClientAuthenticationProcessingFilter</span>(<span class="string">"/login/facebook"</span>);</span><br><span class="line"> <span class="type">OAuth2RestTemplate</span> <span class="variable">facebookTemplate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2RestTemplate</span>(facebook(), oauth2ClientContext);</span><br><span class="line"> facebookFilter.setRestTemplate(facebookTemplate);</span><br><span class="line"> <span class="type">UserInfoTokenServices</span> <span class="variable">tokenServices</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserInfoTokenServices</span>(facebookResource().getUserInfoUri(), facebook().getClientId());</span><br><span class="line"> tokenServices.setRestTemplate(facebookTemplate);</span><br><span class="line"> facebookFilter.setTokenServices(tokenServices);</span><br><span class="line"> filters.add(facebookFilter);</span><br><span class="line"> </span><br><span class="line"> <span class="type">OAuth2ClientAuthenticationProcessingFilter</span> <span class="variable">githubFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2ClientAuthenticationProcessingFilter</span>(<span class="string">"/login/github"</span>);</span><br><span class="line"> <span class="type">OAuth2RestTemplate</span> <span class="variable">githubTemplate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2RestTemplate</span>(github(), oauth2ClientContext);</span><br><span class="line"> githubFilter.setRestTemplate(githubTemplate);</span><br><span class="line"> tokenServices = <span class="keyword">new</span> <span class="title class_">UserInfoTokenServices</span>(githubResource(), getUserInfoUri(), github().getClientId());</span><br><span class="line"> githubFilter.setTokenServices(tokenServices);</span><br><span class="line"> filters.add(githubFilter);</span><br><span class="line"> </span><br><span class="line"> filter.setFilters(filters);</span><br><span class="line"> <span class="keyword">return</span> filter;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 중략 ...</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("github.client")</span></span><br><span class="line"><span class="keyword">public</span> OAuth2ProtectedResourceDetails <span class="title function_">github</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AuthorizationCodeResourceDetails</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("github.resource")</span></span><br><span class="line"><span class="keyword">public</span> ResourceServerProperties <span class="title function_">githubResource</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResourceServerProperties</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>이전에 등록한 Facebook 필터로 인해 <code>CompositeFilter</code>를 구성하여 <code>List</code>로 추가하는 방식을 사용하였다. <code>github()</code>, <code>githubResource()</code>를 추가하여 나머지 내용도 보충하였다.</p>
<p>또한 <code>application.yml</code>에 github 관련 설정을 추가한다. 선행되어야 하는 OAuth2 앱을 작성해야 하는데 github OAuth2 앱을 만드는 방법은 <a target="_blank" rel="noopener" href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/">여기</a>를 참조하자.</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">github:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">clientId:</span> <span class="string">CLIENT-ID</span></span><br><span class="line"> <span class="attr">clientSecret:</span> <span class="string">CLIENT-SECRET</span></span><br><span class="line"> <span class="attr">accessTokenUri:</span> <span class="string">https://github.com/login/oauth/access_token</span></span><br><span class="line"> <span class="attr">userAuthorizationUri:</span> <span class="string">https://github.com/login/oauth/authorize</span></span><br><span class="line"> <span class="attr">clientAuthenticationScheme:</span> <span class="string">form</span></span><br><span class="line"> <span class="attr">resource:</span></span><br><span class="line"> <span class="attr">userInfoUri:</span> <span class="string">https://api.github.com/user</span></span><br></pre></td></tr></table></figure>
<h2 id="결과-확인"><a href="#결과-확인" class="headerlink" title="결과 확인"></a>결과 확인</h2><p>결과를 확인해보자. 깃허브 로그인 링크를 클릭하면 다음과 같은 화면을 볼 수 있고, 승인을 하면 다시 <code>index.html</code>로 돌아와서 사용자정보가 확인되는 것을 볼 수 있다.</p>
<p><img src="https://i.imgur.com/tBv1Wub.png" alt="Github OAuth2 확인"></p>
<h2 id="소스코드"><a href="#소스코드" class="headerlink" title="소스코드"></a>소스코드</h2><p><a target="_blank" rel="noopener" href="https://github.com/hwiVeloper/SpringBootStudy/tree/18f34d30f05db7f0fa998b6a6cae3b0019c05331/spring-boot-oauth2">https://github.com/hwiVeloper/SpringBootStudy/tree/18f34d30f05db7f0fa998b6a6cae3b0019c05331/spring-boot-oauth2</a></p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-04-05T07:30:53.000Z" title="2019. 4. 5. 오후 4:30:53">2019-04-05</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.029Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">17분안에 읽기 (약 2613 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/04/05/spring-boot-oauth2-facebook/">스프링부트 OAuth2 - 1. Facebook 인증</a></h1><div class="content"><blockquote>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">https://spring.io/guides/tutorials/spring-boot-oauth2/</a> 를 참조하여 작성하였습니다.</p>
</blockquote>
<p>개인적으로 서버사이드 프로그래밍을 하면서 가장 어렵기도 하고 귀찮은 부분이 <code>인증</code>하는 부분이라고 생각한다. 로직을 작성해 나간다기 보다는 클라이언트와 서버사이드 간의 통신을 통해 사용자를 인지하고 이를 세션 등으로 관리할 뿐 아니라, 권한 문제까지 연결되는 부분이기 때문이다. 고로 가장 민감한 부분이라고 본다.</p>
<p><a target="_blank" rel="noopener" href="https://spring.io/guides/tutorials/spring-boot-oauth2/">스프링 공식 홈페이지의 OAuth2 가이드</a>를 보면 다른 가이드와는 다르게 다소 스크롤의 압박이 느껴진다. 따라서 크게 세 부분으로 나누어 포스팅이 진행될 예정이다.</p>
<ol>
<li>Facebook으로 로그인</li>
<li><a href="https://hwiveloper.github.io/2019/04/08/spring-boot-oauth2-github/">Github로 로그인</a></li>
<li><a href="http://hwiveloper.github.io/2019/04/21/spring-boot-oauth2-authserver/">OAuth2 인증 서버 구축</a></li>
</ol>
<h1 id="Facebook으로-로그인"><a href="#Facebook으로-로그인" class="headerlink" title="Facebook으로 로그인"></a>Facebook으로 로그인</h1><h2 id="프로젝트-생성"><a href="#프로젝트-생성" class="headerlink" title="프로젝트 생성"></a>프로젝트 생성</h2><p><code>Spring Starter Project</code>를 생성한다. Dependency는 <code>Web</code>을 선택하고 프로젝트 생성을 완료한다.</p>
<p><img src="https://i.imgur.com/q28vXuy.png" alt="프로젝트 생성"></p>
<h2 id="페이지-추가"><a href="#페이지-추가" class="headerlink" title="페이지 추가"></a>페이지 추가</h2><p><code>index.html</code>파일을 <code>src/main/resources/static</code>에 추가하고 아래와 같이 작성한다. </p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"X-UA-Compatible"</span> <span class="attr">content</span>=<span class="string">"IE=edge"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>OAuth2 Sample Application<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"description"</span> <span class="attr">content</span>=<span class="string">"OAuth2 Sample Application"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">base</span> <span class="attr">href</span>=<span class="string">"/"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"/webjars/bootstrap/css/bootstrap.min.css"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"/webjars/jquery/jquery.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"/webjars/bootstrap/js/bootstrap.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>OAuth2 Sample Application<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p><code><head></code> 안쪽에 <code>webjars</code>가 보이는데 클라이언트 사이드에서 사용하는 <code>javascript</code> 또는 <code>css</code> 라이브러리 등을 <code>jar</code> 형태로 import시킬 수 있는 기능이라고 보면 되겠다. <code>pom.xml</code>에 해당 내용을 추가해보자.</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- webjars --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>jquery<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.1.1<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>bootstrap<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.2.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>webjars-locator-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>bootstrap과 jquery를 추가하였다. <code>webjars-locator-core</code>라는 녀석은 앞서 추가한 webjars들의 path를 지정해주는 역할이라고 보면 좋을 것 같다. 앞서 <code>index.html</code>에서 <code>/webjars/**</code>에 위치한 js 파일과 css파일을 불러오기 위한 dependency이다.</p>
<h2 id="Security"><a href="#Security" class="headerlink" title="Security"></a>Security</h2><p>어플리케이션을 안전하게 만들기 위해서는(다소 어색한 말이지만 guide에 있는 말을 빌려왔다.) <code>Spring Security</code>와 관련된 dependency를 적용해야 한다. <code>pom.xml</code>에 추가해보자.</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- Security --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-security<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.security.oauth.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-security-oauth2-autoconfigure<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.0.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<p>Facebook으로 연결하기 위하여 <code>Application.java</code>에 <code>@EnableOAuth2Sso</code> 어노테이션을 추가한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableOAuth2Sso</span> <span class="comment">// <-- 이부분을 추가한다.</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringBootOauth2Application</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> SpringApplication.run(SpringBootOauth2Application.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>application.yml</code> 또는 <code>application.properties</code>를 열어서 다음과 같이 페이스북 관련 정보를 기입한다. 정보에 대한 내용은 <a target="_blank" rel="noopener" href="https://developers.facebook.com/">페이스북 개발자 페이지</a>에서 앱 생성 후 oauth2 관련 세팅을 완료한 후에 다음 스텝으로 넘어가자.</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">security:</span></span><br><span class="line"> <span class="attr">oauth2:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">client-id:</span> <span class="string">CLIENT-ID</span></span><br><span class="line"> <span class="attr">client-secret:</span> <span class="string">CLIENT-SECRET</span></span><br><span class="line"> <span class="attr">accessTokenUri:</span> <span class="string">https://graph.facebook.com/oauth/access_token</span></span><br><span class="line"> <span class="attr">userAuthorizationUri:</span> <span class="string">https://www.facebook.com/dialog/oauth</span></span><br><span class="line"> <span class="attr">tokenName:</span> <span class="string">oauth_token</span></span><br><span class="line"> <span class="attr">authenticationScheme:</span> <span class="string">query</span></span><br><span class="line"> <span class="attr">clientAuthenticationScheme:</span> <span class="string">form</span></span><br><span class="line"> <span class="attr">resource:</span></span><br><span class="line"> <span class="attr">userInfoUri:</span> <span class="string">https://graph.facebook.com/me</span></span><br></pre></td></tr></table></figure>
<p>서버를 실행하고 <code>http://localhost:8080</code>으로 접속해보자. 올바르게 설정이 완료되었다면 곧바로 페이스북 앱에 대해 동의하는 화면이 나올 것이다.</p>
<p><img src="https://i.imgur.com/xLyJVTw.png" alt="서버 실행"></p>
<p>앱에서 정보 수신에 대해 동의버튼을 누르면 원래 우리가 호출하려 했던 <code>index.html</code>과 같은 화면이 등장할 것이다. 이 때 (크롬 기준) 개발자도구를 열고 Network 탭을 확인하면 아래와같이 Cookie에 <code>JSESSIONID</code>가 생성된 것을 확인할 수 있다.</p>
<p><img src="https://i.imgur.com/GCUiAyY.png" alt="로그인 후 확인"></p>
<h2 id="Welcome-Page-추가하기"><a href="#Welcome-Page-추가하기" class="headerlink" title="Welcome Page 추가하기"></a>Welcome Page 추가하기</h2><p>Facebook 로그인 링크와 로그인 되었을 때 사용자 정보를 출력하는 페이지를 작성해보자. 다음과 같이 <code>index.html</code>을 작성한다.</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container unauthenticated"</span>></span></span><br><span class="line"> Facebook : <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"/login"</span>></span>클릭<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container authenticated"</span> <span class="attr">style</span>=<span class="string">"display:none"</span>></span></span><br><span class="line"> 로그인 되었습니다 : <span class="tag"><<span class="name">span</span> <span class="attr">id</span>=<span class="string">"user"</span>></span><span class="tag"></<span class="name">span</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<p><code>authenticated</code>와 <code>unauthenticated</code> 클래스를 통해 인증 여부를 확인하고 그에 따라 display를 해주는 부분이다. 이에 대한 내용을 jQuery를 사용하여 작성하자</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><script type=<span class="string">"text/javascript"</span>></span><br><span class="line"> $.<span class="title function_">get</span>(<span class="string">"/user"</span>, <span class="keyword">function</span>(<span class="params">data</span>) {</span><br><span class="line"> $(<span class="string">"#user"</span>).<span class="title function_">html</span>(data.<span class="property">userAuthentication</span>.<span class="property">details</span>.<span class="property">name</span>);</span><br><span class="line"> $(<span class="string">".unauthenticated"</span>).<span class="title function_">hide</span>()</span><br><span class="line"> $(<span class="string">".authenticated"</span>).<span class="title function_">show</span>()</span><br><span class="line"> });</span><br><span class="line"></script></span><br></pre></td></tr></table></figure>
<h3 id="서버사이드-변경"><a href="#서버사이드-변경" class="headerlink" title="서버사이드 변경"></a>서버사이드 변경</h3><p>위 javascript 소스코드처럼 <code>/user</code> 에 대한 부분을 작성해야 한다. <code>UserController.java</code>를 작성하자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserController</span> {</span><br><span class="line"> <span class="meta">@RequestMapping("/user")</span></span><br><span class="line"> <span class="keyword">public</span> Principal <span class="title function_">user</span><span class="params">(Principal principal)</span> {</span><br><span class="line"> <span class="keyword">return</span> principal;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>본래 <code>Principal</code> 전체를 return 하는 것은 권장되는 방법이 아니지만 샘플 프로젝트의 빠른 작업을 위해 이와 같이 사용한다. 추후 브라우저에서 보이고싶지 않은 부분에 대해서는 숨기는 처리를 한다.</p>
</blockquote>
<p>잘 작동하지만 사용자가 링크를 클릭 할 수가 없다. 로그인 링크를 표시하려면 <code>WebSecurityConfigurer</code>를 추가하여 설정을 한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Application.java</span></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableOAuth2Sso</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SpringBootOauth2Application</span> <span class="keyword">extends</span> <span class="title class_">WebSecurityConfigurerAdapter</span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> SpringApplication.run(SpringBootOauth2Application.class, args);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> http.antMatcher(<span class="string">"/**"</span>)</span><br><span class="line"> .authorizeRequests()</span><br><span class="line"> .antMatchers(<span class="string">"/"</span>, <span class="string">"/login**"</span>, <span class="string">"/webjars/**"</span>, <span class="string">"/error**"</span>)</span><br><span class="line"> .permitAll()</span><br><span class="line"> .anyRequest()</span><br><span class="line"> .authenticated();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>WebSecurityConfigurerAdapter</code>를 상속받고 <code>configure()</code>를 작성한다. 그리고 인증을 처리하는 로그인 엔드포인트와 다른 요청들에 대한 인증이 필요하도록 설정한다.</p>
<p>이제 서버를 재시작하고 접속하면 로그인 링크가 등장하고 로그인을 실행하면 로그인된 사용자의 정보가 등장할 것이다.</p>
<h2 id="로그아웃-버튼-추가하기"><a href="#로그아웃-버튼-추가하기" class="headerlink" title="로그아웃 버튼 추가하기"></a>로그아웃 버튼 추가하기</h2><p><code>index.html</code>에 <code>authenticated</code>클래스 안쪽에 로그아웃 버튼을 추가해보자. 또한 로그아웃 버튼에 대한 이벤트 함수를 작성한다.</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container authenticated"</span> <span class="attr">style</span>=<span class="string">"display:none"</span>></span></span><br><span class="line"> 로그인 되었습니다 : <span class="tag"><<span class="name">span</span> <span class="attr">id</span>=<span class="string">"user"</span>></span><span class="tag"></<span class="name">span</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">"logout()"</span> <span class="attr">class</span>=<span class="string">"btn btn-primary"</span>></span>로그아웃<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> logout = <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> $.<span class="title function_">post</span>(<span class="string">"/logout"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> $(<span class="string">"#user"</span>).<span class="title function_">html</span>(<span class="string">''</span>);</span><br><span class="line"> $(<span class="string">".unauthenticated"</span>).<span class="title function_">show</span>();</span><br><span class="line"> $(<span class="string">".authenticated"</span>).<span class="title function_">hide</span>();</span><br><span class="line"> });</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>서버사이드 역시 <code>/logout</code>에 대한 엔드포인트가 필요하다. 조금 전 작성했던 <code>configure()</code>에 로그아웃에 대한 엔드포인트를 작성하자.</p>
<p><code>/logout</code>은 <code>POST</code>메소드로 요청할 필요가 있다. 또한 <code>CSRF(Cross Site Request Forgery)</code>로부터 보호해야 한다. 현재 세션에 이로부터 보호하는 토큰값이 있기 때문에 클라이언트 단에서 이에 대한 값을 가지고 있어야 한다.</p>
<p>요즘 성행하는 자바스크립트 프레임워크에는 위와 같은 문제에 대한 방도가 내장이 되어있어서 사용해주면 된다. 자세한 내용은 좀 더 봐야 할 것 같다. 아직 이부분에 대해서는 심화학습이 필요하다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Application.java</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> http.antMatcher(<span class="string">"/**"</span>)</span><br><span class="line"> .authorizeRequests()</span><br><span class="line"> .antMatchers(<span class="string">"/"</span>, <span class="string">"/login**"</span>, <span class="string">"/webjars/**"</span>, <span class="string">"/error**"</span>)</span><br><span class="line"> .permitAll()</span><br><span class="line"> .anyRequest()</span><br><span class="line"> .authenticated()</span><br><span class="line"> .and().logout().logoutSuccessUrl(<span class="string">"/"</span>).permitAll()</span><br><span class="line"> .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="CSRF-토큰을-클라이언트-단에-추가하기"><a href="#CSRF-토큰을-클라이언트-단에-추가하기" class="headerlink" title="CSRF 토큰을 클라이언트 단에 추가하기"></a>CSRF 토큰을 클라이언트 단에 추가하기</h2><p>이번 샘플에서는 자바스크립트 프레임워크 등을 사용하지 않기 때문에 <code>pom.xml</code>에 라이브러리를 추가하는 것으로 대체한다. 라이브러리를 추가하고 html에 적용해보자.</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>js-cookie<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.1.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span> <span class="attr">src</span>=<span class="string">"/webjars/js-cookie/js.cookie.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">$.<span class="title function_">ajaxSetup</span>({</span><br><span class="line"> beforeSend : <span class="keyword">function</span>(<span class="params">xhr, settings</span>) {</span><br><span class="line"> <span class="keyword">if</span> (settings.<span class="property">type</span> == <span class="string">'POST'</span> || settings.<span class="property">type</span> == <span class="string">'PUT'</span></span><br><span class="line"> || settings.<span class="property">type</span> == <span class="string">'DELETE'</span>) {</span><br><span class="line"> <span class="keyword">if</span> (!(<span class="regexp">/^http:.*/</span>.<span class="title function_">test</span>(settings.<span class="property">url</span>) || <span class="regexp">/^https:.*/</span>.<span class="title function_">test</span>(settings.<span class="property">url</span>))) {</span><br><span class="line"> xhr.<span class="title function_">setRequestHeader</span>(<span class="string">"X-XSRF-TOKEN"</span>, <span class="title class_">Cookies</span>.<span class="title function_">get</span>(<span class="string">'XSRF-TOKEN'</span>));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>어플리케이션을 재시작하고 결과를 확인해본다.</p>
<h2 id="OAuth2-클라이언트에-대한-설정"><a href="#OAuth2-클라이언트에-대한-설정" class="headerlink" title="OAuth2 클라이언트에 대한 설정"></a>OAuth2 클라이언트에 대한 설정</h2><p><code>@EnableOAuth2Sso</code> 어노테이션에는 OAuth2 클라이언트와 인증 두 가지 기능이 있다. 클라이언트 기능은 인증 서버(이번 경우는 Facebook)에서 제공하는 OAuth2 리소스와 통신하는데 사용한다. 인증 기능은 해당 어플리케이션을 타 Spring Security와 연동시키는 것이다. 클라이언트 기능은 Spring Security OAuth2에 의해 제공되는 기능이고, <code>@EnableOAuth2Client</code>에 의해 enable/disable이 가능하다. 이번에는 <code>@EnableOAuth2Sso</code> 어노테이션을 사용하지 않고 <code>@EnableOAuth2Client</code> 어노테이션을 사용해보자.</p>
<p>우선 <code>OAuth2ClientContext</code>를 주입시키고 인증 filter를 security 설정에 추가할 수 있게 한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableOAuth2Client</span> <span class="comment">// 변경</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SocialApplication</span> <span class="keyword">extends</span> <span class="title class_">WebSecurityConfigurerAdapter</span> {</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> OAuth2ClientContext oauth2ClientContext; <span class="comment">// 의존성 주입</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">configure</span><span class="params">(HttpSecurity http)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> http.antMatcher(<span class="string">"/**"</span>)</span><br><span class="line"> .authorizeRequests()</span><br><span class="line"> .antMatchers(<span class="string">"/"</span>, <span class="string">"/login**"</span>, <span class="string">"/webjars/**"</span>, <span class="string">"/error**"</span>)</span><br><span class="line"> .permitAll()</span><br><span class="line"> .anyRequest()</span><br><span class="line"> .authenticated()</span><br><span class="line"> .and().logout().logoutSuccessUrl(<span class="string">"/"</span>).permitAll()</span><br><span class="line"> .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); </span><br><span class="line"> .and().addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p><code>OAuth2ClientContext</code>를 사용하는 <code>ssoFilter()</code>를 작성한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Filter <span class="title function_">ssoFilter</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">OAuth2ClientAuthenticationProcessingFilter</span> <span class="variable">facebookFilter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2ClientAuthenticationProcessingFilter</span>(<span class="string">"/login/facebook"</span>);</span><br><span class="line"> <span class="type">OAuth2RestTemplate</span> <span class="variable">facebookTemplate</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OAuth2RestTemplate</span>(facebook(), oauth2ClientContext);</span><br><span class="line"> facebookFilter.setRestTemplate(facebookTemplate);</span><br><span class="line"> <span class="type">UserInfoTokenServices</span> <span class="variable">tokenServices</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">UserInfoTokenServices</span>(facebookResource().getUserInfoUri(), facebook().getClientId());</span><br><span class="line"> tokenServices.setRestTemplate(facebookTemplate);</span><br><span class="line"> facebookFilter.setTokenServices(tokenServices);</span><br><span class="line"> <span class="keyword">return</span> facebookFilter;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>위 필터에서는 facebook과 관련된 application 속성을 가지고 client 정보를 등록한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("facebook.client")</span> <span class="comment">// application 속성</span></span><br><span class="line"><span class="keyword">public</span> AuthorizationCodeResourceDetails <span class="title function_">facebook</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AuthorizationCodeResourceDetails</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>그리고 facebook 사용자 정보를 가져오는 엔드포인트를 등록하여 인증처리를 한다. (<code>facebookResource()</code> 작성)</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConfigurationProperties("facebook.resource")</span></span><br><span class="line"><span class="keyword">public</span> ResourceServerProperties <span class="title function_">facebookResource</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ResourceServerProperties</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>위 수정사항들을 추가하면서 <code>application.yml</code>(<code>application.properties</code>)의 속성을 바꿔줄 필요가 있다.</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">facebook:</span></span><br><span class="line"> <span class="attr">client:</span></span><br><span class="line"> <span class="attr">client-id:</span> <span class="string">CLIENT-ID</span></span><br><span class="line"> <span class="attr">client-secret:</span> <span class="string">CLIENT-SECRET</span></span><br><span class="line"> <span class="attr">accessTokenUri:</span> <span class="string">https://graph.facebook.com/oauth/access_token</span></span><br><span class="line"> <span class="attr">userAuthorizationUri:</span> <span class="string">https://www.facebook.com/dialog/oauth</span></span><br><span class="line"> <span class="attr">tokenName:</span> <span class="string">oauth_token</span></span><br><span class="line"> <span class="attr">authenticationScheme:</span> <span class="string">query</span></span><br><span class="line"> <span class="attr">clientAuthenticationScheme:</span> <span class="string">form</span></span><br><span class="line"> <span class="attr">resource:</span></span><br><span class="line"> <span class="attr">userInfoUri:</span> <span class="string">https://graph.facebook.com/me</span></span><br></pre></td></tr></table></figure>
<p>그리고 <code>index.html</code>파일을 수정해준다. 링크의 url을 변경한다.</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h1</span>></span>로그인<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"container unauthenticated"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span>></span></span><br><span class="line"> Facebook : <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"/login/facebook"</span>></span>클릭<span class="tag"></<span class="name">a</span>></span> <span class="comment"><!-- url 변경 --></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure>
<p>마지막으로 어플리케이션에서 Facebook으로 redirect하는 것을 지원하는 Filter를 작성하고 Bean으로 등록한다. 필터는 이미 <code>@EnableOAuth2Client</code>를 통해 등록된 것이기 때문에 정상적인 필터 작동을 위해 연결시켜줄 필요가 있다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> FilterRegistrationBean<OAuth2ClientContextFilter> <span class="title function_">oauth2ClientFilterRegistration</span><span class="params">(OAuth2ClientContextFilter filter)</span> {</span><br><span class="line"> FilterRegistrationBean<OAuth2ClientContextFilter> registration = <span class="keyword">new</span> <span class="title class_">FilterRegistrationBean</span><OAuth2ClientContextFilter>();</span><br><span class="line"> registration.setFilter(filter);</span><br><span class="line"> registration.setOrder(-<span class="number">100</span>);</span><br><span class="line"> <span class="keyword">return</span> registration;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>이제 마지막으로 로그인 / 로그아웃을 테스트해본다. 잘되는 모습을 볼 수 있다.</p>
<p>막바지의 어노테이션 변경 부분부터 다소 어려운 부분인 것 같다. 평소 못보았던 클래스도 많이 보이고 OAuth2와 관련된 필터 설정이 생각보다 손이 많이 가는 것을 보았다. 포스팅 가장 처음에 말했듯이 가장 어려우면서도 귀찮은 부분인 <code>인증</code> 과정에 대해 책을 정독하듯이 따라오게 되었는데, 앞으로 이 부분(특히 <code>Filter</code>를 적용하는 부분)에 대해 더 심도있게 알아보고 싶다. 그래도 한 번 해놓으면 또 써먹을 날이 오겠지..</p>
<p>다음 포스팅에서는 이와 유사하게 <code>Github</code> 인증을 진행할 것이다.</p>
<h2 id="소스코드"><a href="#소스코드" class="headerlink" title="소스코드"></a>소스코드</h2><p><a target="_blank" rel="noopener" href="https://github.com/hwiVeloper/SpringBootStudy/tree/2c714d3ba9c511e790e087b9362842017dcfc9f3/spring-boot-oauth2">https://github.com/hwiVeloper/SpringBootStudy/tree/2c714d3ba9c511e790e087b9362842017dcfc9f3/spring-boot-oauth2</a></p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-01-16T14:39:53.000Z" title="2019. 1. 16. 오후 11:39:53">2019-01-16</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.029Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">4분안에 읽기 (약 673 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/01/16/spring-boot-scheduler/">스프링부트 스케쥴러</a></h1><div class="content"><p>스프링 부트에서는 스케쥴러가 사용이 가능하다. 따로 <code>Maven</code>이나 <code>Gradle</code>에서 dependency를 추가하지 않고 기본 요소로 탑재가 되어있다. 간단하게 어노테이션과 <code>Task</code>를 정의하는 것만으로도 사용할 수 있는데, 이에 대해서 알아보자.</p>
<h2 id="스케쥴러-ON"><a href="#스케쥴러-ON" class="headerlink" title="스케쥴러 ON"></a>스케쥴러 ON</h2><p><code>Application.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.boot.SpringApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;</span><br><span class="line"><span class="keyword">import</span> org.springframework.scheduling.annotation.EnableScheduling;</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableScheduling</span> <span class="comment">// 이 부분 추가</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Application</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> SpringApplication.run(Application.class);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>스프링 스타터 프로젝트 생성 시 기본으로 생성되는 어플리케이션 소스코드이다. <code>@EnableScheduling</code> 어노테이션 추가를 통해 스케쥴러를 동작시키겠다는 정의를 내리는 부분이다.</p>
<h2 id="스케쥴-task-정의"><a href="#스케쥴-task-정의" class="headerlink" title="스케쥴(task) 정의"></a>스케쥴(task) 정의</h2><p>이제 스케쥴을 정의해보자. 따로 패키지 위치는 상관없는 것으로 보인다.</p>
<p><code>ScheduleTasks.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ScheduledTasks</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(ScheduledTasks.class);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">SimpleDateFormat</span> <span class="variable">dateFormat</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">"HH:mm:ss"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 스케쥴 task 정의 */</span></span><br><span class="line"> <span class="meta">@Scheduled(fixedRate = 5000)</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">reportCurrentTime</span><span class="params">()</span> {</span><br><span class="line"> log.info(<span class="string">"The time is now {}"</span>, dateFormat.format(<span class="keyword">new</span> <span class="title class_">Date</span>()));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>@Scheduled</code> 어노테이션으로 해당 메서드가 스케쥴러에 의해 동작되는 것임을 알려주는 부분이다. 이 어노테이션은 현재 <code>fixedRate</code>의 값이 지정되어 있고 이는 5초에 한번씩 동작하게끔 한다. 이외에도 특히 쓸만하다고 생각되는 것은 <code>cron</code>이다. 리눅스 환경에서의 <code>crontab</code>와 같은 부분이다.</p>
<blockquote>
<p>단, crontab에서는 다섯자리 표현식이 가능했던 반면, Spring boot scheduler에서는 6자리 표현식만 허용된다.</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Scheduled(cron="*/5 * * * * *")</span></span><br></pre></td></tr></table></figure>
<p><code>cron</code> 속성은 총 6자리로 이루어지고 일반적은 cron을 설정하듯이 작성해준다. 작성 후 서버를 재기동 하면 그 순간부터 스케쥴러가 돌기 시작하면서 사전에 정의된 메서드를 실행한다.</p>
<p><code>@Scheduled</code> 어노테이션 속성은 <code>cron</code> 외에도 다음과 같은 것들이 있다. 더 다양한 속성이 있지만 두 가지만 살펴보도록 하자.</p>
<p>| 속성 | type | 설명 |<br>|———— |—— |——————————————————– |<br>| fixedDelay | int | invoke 완료 후 지정된 시간(milliseconds) 이후에 재실행 |<br>| fixedRate | int | 지정된 시간(milliseconds) 간격으로 실행 |</p>
<h2 id="정리"><a href="#정리" class="headerlink" title="정리"></a>정리</h2><p>스프링의 스케쥴러는 일반적인 리눅스의 <code>crontab</code>사용보다도 더 간단해서 마음에 쏙 들었다. 하지만 너무 많은 스케쥴 task, 또는 과중한 스케쥴러는 어플리케이션에 지장을 줄 수도 있으니 적절하게 쓰는 것이 중요하다고 생각된다.</p>
<h2 id="참조"><a href="#참조" class="headerlink" title="참조"></a>참조</h2><p><a target="_blank" rel="noopener" href="https://spring.io/guides/gs/scheduling-tasks/">https://spring.io/guides/gs/scheduling-tasks/</a></p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-01-10T07:34:01.000Z" title="2019. 1. 10. 오후 4:34:01">2019-01-10</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.014Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/Review/">Review</a></span><span class="level-item">2분안에 읽기 (약 325 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/01/10/book-review-it-trend-special-report-2019/">[도서리뷰] IT 트렌드 스페셜 리포트 2019</a></h1><div class="content"><p><img src="https://i.imgur.com/13LUPdi.jpg" alt="IT 트렌드 스페셜 리포트 2019"></p>
<p><code>4차산업혁명</code>, 이시대에 가장 핫한 키워드임은 분명하다. 매년 가트너에서 새해에 주목해야 할 10가지 기술 및 트렌드에 대해서 발표하는데, 이 책도 약간 그러한 느낌이다. 추가적으로 챕터마다 관련된 기업과 그 기업의 솔루션 및 제품을 함께 소개한다.</p>
<p>IT쪽에 종사하지 않는 사람은 이 책에 나오는 내용이 어려울 수도 있지만, 그래도 쉽게 풀어쓰려 노력한 흔적이 보인다. 갖가지 도표와 이미지, 예시와 함께 어려운 설명을 보충해나가며 범용적으로 읽히기 쉽도록 했다.</p>
<p>구성된 큼지막한 챕터는 이미 우리 생활 주변에서 한 번쯤은 들어본 말도 있을 것이다. 그만큼 4차산업혁명이라는 큰 파도의 시작 속에 우리가 살아가고 있다고 생각한다. 그 파도 속을 잘 헤쳐나가서 멋진 <code>서퍼</code>가 되기 위해 이 책 한 번 쯤은 읽어볼만하다.</p>
<blockquote>
<p>이 리뷰는 ‘한빛미디어’로부터 도서를 제공받아 작성되었습니다.</p>
</blockquote>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-01-10T00:43:59.000Z" title="2019. 1. 10. 오전 9:43:59">2019-01-10</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.029Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">14분안에 읽기 (약 2147 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/01/10/spring-boot-stomp-websocket/">스프링부트에서 STOMP 웹소켓 사용하기</a></h1><div class="content"><p><code>spring guide</code>를 보던 중 <code>STOMP</code> 프로토콜을 사용하여 <code>WebSocket</code>을 구현하는 재미있는 샘플이 있어서 사용해보았다. <code>WebSocket</code>은 TCP레이어 위에 존재하여 중간에 메세지를 전달할 수 있는 채널의 개념이다. 공식 가이드에서는 <code>STOMP</code>라는 프로토콜을 사용하여 간단한 요청 및 응답을 하는 기본적인 구성으로 되어있다. 이에 더하여 정말 간단한 채팅 어플리케이션을 구현하는 것까지가 이번 글의 목표이다.</p>
<p>소스코드는 <code>http://spring.io/guides/gs/messaging-stomp-websocket</code>에서 참조했다.</p>
<h1 id="STOMP"><a href="#STOMP" class="headerlink" title="STOMP"></a>STOMP</h1><p>프로젝트 생성 전에 <code>STOMP(https://stomp.github.io/)</code>가 무엇인지 간단히 알아보자. <code>Simple Text Oriented Messaging Protocol</code>의 약자로 단순 텍스트 기반 메세징 프로토콜이다.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">COMMAND</span><br><span class="line">key(header):value</span><br><span class="line">key(header):value</span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">BODY^@</span><br></pre></td></tr></table></figure>
<p>위와 같은 형식으로 되어있다. <code>COMMAND</code>에는 보통 <code>SEND</code>, <code>SUBSCRIBE</code>와 같은 명령을 사용할 수 있다. 추가적인 header와 body 본문 내용을 통해 통신을 하는 방식이라고 보면 되겠다.</p>
<p><img src="https://i.imgur.com/wUQSJkH.png" alt="STOMP 통신 포맷 예시"></p>
<p>위 예시 이미지는 소켓 통신 커넥션을 생성하는 부분이므로 COMMAND와 header만 존재하고 body 본문 내용은 존재하지 않다.</p>
<p>더 자세한 내용은 <a target="_blank" rel="noopener" href="https://zetawiki.com/wiki/STOMP">https://zetawiki.com/wiki/STOMP</a> 등에서 참조하도록 하고 본격적인 프로젝트 생성부터 들어가보자.</p>
<h1 id="프로젝트-생성"><a href="#프로젝트-생성" class="headerlink" title="프로젝트 생성"></a>프로젝트 생성</h1><p><a target="_blank" rel="noopener" href="https://start.spring.io/">https://start.spring.io/</a> 에서 프로젝트를 생성하거나 eclipse, sts 등 사용하고 있는 IDE에서 spring boot project를 생성한다.</p>
<p><img src="https://i.imgur.com/sU6nfkH.png" alt="STS에서 프로젝트 생성-1"><br><img src="https://i.imgur.com/DqqLsAJ.png" alt="STS에서 프로젝트 생성-2"></p>
<h1 id="빌드-툴"><a href="#빌드-툴" class="headerlink" title="빌드 툴"></a>빌드 툴</h1><p><code>Gradle</code>과 <code>Maven</code>을 사용할텐데 두 가지 뭐로 하든 무방하다.</p>
<details><summary>Gradle</summary>
<figure class="highlight gradle"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">buildscript</span> {</span><br><span class="line"> ext {</span><br><span class="line"> springBootVersion = <span class="string">'2.1.1.RELEASE'</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">repositories</span> {</span><br><span class="line"> mavenCentral()</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">dependencies</span> {</span><br><span class="line"> <span class="keyword">classpath</span>(<span class="string">"org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">apply plugin: <span class="string">'java'</span></span><br><span class="line">apply plugin: <span class="string">'eclipse-wtp'</span></span><br><span class="line">apply plugin: <span class="string">'org.springframework.boot'</span></span><br><span class="line">apply plugin: <span class="string">'io.spring.dependency-management'</span></span><br><span class="line"></span><br><span class="line">bootJar {</span><br><span class="line"> baseName = <span class="string">'spring-boot-chatting'</span></span><br><span class="line"> version = <span class="string">'0.1.0'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">sourceCompatibility</span> = <span class="string">'1.8'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">repositories</span> {</span><br><span class="line"> mavenCentral()</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">dependencies</span> {</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.springframework.boot:spring-boot-starter-websocket"</span>)</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.webjars:webjars-locator-core"</span>)</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.webjars:sockjs-client:1.0.2"</span>)</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.webjars:stomp-websocket:2.3.3"</span>)</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.webjars:bootstrap:3.3.7"</span>)</span><br><span class="line"> <span class="keyword">compile</span>(<span class="string">"org.webjars:jquery:3.1.0"</span>)</span><br><span class="line"></span><br><span class="line"> testCompile(<span class="string">"org.springframework.boot:spring-boot-starter-test"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">task</span> stage {</span><br><span class="line"> dependsOn build</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</details>
<details><summary>Maven</summary>
<p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">modelVersion</span>></span>4.0.0<span class="tag"></<span class="name">modelVersion</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>gs-messaging-stomp-websocket<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.1.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">parent</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-parent<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.0.5.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">parent</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-websocket<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>webjars-locator-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>sockjs-client<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.0.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>stomp-websocket<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>2.3.3<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>bootstrap<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.3.7<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.webjars<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>jquery<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.1.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-starter-test<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>test<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">properties</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">java.version</span>></span>1.8<span class="tag"></<span class="name">java.version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">properties</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">build</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.boot<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-boot-maven-plugin<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">plugins</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">build</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">project</span>></span></span><br></pre></td></tr></table></figure>
</p>
</details>
<h1 id="메세지-Class-작성"><a href="#메세지-Class-작성" class="headerlink" title="메세지 Class 작성"></a>메세지 Class 작성</h1><p><code>STOMP</code>메세지 포맷에 맞게 <code>getter</code>를 가진 domain class를 생성해보자. STOMP 메세지에서 body는 <code>JSON</code> 오브젝트로 구성이 되어있다. 스프링 부트에서 <code>Jackson JSON</code> 라이브러리를 사용하고 있기 때문에 domain class를 파라미터로 사용하거나 <code>return</code>시켜준다면 json으로 변환이 된 상태로 통신할 것이다.</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"hwiVeloper"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure>
<p><code>HelloMessage.java</code>를 생성하고 내용을 작성한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> dev.hwi.domain;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloMessage</span> {</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">HelloMessage</span><span class="params">()</span> {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">HelloMessage</span><span class="params">(String name)</span> {</span><br><span class="line"> <span class="built_in">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setName</span><span class="params">(String name)</span> {</span><br><span class="line"> <span class="built_in">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>그리고 <code>Greeting.java</code>를 작성해보자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> dev.hwi.domain;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Greeting</span> {</span><br><span class="line"> <span class="keyword">private</span> String content;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Greeting</span><span class="params">()</span> {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Greeting</span><span class="params">(String content)</span> {</span><br><span class="line"> <span class="built_in">this</span>.content = content;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getContent</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> content;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h1 id="컨트롤러"><a href="#컨트롤러" class="headerlink" title="컨트롤러"></a>컨트롤러</h1><p><code>STOMP</code> 메세지는 스프링에서는 컨트롤러에서 핸들링된다. 아래 <code>GreetingConroller.java</code>를 작성해보자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> dev.hwi.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.messaging.handler.annotation.MessageMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.messaging.handler.annotation.SendTo;</span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Controller;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.util.HtmlUtils;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> dev.hwi.domain.Chat;</span><br><span class="line"><span class="keyword">import</span> dev.hwi.domain.Greeting;</span><br><span class="line"><span class="keyword">import</span> dev.hwi.domain.HelloMessage;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GreetingController</span> {</span><br><span class="line"> <span class="meta">@MessageMapping("/hello")</span></span><br><span class="line"> <span class="meta">@SendTo("/topic/greetings")</span></span><br><span class="line"> <span class="keyword">public</span> Greeting <span class="title function_">greeting</span><span class="params">(HelloMessage message)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> Thread.sleep(<span class="number">100</span>); <span class="comment">// delay</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Greeting</span>(<span class="string">"Hello, "</span> + HtmlUtils.htmlEscape(message.getName()) + <span class="string">"!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>단순한 컨트롤러이지만 생소한 <code>annotation</code> 두 개가 보인다. <code>@MessageMapping</code>은 클라이언트에서 <code>/hello</code>쪽으로 메세지를 전달하면 <code>greeting</code>메서드가 실행된다. 그리고 이 메서드는 다시 <code>@SendTo</code> 어노테이션에 정의된 쪽으로 결과를 return시킨다. 예제에서는 <code>Thread.sleep(1000);</code>를 작성했지만 생략해도 무방하다. <code>@SendTo</code>로 return시킨 후에 클라이언트에서는 전달받은 값을 렌더링하여 브라우저에 표시해 줄 수 있는 상태가 된다.</p>
<h1 id="STOMP-관련-설정"><a href="#STOMP-관련-설정" class="headerlink" title="STOMP 관련 설정"></a>STOMP 관련 설정</h1><p>설정이 필요하므로 <code>WebSocketConfig.java</code>파일을 작성한다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> dev.hwi.config;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.context.annotation.Configuration;</span><br><span class="line"><span class="keyword">import</span> org.springframework.messaging.simp.config.MessageBrokerRegistry;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.socket.config.annotation.StompEndpointRegistry;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableWebSocketMessageBroker</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WebSocketConfig</span> <span class="keyword">implements</span> <span class="title class_">WebSocketMessageBrokerConfigurer</span> {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">configureMessageBroker</span><span class="params">(MessageBrokerRegistry registry)</span> {</span><br><span class="line"> registry.enableSimpleBroker(<span class="string">"/topic"</span>);</span><br><span class="line"> registry.setApplicationDestinationPrefixes(<span class="string">"/app"</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">registerStompEndpoints</span><span class="params">(StompEndpointRegistry registry)</span> {</span><br><span class="line"> registry.addEndpoint(<span class="string">"/websocket"</span>).withSockJS();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>@Configuration</code>, <code>@EnableWebSocketMessageBroker</code>를 통해 WebSocket과 관련된 설정을 작동시킨다. <code>WebSocketConfig</code> 클래스는 <code>WebSocketMessageBrokerConfigurer</code>를 <code>implements</code>하고 두 개의 메서드를 오버라이딩한다.</p>
<blockquote>
<p>메세지 브로커(Message Broker)는 송신자에게 수신자의 이전 메세지 프로토콜로 변환해주는 모듈 중에 하나이다. 요청이 오면 그에 해당하는 통신 채널로 전송해주고 응답 역시 왔던 길을 그대로 다시 가서 안정적으로 메세지를 응답해주기 위한 부분입니다.</p>
</blockquote>
<ul>
<li><code>configureMessageBroker()</code><ul>
<li>enableSimpleBroker는 클라이언트로 메세지를 응답해줄 때 prefix를 정의한다.</li>
<li>setApplicationDestinationPrefixes는 클라이언트에서 메세지 송신 시 붙여줄 prefix를 정의한다.</li>
</ul>
</li>
<li><code>registerStompEndpoints()</code><ul>
<li>최초 소켓 연결을 하는 경우, endpoint가 되는 url이다. 추후 javascript에서 SockJS 생성자를 통해 연결될 것이다.</li>
</ul>
</li>
</ul>
<h1 id="html-js-작성"><a href="#html-js-작성" class="headerlink" title="html, js 작성"></a>html, js 작성</h1><p><code>/src/main/resources/static/index.html</code></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><!DOCTYPE <span class="keyword">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>Hello WebSocket<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/webjars/bootstrap/css/bootstrap.min.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">link</span> <span class="attr">href</span>=<span class="string">"/main.css"</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/webjars/jquery/jquery.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/webjars/sockjs-client/sockjs.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/webjars/stomp-websocket/stomp.min.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/app.js"</span>></span><span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line"><span class="tag"><<span class="name">noscript</span>></span><span class="tag"><<span class="name">h2</span> <span class="attr">style</span>=<span class="string">"color: #ff0000"</span>></span>Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!<span class="tag"></<span class="name">h2</span>></span><span class="tag"></<span class="name">noscript</span>></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"main-content"</span> <span class="attr">class</span>=<span class="string">"container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"row"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"col-md-6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">form</span> <span class="attr">class</span>=<span class="string">"form-inline"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-group"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"connect"</span>></span>WebSocket connection:<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"connect"</span> <span class="attr">class</span>=<span class="string">"btn btn-default"</span> <span class="attr">type</span>=<span class="string">"submit"</span>></span>Connect<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"disconnect"</span> <span class="attr">class</span>=<span class="string">"btn btn-default"</span> <span class="attr">type</span>=<span class="string">"submit"</span> <span class="attr">disabled</span>=<span class="string">"disabled"</span>></span>Disconnect</span><br><span class="line"> <span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">form</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"col-md-6"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">form</span> <span class="attr">class</span>=<span class="string">"form-inline"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-group"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"name"</span>></span>What is your name?<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"name"</span> <span class="attr">class</span>=<span class="string">"form-control"</span> <span class="attr">placeholder</span>=<span class="string">"Your name here..."</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"send"</span> <span class="attr">class</span>=<span class="string">"btn btn-default"</span> <span class="attr">type</span>=<span class="string">"submit"</span>></span>Send<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">form</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"row"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"col-md-12"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">table</span> <span class="attr">id</span>=<span class="string">"conversation"</span> <span class="attr">class</span>=<span class="string">"table table-striped"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">thead</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>Greetings<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">thead</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tbody</span> <span class="attr">id</span>=<span class="string">"greetings"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tbody</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure>
<p><code>/src/main/resources/static/app.js</code></p>
<p>부가 내용은 주석으로 설명</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> stompClient = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">setConnected</span>(<span class="params">connected</span>) {</span><br><span class="line"> $(<span class="string">"#connect"</span>).<span class="title function_">prop</span>(<span class="string">"disabled"</span>, connected);</span><br><span class="line"> $(<span class="string">"#disconnect"</span>).<span class="title function_">prop</span>(<span class="string">"disabled"</span>, !connected);</span><br><span class="line"> <span class="keyword">if</span> (connected) {</span><br><span class="line"> $(<span class="string">"#conversation"</span>).<span class="title function_">show</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> $(<span class="string">"#conversation"</span>).<span class="title function_">hide</span>();</span><br><span class="line"> }</span><br><span class="line"> $(<span class="string">"#greetings"</span>).<span class="title function_">html</span>(<span class="string">""</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">connect</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">var</span> socket = <span class="keyword">new</span> <span class="title class_">SockJS</span>(<span class="string">'/websocket'</span>);</span><br><span class="line"> stompClient = <span class="title class_">Stomp</span>.<span class="title function_">over</span>(socket);</span><br><span class="line"> <span class="comment">// SockJS와 stomp client를 통해 연결을 시도.</span></span><br><span class="line"> stompClient.<span class="title function_">connect</span>({}, <span class="keyword">function</span> (<span class="params">frame</span>) {</span><br><span class="line"> <span class="title function_">setConnected</span>(<span class="literal">true</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Connected: '</span> + frame);</span><br><span class="line"> stompClient.<span class="title function_">subscribe</span>(<span class="string">'/topic/greetings'</span>, <span class="keyword">function</span> (<span class="params">greeting</span>) {</span><br><span class="line"> <span class="title function_">showGreeting</span>(<span class="title class_">JSON</span>.<span class="title function_">parse</span>(greeting.<span class="property">body</span>).<span class="property">content</span>);</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">disconnect</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (stompClient !== <span class="literal">null</span>) {</span><br><span class="line"> stompClient.<span class="title function_">disconnect</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_">setConnected</span>(<span class="literal">false</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Disconnected"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sendName</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// /app/hello로 JSON 파라미터를 메세지 body로 전송.</span></span><br><span class="line"> stompClient.<span class="title function_">send</span>(<span class="string">"/app/hello"</span>, {}, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>({<span class="string">'name'</span>: $(<span class="string">"#name"</span>).<span class="title function_">val</span>()}));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">showGreeting</span>(<span class="params">message</span>) {</span><br><span class="line"> $(<span class="string">"#greetings"</span>).<span class="title function_">append</span>(<span class="string">"<tr><td>"</span> + message + <span class="string">"</td></tr>"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> $(<span class="string">"form"</span>).<span class="title function_">on</span>(<span class="string">'submit'</span>, <span class="keyword">function</span> (<span class="params">e</span>) {</span><br><span class="line"> e.<span class="title function_">preventDefault</span>();</span><br><span class="line"> });</span><br><span class="line"> $( <span class="string">"#connect"</span> ).<span class="title function_">click</span>(<span class="keyword">function</span>(<span class="params"></span>) { <span class="title function_">connect</span>(); });</span><br><span class="line"> $( <span class="string">"#disconnect"</span> ).<span class="title function_">click</span>(<span class="keyword">function</span>(<span class="params"></span>) { <span class="title function_">disconnect</span>(); });</span><br><span class="line"> $( <span class="string">"#send"</span> ).<span class="title function_">click</span>(<span class="keyword">function</span>(<span class="params"></span>) { <span class="title function_">sendName</span>(); });</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<p>이후 작성한 프로젝트를 Server Start 한 후 확인한다.</p>
<p><img src="https://i.imgur.com/7JLA9iF.gif" alt="실행 후 개발자도구로 확인하는 모습"></p>
<p>여기까지가 공식가이드에서 보여주는 샘플이다. 진짜 채팅처럼(그래도 디자인은 자신이 없다..) ui를 약간만 추가해주자.</p>
<h1 id="간단한-채팅-어플리케이션-만들어보기"><a href="#간단한-채팅-어플리케이션-만들어보기" class="headerlink" title="간단한 채팅 어플리케이션 만들어보기"></a>간단한 채팅 어플리케이션 만들어보기</h1><p><code>/app/chat</code>으로 송신하여 <code>/topic/chat</code>으로 수신을 받아서 여러 명이 접속하는 경우까지 가정하여 작성해보도록 한다. 송신하는 데이터 포맷은 다음과 같다.</p>
<figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"hwiVeloper"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"message"</span><span class="punctuation">:</span> <span class="string">"안녕하세요."</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></table></figure>
<p>이를 위해 <code>Greeting.java</code>와 유사하게 메세지를 받고 전달해줄 domain class가 필요하다.</p>
<p><code>Chat.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> dev.hwi.domain;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Chat</span> {</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="keyword">private</span> String message;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Chat</span><span class="params">()</span> {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">Chat</span><span class="params">(String name, String message)</span> {</span><br><span class="line"> <span class="built_in">this</span>.name = name;</span><br><span class="line"> <span class="built_in">this</span>.message = message;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String <span class="title function_">getMessage</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> message;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>GreetingController.java</code>에는 메서드를 하나 추가해준다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@MessageMapping("/chat")</span></span><br><span class="line"><span class="meta">@SendTo("/topic/chat")</span></span><br><span class="line"><span class="keyword">public</span> Chat <span class="title function_">chat</span><span class="params">(Chat chat)</span> <span class="keyword">throws</span> Exception {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Chat</span>(chat.getName(), chat.getMessage());</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>동일하게 <code>@MessageMapping</code>과 <code>@SendTo</code> 어노테이션을 통해 매핑해주고 return시켜줄 내용을 작성해준다.</p>
<p><code>index.html</code></p>
<p>좀전 예제에서 이름을 입력했던 input 태그 밑에 다음과 같은 내용을 추가한다.</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"form-group"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">label</span> <span class="attr">for</span>=<span class="string">"message"</span>></span>Input Message<span class="tag"></<span class="name">label</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">id</span>=<span class="string">"chatMessage"</span> <span class="attr">class</span>=<span class="string">"form-control"</span> <span class="attr">placeholder</span>=<span class="string">"message.."</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"><<span class="name">button</span> <span class="attr">id</span>=<span class="string">"chatSend"</span> <span class="attr">class</span>=<span class="string">"btn btn-default"</span> <span class="attr">type</span>=<span class="string">"button"</span>></span>Chat Send<span class="tag"></<span class="name">button</span>></span></span><br></pre></td></tr></table></figure>
<p><code>app.js</code></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ... 앞에 생략 ...</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">connect</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">var</span> socket = <span class="keyword">new</span> <span class="title class_">SockJS</span>(<span class="string">'/websocket'</span>);</span><br><span class="line"> stompClient = <span class="title class_">Stomp</span>.<span class="title function_">over</span>(socket);</span><br><span class="line"> stompClient.<span class="title function_">connect</span>({}, <span class="keyword">function</span> (<span class="params">frame</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">/* 이 부분 추가 */</span></span><br><span class="line"> stompClient.<span class="title function_">subscribe</span>(<span class="string">'/topic/chat'</span>, <span class="keyword">function</span> (<span class="params">chat</span>) {</span><br><span class="line"> <span class="title function_">showChat</span>(<span class="title class_">JSON</span>.<span class="title function_">parse</span>(chat.<span class="property">body</span>));</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// ... 중략 ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Chat과 관련된 메서드 추가 */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">sendChat</span>(<span class="params"></span>) {</span><br><span class="line"> stompClient.<span class="title function_">send</span>(<span class="string">"/app/chat"</span>, {}, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>({<span class="string">'name'</span>: $(<span class="string">"#name"</span>).<span class="title function_">val</span>(), <span class="string">'message'</span>: $(<span class="string">"#chatMessage"</span>).<span class="title function_">val</span>()}));</span><br><span class="line">}</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">showChat</span>(<span class="params">chat</span>) {</span><br><span class="line"> $(<span class="string">"#greetings"</span>).<span class="title function_">append</span>(<span class="string">"<tr><td>"</span> + chat.<span class="property">name</span> + <span class="string">" : "</span> + chat.<span class="property">message</span> + <span class="string">"</td></tr>"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">$(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> </span><br><span class="line"> $( <span class="string">"#chatSend"</span> ).<span class="title function_">click</span>(<span class="keyword">function</span>(<span class="params"></span>){ <span class="title function_">sendChat</span>(); }); <span class="comment">// 추가</span></span><br><span class="line">});</span><br></pre></td></tr></table></figure>
<h1 id="테스트"><a href="#테스트" class="headerlink" title="테스트"></a>테스트</h1><p>이제 브라우저 세션을 여러 개 띄운 후 채팅하듯이 테스트를 해본다.</p>
<p><img src="https://i.imgur.com/UgZ6Rn0.gif" alt="4개의 브라우저에서 테스트하는 모습"></p>
<h1 id="정리"><a href="#정리" class="headerlink" title="정리"></a>정리</h1><p><code>STOMP</code>기반의 메세징 서비스에 대해 알아보고 이를 <code>Spring Boot</code>에서 어떻게 사용하는지를 알아보았다.</p>
<h1 id="소스코드"><a href="#소스코드" class="headerlink" title="소스코드"></a>소스코드</h1><p><a target="_blank" rel="noopener" href="https://github.com/hwiVeloper/SpringBootStudy/tree/master/spring-boot-websocket">https://github.com/hwiVeloper/SpringBootStudy/tree/master/spring-boot-websocket</a></p>
<h1 id="참고"><a href="#참고" class="headerlink" title="참고"></a>참고</h1><p><a target="_blank" rel="noopener" href="http://spring.io/guides/gs/messaging-stomp-websocket">http://spring.io/guides/gs/messaging-stomp-websocket</a><br><a target="_blank" rel="noopener" href="https://zetawiki.com/wiki/STOMP">https://zetawiki.com/wiki/STOMP</a><br><a target="_blank" rel="noopener" href="https://stomp.github.io/">https://stomp.github.io/</a><br><a target="_blank" rel="noopener" href="http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3.5:ptl:stomp">http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3.5:ptl:stomp</a></p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2019-01-07T05:05:44.000Z" title="2019. 1. 7. 오후 2:05:44">2019-01-07</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.014Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">5분안에 읽기 (약 762 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2019/01/07/java-stringjoiner/">Java StringJoiner 사용하기</a></h1><div class="content"><p>Java에서 문자열 사이에 특정한 기호로 연결시키고 싶을 경우가 간혹 생긴다. 보통이라면 <code>for</code>루프를 통해서 간단하게 만들 수 있을 것이다.</p>
<p><code>{"아이언맨", "캡틴아메리카", "블랙위도우", "헐크", "토르", "호크아이"}</code>와 같은 배열이 있다고 생각하자 이들을 <code>-</code>로 연결하겠다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringJoinerExample</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ex1</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">DELIMITER</span> <span class="operator">=</span> <span class="string">"-"</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="type">StringBuilder</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line"> String[] strArr = {<span class="string">"아이언맨"</span>, <span class="string">"캡틴아메리카"</span>, <span class="string">"블랙위도우"</span>, <span class="string">"헐크"</span>, <span class="string">"토르"</span>, <span class="string">"호크아이"</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < strArr.length; i++) {</span><br><span class="line"> result.append(strArr[i]);</span><br><span class="line"> <span class="keyword">if</span> (i < strArr.length - <span class="number">1</span>) { <span class="comment">// 마지막 이전까지 delimiter 를 append.</span></span><br><span class="line"> result.append(DELIMITER);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(result.toString());</span><br><span class="line"> <span class="comment">// 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>문제 없이 실행되는 소스코드이지만 뭔가 더 간결하게 하고 싶은 욕심이 생긴다. <code>StringJoiner</code>를 사용하면 이를 좀 더 깔끔하게 작성이 가능하다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.StringJoiner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringJoinerExample</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ex2</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">joiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">"-"</span>);</span><br><span class="line"> </span><br><span class="line"> String[] strArr = {<span class="string">"아이언맨"</span>, <span class="string">"캡틴아메리카"</span>, <span class="string">"블랙위도우"</span>, <span class="string">"헐크"</span>, <span class="string">"토르"</span>, <span class="string">"호크아이"</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < strArr.length; i++) {</span><br><span class="line"> joiner.add(strArr[i]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(joiner.toString());</span><br><span class="line"> <span class="comment">// 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>StringJoiner</code>는 문자열 생성자에서 선언한 구분자를 <code>add</code>시켜주면서 <code>StringBuilder</code>와 유사한 효과를 낸다. 차이점이 있다면 <code>prefix(접두사)</code>와 <code>suffix(접미사)</code>까지 선언이 가능하다는 점이다.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.StringJoiner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringJoinerExample</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ex3</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">joiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">"-"</span>, <span class="string">"[ "</span>, <span class="string">" ]"</span>);</span><br><span class="line"> </span><br><span class="line"> String[] strArr = {<span class="string">"아이언맨"</span>, <span class="string">"캡틴아메리카"</span>, <span class="string">"블랙위도우"</span>, <span class="string">"헐크"</span>, <span class="string">"토르"</span>, <span class="string">"호크아이"</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < strArr.length; i++) {</span><br><span class="line"> joiner.add(strArr[i]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"AVENGERS"</span>);</span><br><span class="line"> System.out.println(joiner.toString());</span><br><span class="line"> <span class="comment">// AVENGERS</span></span><br><span class="line"> <span class="comment">// [ 아이언맨-캡틴아메리카-블랙위도우-헐크-토르-호크아이 ]</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>StringJoiner</code>는 이번에 암호화폐 거래소 open api를 사용하면서 알게된 클래스이다. ETH-BTC, SNT-BTC처럼 적게는 몇 개, 많게는 백 개가 넘는 마켓코드를 <code>,</code>를 통해 쉽게 이어 붙이는 방법을 찾다가 발견하게 된 것이다. 앞으로도 유용하게 쓰일 것 같다.</p>
<p>마지막으로 <code>StringJoiner</code>를 활용하여 배열을 <code>json</code>형태로 만드는 예제를 살펴보자.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.StringJoiner;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StringJoinerExample</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ex4</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">jsonJoiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">",\n\t"</span>, <span class="string">"{\n\t"</span>, <span class="string">"\n}"</span>);</span><br><span class="line"></span><br><span class="line"> String[] strArr = {<span class="string">"아이언맨"</span>, <span class="string">"캡틴아메리카"</span>, <span class="string">"블랙위도우"</span>, <span class="string">"헐크"</span>, <span class="string">"토르"</span>, <span class="string">"호크아이"</span>};</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < strArr.length; i++) {</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">keyJoiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">""</span>, <span class="string">"\""</span>, <span class="string">"\""</span>);</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">valueJoiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">""</span>, <span class="string">"\""</span>, <span class="string">"\""</span>);</span><br><span class="line"> <span class="type">StringJoiner</span> <span class="variable">keyValueJoiner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringJoiner</span>(<span class="string">" : "</span>);</span><br><span class="line"> </span><br><span class="line"> keyJoiner.add(<span class="string">"hero"</span> + i);</span><br><span class="line"> valueJoiner.add(strArr[i]);</span><br><span class="line"> </span><br><span class="line"> keyValueJoiner.add(keyJoiner.toString());</span><br><span class="line"> keyValueJoiner.add(valueJoiner.toString());</span><br><span class="line"> </span><br><span class="line"> jsonJoiner.add(keyValueJoiner.toString());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> System.out.println(<span class="string">"AVENGERS JSON RESULT"</span>);</span><br><span class="line"> System.out.println(jsonJoiner.toString());</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> AVENGERS JSON RESULT</span></span><br><span class="line"><span class="comment"> {</span></span><br><span class="line"><span class="comment"> "hero0" : "아이언맨",</span></span><br><span class="line"><span class="comment"> "hero1" : "캡틴아메리카",</span></span><br><span class="line"><span class="comment"> "hero2" : "블랙위도우",</span></span><br><span class="line"><span class="comment"> "hero3" : "헐크",</span></span><br><span class="line"><span class="comment"> "hero4" : "토르",</span></span><br><span class="line"><span class="comment"> "hero5" : "호크아이"</span></span><br><span class="line"><span class="comment"> }</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><code>StringBuilder</code>, 또는 기타 문자열을 생성하는 클래스와 잘 조합해서 쓴다면 다양한 방면으로 문자열을 만들어 볼 수 있을 것 같다.</p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2018-12-31T08:47:56.000Z" title="2018. 12. 31. 오후 5:47:56">2018-12-31</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.014Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/Scribble/">Scribble</a></span><span class="level-item">13분안에 읽기 (약 1899 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2018/12/31/2018-memoir/">돌아보는 2018</a></h1><div class="content"><p>어쩌다보니 2018년의 처음이자 마지막 포스팅이 되었다. 정비도 어느정도 마쳤으니, 내년에는 좀 더 부지런하게 기록을 남겨야겠다. 올 한 해는 이런저런 공부를 해야지 하고 다짐했던 1월 1일이 아직도 생생한데 벌써 이렇게 1년이 훌쩍 가버렸다. 그동안 이룬 것도 있었지만, 이루지 못한 잔여물을 보면서 일부 반성한다.</p>
<h1 id="이직"><a href="#이직" class="headerlink" title="이직"></a>이직</h1><p>이직, 이직 1년동안 노래를 불렀는데 드디어 하게 되었다.</p>
<p>아마 한 해 동안 가장 큰 변화가 아닌가 싶다. SI를 떠나서 UI 솔루션 회사로 이직했다. 이직하면서 아쉬운 부분도 있었지만(💰) 전에 몸담던 곳보다 큰 규모로 옮기게 되어 뿌듯했다. 6월에 <code>투비소프트 프리세일즈팀</code>으로 합류하여 내부 및 외부 <code>POC(Proof of Concept)</code>를 수행하면서 회사 제품에 대해 알아가는 단계이다. <code>세일즈</code>라는 단어 때문에 많은 고민을 했지만 결정 후 이곳에서 내가 할 일이 생각보다 많을 것 같다는 생각이 요즘 부쩍 든다. 선배 개발자와 함께 POC를 수행한 적도 있지만 최근 팀 여건상 팀에서 혼자 + 다른팀 분과 함께 수행한 POC는 힘들었지만 기억에 남을 한 줄 이력이 될 것 같다. <code>2019</code>년에도 많은 POC가 있을텐데 별탈없이 잘 수행할 수 있길 바라며…</p>
<p><code>BMT(Benchmark Test)</code>와 <code>POC(Proof of Concept)</code>라는 말은 입사 당시 생소한 말이었다. 쉽게 말해서 제품의 기능 및 성능 검증을 통해 제품 수주를 위해 수행하는 일종의 작은 파일럿 프로젝트이다. 짧으면 1주, 길면 한 두달씩 진행되는데, 고객이 요구하는 제품의 기능과 일정 수준 이상의 성능을 보여주는 샘플 페이지 개발과 시연 등이 주된 일이다.</p>
<h1 id="여행"><a href="#여행" class="headerlink" title="여행"></a>여행</h1><blockquote>
<p>작년에 다녀온 오사카를 기점으로 해외여행에 맛들려서 올해만 3번 정도 해외로 여행을 다녀왔다.</p>
</blockquote>
<ul>
<li>대만 🇹🇼 : 4월에 다녀왔음에도 불구하고 매우 덥고 습한 날씨로 고생한 곳이다. 일행이 몸이 안좋아지는 바람에 더 많이 못 먹고 더 많이 못 둘러 본 것이 다소 아쉬웠지만 음식도 맛있는 편이었고, 소소한 즐거움이 있던 곳이다. 특히 <code>예류지질공원</code>은 대만에서 내가 꼽은 베스트 여행지다.</li>
<li>후쿠오카 🇯🇵 : 6월에 다녀왔는데, <code>유후인</code>을 위주로 여행루트를 짰다. 버스에서 가방도 잃어버려서 애먹는 상황도 있었고, 버스 표 날짜를 잘못 티케팅 하는 바람에 스스로 많이 자책했던 여행(ㅋㅋㅋ). 유후인은 정말 힐링되는 평화롭고 아늑한 곳이었다.</li>
<li>도쿄 🇯🇵 : 아이폰 XS Max를 사는 것이 또 하나의 목표였던 여행. 추석 끼면서 꽤 오래 다녀왔는데 도쿄 전역을 다 돌아다니느라 생각보다 빡빡한 일정이 되었다. 하루 2만보 이상 걸을 정도이니…😵</li>
</ul>
<h1 id="개발"><a href="#개발" class="headerlink" title="개발"></a>개발</h1><p>개인 프로젝트를 많이 하려고 했지만, 이직 준비와 입사 초기 적응 기간동안 정신 없던 관계로(핑계) 많은 성과를 못 내서 아쉽다. 내년에는 새로운 것을 배우는 시간보다 그동안 익힌 것을 기반으로 많이 만들어보고 부딪히며 성장하는 한 해를 다짐해본다.</p>
<ul>
<li><input checked="" disabled="" type="checkbox"> Java 기초 다시보기 : 2년차때부터 지속적으로 느낀 이유로 하게 되었다. 버전업이 계속 되는 <code>Java</code>이기 때문에 앞으로도 꾸준히 해야하는 부분이라고 생각한다.</li>
<li><input checked="" disabled="" type="checkbox"> Spring, Spring boot : <code>Java</code>를 기반으로 프레임워크 역시 학습하게 되었다. <code>Spring Boot</code>는 접할 당시 신세계였고, 현재 <a target="_blank" rel="noopener" href="https://www.github.com/ruden91">@ruden91</a> 과 함께 미니 프로젝트를 진행중이다. 책으로 배운 내용 외에도 많은 부분을 다뤄볼 예정이다.</li>
<li><input disabled="" type="checkbox"> iOS 앱 개발 : 초기학습은 끝냈는데, 실제 앱 개발 및 스토어 배포를 아직 시행하지 못한 아쉬움이 있다. 2019년에는 반드시 해내리라…</li>
<li><input checked="" disabled="" type="checkbox"> React 학습 : Velopert님의 리액트 강의를 보며 학습하고 있다. 더불어 리액트 교과서도 함께 보고 있는데, 조금씩 어려움을 느끼고 있지만 꾸준한 학습으로 극복해내고 있는 중이다. 하던 학습 마저 끝내고 실전으로 들어가서 <code>React + Spring boot</code> 로 어플리케이션을 개발하고 싶다.</li>
</ul>
<h1 id="2019"><a href="#2019" class="headerlink" title="2019"></a>2019</h1><p>앞서 언급했듯이 2019년에는 새로운 걸 학습하는 비중을 대폭 줄이고, 공부하고 다져낸 것을 기반으로 실제 개발해보는 것이 주된 목표이다. 학습만 하고 끝낸다면 학습의 의미가 점차 사라지고 시간이 지나면 까먹게 되기 때문에 이와 같은 다짐을 했다.</p>
<ul>
<li>NP17 : <code>1순위</code>. 개인적인 공부도 중요하지만 회사 업무가 최우선이라고 생각한다.</li>
<li>Java 기초 학습 : 기존 내용과 새로운 버전에 나오는 내용을 숙지하고 내가 개발자를 계속 하면서 Java가 사라지지 않는 한 계속될 내용일 듯 싶다.</li>
<li>알고리즘 & 자료 구조 : 요즘 가장 부족하다고 느끼는 부분이다. 컴퓨터공학 전공을 하지 않은 것도 있지만, 언어 위주로만 학습하고 개발해왔기 때문에 내가 가진 능력치 중 가장 떨어진다. <code>종만북</code>이라고 불리는 서적을 사서 차근차근 익혀보고 온라인 코딩 테스트(<code>Codewars</code>, <code>백준</code> 등등..)에 적용할 계획이다.</li>
<li>iOS 앱 개발 : 올해 못 다 이룬 업적을 꼭 이뤄내겠다.</li>
<li>React + Spring Boot : 1인 <code>Frontend/Backend</code> 개발하여 작은 서비스 하나 출시해보고 싶다.</li>
<li>AWS : 최근들어 매력을 느낀 <code>AWS</code>. 개인 프로젝트는 AWS 환경에서 대부분 처리하는 방향으로 진행하며 AWS에 대해 좀 더 알아가는 한 해가 될 수 있길 바란다.</li>
<li>블로그 : 2018년에 글 한 개 없이 방치된 나의 블로그. 2019년에 다시 활성화 해보겠다.</li>
<li>주변 챙기기 : 매년 마음속으로 하는 다짐이지만 매번 부족하다고 생각되는 부분이다. 바쁜 와중에도 1분이라도 투자하여 주변사람들 잘 챙기자.</li>
<li>견문 : 여행도 많이 다니고, 세미나와 컨퍼런스도 올해보다 더 많이 참석해서 세상을 보는 눈이 넓어지는 한 해가 되길 바란다.</li>
</ul>
<h1 id="마무리하며…"><a href="#마무리하며…" class="headerlink" title="마무리하며…"></a>마무리하며…</h1><p>쓰다보니 주저리주저리가 된 느낌이다. 내년 이맘때쯤 이 글을 보면서 한 해를 알차게 보냈는지 체크해 볼 것이다. 앞서 많은 다짐을 기록했지만 무엇보다 중요한 건 <code>건강</code>. 혹 이 글을 보게 되는 여러분도 건강한 한 해가 되길 바라며.. 세상의 모든 개발자분들 화이팅!</p>
</div></article></div><div class="card"><article class="card-content article" role="article"><div class="article-meta is-size-7 is-uppercase level is-mobile"><div class="level-left"><span class="level-item"><time dateTime="2017-10-10T14:54:06.000Z" title="2017. 10. 10. 오후 11:54:06">2017-10-10</time> 게시 됨</span><span class="level-item"><time dateTime="2022-12-29T06:52:52.014Z" title="2022. 12. 29. 오후 3:52:52">2022-12-29</time> 업데이트 됨</span><span class="level-item"><a class="link-muted" href="/categories/java/">java</a></span><span class="level-item">3분안에 읽기 (약 388 단어)</span></div></div><h1 class="title is-3 is-size-4-mobile"><a class="link-muted" href="/2017/10/10/java-jdbc-db-connection-datasource/">jsp db connection 설정 (datasource)</a></h1><div class="content"><h1 id="라이브러리"><a href="#라이브러리" class="headerlink" title="라이브러리"></a>라이브러리</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">commons-collections4-4.1.jar</span><br><span class="line">commons-dbcp2-2.1.1.jar</span><br><span class="line">commons-pool2.2.4.2.jar</span><br><span class="line">mysql-connector-java-5.1.44-bin.jar</span><br></pre></td></tr></table></figure>
<p>4개 파일 다운 후 <code>WebContent/WEB-INF/lib</code>에 복사</p>
<p>mysql connector는</p>
<h1 id="프로젝트-web-xml-생성"><a href="#프로젝트-web-xml-생성" class="headerlink" title="프로젝트 web.xml 생성"></a>프로젝트 web.xml 생성</h1><p>프로젝트 우클릭 - Java EE Tools - Generate Deployment Descriptor Stub -> <code>WebContent/WEB-INF/web.xml</code> 생성 확인</p>
<p>아래 내용 추가</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- web.xml --></span></span><br><span class="line"><span class="tag"><<span class="name">resource-ref</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">description</span>></span>DB Connection<span class="tag"></<span class="name">description</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">res-ref-name</span>></span>jdbc/mysql<span class="tag"></<span class="name">res-ref-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">res-auth</span>></span>Container<span class="tag"></<span class="name">res-auth</span>></span></span><br><span class="line"><span class="tag"></<span class="name">resource-ref</span>></span></span><br></pre></td></tr></table></figure>
<h1 id="톰캣-서버-context-xml-설정"><a href="#톰캣-서버-context-xml-설정" class="headerlink" title="톰캣 서버 context.xml 설정"></a>톰캣 서버 context.xml 설정</h1><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- context.xml --></span></span><br><span class="line"><span class="tag"><<span class="name">Resource</span> <span class="attr">name</span>=<span class="string">"jdbc/mysql"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">auth</span>=<span class="string">"Container"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">type</span>=<span class="string">"javax.sql.DataSource"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">maxActive</span>=<span class="string">"100"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">maxIdle</span>=<span class="string">"30"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">maxWait</span>=<span class="string">"10000"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">username</span>=<span class="string">"username"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">password</span>=<span class="string">"userpass"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">driverClassName</span>=<span class="string">"com.mysql.jdbc.Driver"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">url</span>=<span class="string">"jdbc:mysql://hostname:port/dbname"</span>/></span></span><br><span class="line"><!—- 빨간글씨는 바꿔야 하는 부분 —-></span><br></pre></td></tr></table></figure>
<h1 id="dao-생성자"><a href="#dao-생성자" class="headerlink" title="dao 생성자"></a>dao 생성자</h1><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"><span class="keyword">import</span> java.sql.Timestamp;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.naming.Context;</span><br><span class="line"><span class="keyword">import</span> javax.naming.InitialContext;</span><br><span class="line"><span class="keyword">import</span> javax.naming.NamingException;</span><br><span class="line"><span class="keyword">import</span> javax.sql.DataSource;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.Connection;</span><br><span class="line"><span class="keyword">import</span> java.sql.PreparedStatement;</span><br><span class="line"><span class="keyword">import</span> java.sql.ResultSet;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BDao</span> {</span><br><span class="line"> <span class="type">Connection</span> <span class="variable">connection</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="type">PreparedStatement</span> <span class="variable">ps</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> <span class="type">ResultSet</span> <span class="variable">rs</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">BDao</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> <span class="type">Context</span> <span class="variable">init</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InitialContext</span>();</span><br><span class="line"> <span class="type">Context</span> <span class="variable">env</span> <span class="operator">=</span> (Context)init.lookup(<span class="string">"java:comp/env"</span>);</span><br><span class="line"> <span class="type">DataSource</span> <span class="variable">ds</span> <span class="operator">=</span> (DataSource)env.lookup(<span class="string">"jdbc/mysql"</span>);</span><br><span class="line"> connection = ds.getConnection();</span><br><span class="line"> System.out.println(<span class="string">"db connection success!!"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (NamingException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">catch</span> (SQLException e) {</span><br><span class="line"> <span class="comment">// TODO Auto-generated catch block</span></span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 각 method 에서 connection, ps, rs 를 close() 해주는 것을 잊지 말자</span></span><br></pre></td></tr></table></figure>
<h1 id="주의사항"><a href="#주의사항" class="headerlink" title="주의사항"></a>주의사항</h1><p>mysql connector는 소스쪽과 서버쪽 폴더 동시에 적용시켜야한다.(복사)</p>
<ul>
<li>위와 같이 했음에도 아래와 같이 에러가 나는 경우<ul>
<li>심각: Servlet.service() for servlet [com.javalec.ex.frontcontroller.BFrontContrller] in context with path [/first_test] threw exception [Servlet execution threw an exception] with root cause</li>
<li>위에설치한 4개의jar파일중 버전이 맞지 않아서 생기는 에러(ex. mysql-connector-java-5.1.44-bin.jar이 낮아서 생기는 에러였음)</li>
</ul>
</li>
<li>최신버전으로 다운로드 후 다시 WebContent/WEB-INF/lib에 복사</li>
<li>아파치 톰캣 설치한 경로에도 mysql-connector jar파일을 복사해주어야 한다.</li>
</ul>
</div></article></div><nav class="pagination" role="navigation" aria-label="pagination"><div class="pagination-previous is-invisible is-hidden-mobile"><a href="/page/0/">이전</a></div><div class="pagination-next"><a href="/page/2/">다음</a></div><ul class="pagination-list is-hidden-mobile"><li><a class="pagination-link is-current" href="/">1</a></li><li><a class="pagination-link" href="/page/2/">2</a></li><li><a class="pagination-link" href="/page/3/">3</a></li><li><a class="pagination-link" href="/page/4/">4</a></li></ul></nav></div><div class="column column-left is-4-tablet is-4-desktop is-4-widescreen order-1"><div class="card widget" data-type="profile"><div class="card-content"><nav class="level"><div class="level-item has-text-centered flex-shrink-1"><div><figure class="image is-128x128 mx-auto mb-2"><img class="avatar is-rounded" src="https:/github.com/hwiVeloper.png" alt="hwiVeloper"></figure><p class="title is-size-4 is-block" style="line-height:inherit;">hwiVeloper</p><p class="is-size-6 is-block">dev note</p><p class="is-size-6 is-flex justify-content-center"><i class="fas fa-map-marker-alt mr-1"></i><span>South Korea</span></p></div></div></nav><nav class="level is-mobile"><div class="level-item has-text-centered is-marginless"><div><p class="heading">포스트</p><a href="/archives"><p class="title">36</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">카테고리</p><a href="/categories"><p class="title">10</p></a></div></div><div class="level-item has-text-centered is-marginless"><div><p class="heading">태그</p><a href="/tags"><p class="title">60</p></a></div></div></nav><div class="level"><a class="level-item button is-primary is-rounded" href="https://github.com/ppoffice" target="_blank" rel="noopener">팔로우</a></div><div class="level is-mobile is-multiline"><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Github" href="https://github.com/ppoffice"><i class="fab fa-github"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Facebook" href="https://facebook.com"><i class="fab fa-facebook"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Twitter" href="https://twitter.com"><i class="fab fa-twitter"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="Dribbble" href="https://dribbble.com"><i class="fab fa-dribbble"></i></a><a class="level-item button is-transparent is-marginless" target="_blank" rel="noopener" title="RSS" href="/"><i class="fas fa-rss"></i></a></div></div></div><!--!--><div class="card widget" data-type="recent-posts"><div class="card-content"><h3 class="menu-label">최근 글</h3><article class="media"><figure class="media-left"><a class="image" href="/2019/10/28/2nd-job-flipping/"><img src="https://i.imgur.com/E08CWBZ.png" alt="두 번째 이직"></a></figure><div class="media-content"><p class="date"><time dateTime="2019-10-28T05:42:29.000Z">2019-10-28</time></p><p class="title"><a href="/2019/10/28/2nd-job-flipping/">두 번째 이직</a></p><p class="categories"><a href="/categories/Scribble/">Scribble</a></p></div></article><article class="media"><figure class="media-left"><a class="image" href="/2019/04/21/spring-boot-oauth2-authserver/"><img src="https://i.imgur.com/WTlXjeF.jpg" alt="스프링부트 OAuth2 - 3. OAuth 인증 서버 구축"></a></figure><div class="media-content"><p class="date"><time dateTime="2019-04-21T09:12:00.000Z">2019-04-21</time></p><p class="title"><a href="/2019/04/21/spring-boot-oauth2-authserver/">스프링부트 OAuth2 - 3. OAuth 인증 서버 구축</a></p><p class="categories"><a href="/categories/java/">java</a></p></div></article><article class="media"><figure class="media-left"><a class="image" href="/2019/04/08/spring-boot-oauth2-github/"><img src="https://i.imgur.com/WTlXjeF.jpg" alt="스프링부트 OAuth2 - 2. Github 인증"></a></figure><div class="media-content"><p class="date"><time dateTime="2019-04-08T07:30:00.000Z">2019-04-08</time></p><p class="title"><a href="/2019/04/08/spring-boot-oauth2-github/">스프링부트 OAuth2 - 2. Github 인증</a></p><p class="categories"><a href="/categories/java/">java</a></p></div></article><article class="media"><figure class="media-left"><a class="image" href="/2019/04/05/spring-boot-oauth2-facebook/"><img src="https://i.imgur.com/WTlXjeF.jpg" alt="스프링부트 OAuth2 - 1. Facebook 인증"></a></figure><div class="media-content"><p class="date"><time dateTime="2019-04-05T07:30:53.000Z">2019-04-05</time></p><p class="title"><a href="/2019/04/05/spring-boot-oauth2-facebook/">스프링부트 OAuth2 - 1. Facebook 인증</a></p><p class="categories"><a href="/categories/java/">java</a></p></div></article><article class="media"><figure class="media-left"><a class="image" href="/2019/01/16/spring-boot-scheduler/"><img src="https://i.imgur.com/WTlXjeF.jpg" alt="스프링부트 스케쥴러"></a></figure><div class="media-content"><p class="date"><time dateTime="2019-01-16T14:39:53.000Z">2019-01-16</time></p><p class="title"><a href="/2019/01/16/spring-boot-scheduler/">스프링부트 스케쥴러</a></p><p class="categories"><a href="/categories/java/">java</a></p></div></article></div></div><div class="card widget" data-type="categories"><div class="card-content"><div class="menu"><h3 class="menu-label">카테고리</h3><ul class="menu-list"><li><a class="level is-mobile" href="/categories/Review/"><span class="level-start"><span class="level-item">Review</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/Scribble/"><span class="level-start"><span class="level-item">Scribble</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/categories/Software/"><span class="level-start"><span class="level-item">Software</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/java/"><span class="level-start"><span class="level-item">java</span></span><span class="level-end"><span class="level-item tag">7</span></span></a></li><li><a class="level is-mobile" href="/categories/javascript/"><span class="level-start"><span class="level-item">javascript</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/mysql/"><span class="level-start"><span class="level-item">mysql</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/categories/php/"><span class="level-start"><span class="level-item">php</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/categories/raspberrypi/"><span class="level-start"><span class="level-item">raspberrypi</span></span><span class="level-end"><span class="level-item tag">8</span></span></a></li><li><a class="level is-mobile" href="/categories/swift/"><span class="level-start"><span class="level-item">swift</span></span><span class="level-end"><span class="level-item tag">9</span></span></a></li><li><a class="level is-mobile" href="/categories/tools/"><span class="level-start"><span class="level-item">tools</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li></ul></div></div></div><div class="card widget" data-type="archives"><div class="card-content"><div class="menu"><h3 class="menu-label">아카이브</h3><ul class="menu-list"><li><a class="level is-mobile" href="/archives/2019/10/"><span class="level-start"><span class="level-item">10월 2019</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/04/"><span class="level-start"><span class="level-item">4월 2019</span></span><span class="level-end"><span class="level-item tag">3</span></span></a></li><li><a class="level is-mobile" href="/archives/2019/01/"><span class="level-start"><span class="level-item">1월 2019</span></span><span class="level-end"><span class="level-item tag">4</span></span></a></li><li><a class="level is-mobile" href="/archives/2018/12/"><span class="level-start"><span class="level-item">12월 2018</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2017/10/"><span class="level-start"><span class="level-item">10월 2017</span></span><span class="level-end"><span class="level-item tag">1</span></span></a></li><li><a class="level is-mobile" href="/archives/2017/08/"><span class="level-start"><span class="level-item">8월 2017</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2017/07/"><span class="level-start"><span class="level-item">7월 2017</span></span><span class="level-end"><span class="level-item tag">5</span></span></a></li><li><a class="level is-mobile" href="/archives/2017/06/"><span class="level-start"><span class="level-item">6월 2017</span></span><span class="level-end"><span class="level-item tag">2</span></span></a></li><li><a class="level is-mobile" href="/archives/2017/05/"><span class="level-start"><span class="level-item">5월 2017</span></span><span class="level-end"><span class="level-item tag">17</span></span></a></li></ul></div></div></div><div class="card widget" data-type="tags"><div class="card-content"><div class="menu"><h3 class="menu-label">태그</h3><div class="field is-grouped is-grouped-multiline"><div class="control"><a class="tags has-addons" href="/tags/2018/"><span class="tag">2018</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/apm/"><span class="tag">apm</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/atom/"><span class="tag">atom</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/automount/"><span class="tag">automount</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/book/"><span class="tag">book</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/codeigniter/"><span class="tag">codeigniter</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/composer/"><span class="tag">composer</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/database/"><span class="tag">database</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/datasource/"><span class="tag">datasource</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/date/"><span class="tag">date</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/db-connection/"><span class="tag">db-connection</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/developer/"><span class="tag">developer</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/devtools/"><span class="tag">devtools</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/facebook/"><span class="tag">facebook</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ftp/"><span class="tag">ftp</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/github/"><span class="tag">github</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/iOS/"><span class="tag">iOS</span><span class="tag">9</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ide/"><span class="tag">ide</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/installer/"><span class="tag">installer</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/java/"><span class="tag">java</span><span class="tag">7</span></a></div><div class="control"><a class="tags has-addons" href="/tags/javascript/"><span class="tag">javascript</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/laravel/"><span class="tag">laravel</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/log/"><span class="tag">log</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/logrotate/"><span class="tag">logrotate</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/mariadb/"><span class="tag">mariadb</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/memoir/"><span class="tag">memoir</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/messaging/"><span class="tag">messaging</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/multi-cursor/"><span class="tag">multi-cursor</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/mvc/"><span class="tag">mvc</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/mysql/"><span class="tag">mysql</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/nginx/"><span class="tag">nginx</span><span class="tag">2</span></a></div><div class="control"><a class="tags has-addons" href="/tags/oauth2/"><span class="tag">oauth2</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/obj-c/"><span class="tag">obj-c</span><span class="tag">9</span></a></div><div class="control"><a class="tags has-addons" href="/tags/objective-c/"><span class="tag">objective-c</span><span class="tag">9</span></a></div><div class="control"><a class="tags has-addons" href="/tags/order-by/"><span class="tag">order by</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/php/"><span class="tag">php</span><span class="tag">4</span></a></div><div class="control"><a class="tags has-addons" href="/tags/php7/"><span class="tag">php7</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/php7-fpm/"><span class="tag">php7-fpm</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/pigments/"><span class="tag">pigments</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/proftpd/"><span class="tag">proftpd</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/raspberrypi/"><span class="tag">raspberrypi</span><span class="tag">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/raspbian/"><span class="tag">raspbian</span><span class="tag">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/remote-ftp/"><span class="tag">remote-ftp</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/review/"><span class="tag">review</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/scheduler/"><span class="tag">scheduler</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/spring/"><span class="tag">spring</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/spring-boot/"><span class="tag">spring-boot</span><span class="tag">5</span></a></div><div class="control"><a class="tags has-addons" href="/tags/spring-security/"><span class="tag">spring-security</span><span class="tag">3</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ssh/"><span class="tag">ssh</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/stomp/"><span class="tag">stomp</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/stringbuilder/"><span class="tag">stringbuilder</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/stringjoiner/"><span class="tag">stringjoiner</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/swift/"><span class="tag">swift</span><span class="tag">9</span></a></div><div class="control"><a class="tags has-addons" href="/tags/torrent/"><span class="tag">torrent</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/transmission/"><span class="tag">transmission</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/ubuntu/"><span class="tag">ubuntu</span><span class="tag">8</span></a></div><div class="control"><a class="tags has-addons" href="/tags/websocket/"><span class="tag">websocket</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%EA%B0%9C%EB%B0%9C%EC%9E%90/"><span class="tag">개발자</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%EC%9D%B4%EC%A7%81/"><span class="tag">이직</span><span class="tag">1</span></a></div><div class="control"><a class="tags has-addons" href="/tags/%ED%95%9C%EB%B9%9B%EB%AF%B8%EB%94%94%EC%96%B4/"><span class="tag">한빛미디어</span><span class="tag">1</span></a></div></div></div></div></div></div><!--!--></div></div></section><footer class="footer"><div class="container"><div class="level"><div class="level-start"><a class="footer-logo is-block mb-2" href="/"><img src="https://github.com/hwiVeloper.png" alt="hwiVeloper" height="28"></a><p class="is-size-7"><span>© 2022 hwiVeloper</span> Powered by <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a> & <a href="https://github.com/ppoffice/hexo-theme-icarus" target="_blank" rel="noopener">Icarus</a></p></div><div class="level-end"><div class="field has-addons"><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Creative Commons" href="https://creativecommons.org/"><i class="fab fa-creative-commons"></i></a></p><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Attribution 4.0 International" href="https://creativecommons.org/licenses/by/4.0/"><i class="fab fa-creative-commons-by"></i></a></p><p class="control"><a class="button is-transparent is-large" target="_blank" rel="noopener" title="Download on GitHub" href="https://github.com/ppoffice/hexo-theme-icarus"><i class="fab fa-github"></i></a></p></div></div></div></div></footer><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/min/moment-with-locales.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/clipboard.min.js" defer></script><script>moment.locale("ko");</script><script>var IcarusThemeSettings = {
article: {
highlight: {
clipboard: true,
fold: 'unfolded'
}
}
};</script><script src="/js/column.js"></script><script src="/js/animation.js"></script><a id="back-to-top" title="맨 위로" href="javascript:;"><i class="fas fa-chevron-up"></i></a><script src="/js/back_to_top.js" defer></script><!--!--><!--!--><!--!--><script src="https://cdn.jsdelivr.net/npm/[email protected]/build/cookieconsent.min.js" defer></script><script>window.addEventListener("load", () => {
window.cookieconsent.initialise({
type: "info",
theme: "edgeless",
static: false,
position: "bottom-left",
content: {
message: "이 웹 사이트는 귀하의 경험을 향상시키기 위해 Cookie를 사용합니다.",
dismiss: "무시",
allow: "허용",
deny: "거부",
link: "더 알아보기",
policy: "Cookie 정책",
href: "https://www.cookiesandyou.com/",
},
palette: {
popup: {
background: "#edeff5",
text: "#838391"
},
button: {
background: "#4b81e8"
},
},
});
});</script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/lightgallery.min.js" defer></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/jquery.justifiedGallery.min.js" defer></script><script>window.addEventListener("load", () => {
if (typeof $.fn.lightGallery === 'function') {
$('.article').lightGallery({ selector: '.gallery-item' });
}
if (typeof $.fn.justifiedGallery === 'function') {
if ($('.justified-gallery > p > .gallery-item').length) {
$('.justified-gallery > p > .gallery-item').unwrap();
}
$('.justified-gallery').justifiedGallery();
}
});</script><!--!--><!--!--><!--!--><!--!--><!--!--><script src="/js/main.js" defer></script><div class="searchbox"><div class="searchbox-container"><div class="searchbox-header"><div class="searchbox-input-container"><input class="searchbox-input" type="text" placeholder="입력 하세요..."></div><a class="searchbox-close" href="javascript:;">×</a></div><div class="searchbox-body"></div></div></div><script src="/js/insight.js" defer></script><script>document.addEventListener('DOMContentLoaded', function () {
loadInsight({"contentUrl":"/content.json"}, {"hint":"입력 하세요...","untitled":"(제목 없음)","posts":"포스트","pages":"페이지","categories":"카테고리","tags":"태그"});
});</script></body></html>