Skip to content

yoonsseo/spring-dangn-market-18th

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

61 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

spring-dangn-market-18th

CEOS 18th Backend Study - Carrot Market

[2์ฃผ์ฐจ] ๐Ÿฅ•๋‹น๊ทผ๋งˆ์ผ“ DB ๋ชจ๋ธ๋ง

1๏ธโƒฃ ํšŒ์› ๊ธฐ๋Šฅ โžก๏ธ User ์—”ํ‹ฐํ‹ฐ

User2 User1

  • ํšŒ์› ๊ณ ์œ  ๋ฒˆํ˜ธ userId
  • ํ•ธ๋“œํฐ๋ฒˆํ˜ธ phone
    โ†’ ํ•ธ๋“œํฐ ๋ฒˆํ˜ธ๋Š” ์ˆซ์ž์ด์ง€๋งŒ ์—ฐ์‚ฐ์ด ์—†๊ณ  ๊ฒ€์ƒ‰์ด ํŽธํ•˜๋„๋ก varchar/String์œผ๋กœ ์„ค์ •
  • ์ด๋ฉ”์ผ email
๋‹น๊ทผ๋งˆ์ผ“์€ ์šฐ์„  ํ•ธ๋“œํฐ ๋ฒˆํ˜ธ๋กœ ํšŒ์›๊ฐ€์ž…์„ ํ•œ ํ›„ ์›ํ•œ๋‹ค๋ฉด ์ด๋ฉ”์ผ๋„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค 
๋น„๋ฐ€๋ฒˆํ˜ธ?  ๋‹น๊ทผ๋งˆ์ผ“์—์„œ ์ „์†กํ•˜๋Š” ์ธ์ฆ๋ฒˆํ˜ธ? 
  • ๋‹‰๋„ค์ž„ nickname
  • ํ”„๋กœํ•„ ์‚ฌ์ง„ profileImage
  • ๋งค๋„ˆ์˜จ๋„ manners
  • ์žฌ๊ฑฐ๋ž˜ํฌ๋ง๋ฅ 
  • ์‘๋‹ต๋ฅ 
  • ์‘๋‹ต์‹œ๊ฐ„

2๏ธโƒฃ ๋™๋„ค โžก๏ธ Town ์—”ํ‹ฐํ‹ฐ

Town1 Town2
  • townId
  • stateName, districtName, townName ex. ์„œ์šธ์‹œ ์„œ์ดˆ๊ตฌ ๋ฐฉ๋ฐฐ๋™
์ด๋ ‡๊ฒŒ ๋‚˜๋ˆ ์„œ ์ €์žฅํ•˜๋Š” ๊ฒŒ ๋งž๋Š”์ง€ 
์„œ์šธ์‹œ ์„œ์ดˆ๊ตฌ ๋ฐฉ๋ฐฐ๋™์œผ๋กœ ์„ค์ •ํ•˜๊ณ ์ž ํ•  ๋•Œ
๋ฐฉ๋ฐฐ๋ผ๊ณ  ๊ฒ€์ƒ‰ํ•ด๋„ ๋œจ๊ณ  ์„œ์ดˆ๋ผ๊ณ  ๊ฒ€์ƒ‰ํ•ด๋„ ๊ด€๋ จ ๋™๋„ค๊ฐ€ ๋œจ๋Š”๋ฐ
์ดˆ๊ตฌ๋‚˜ ๋ฐฐ๋™ ์ด๋ ‡๊ฒŒ ๊ฒ€์ƒ‰ํ•˜๋ฉด ์•ˆ ๋œฌ๋‹ค โ†’ ๊ฒ€์ƒ‰์ด ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฑฐ์ง€???
์ฒ˜์Œ์— ํ–‰์ •๊ตฌ์—ญ๋ณ„๋กœ ์•„์˜ˆ ์„ธ ๊ฐœ์˜ ํ…Œ์ด๋ธ”๋กœ ๋‚˜๋ˆ„์—ˆ๋‹ค๊ฐ€ ๊ทธ๋ ‡๊ฒŒ๊นŒ์ง€ ๋‚˜๋ˆ„์–ด์•ผํ•˜๋‚˜ ์‹ถ์—ˆ๋Š”๋ฐ
๊ทผ๋ฐ ํ…Œ์ด๋ธ”์ด๋‚˜ ์ปฌ๋Ÿผ๋ณ„๋กœ ๊ตณ์ด ๋ถ„๋ฆฌํ•ด์•ผํ•˜๋‚˜ ์‹ถ๊ธฐ๋„ ํ•˜๊ณ  ์•„๋ฌดํŠผ ๊ณ ๋ฏผ 

๊ทธ๋ฆฌ๊ณ  ๊ทผ์ฒ˜ ๋™๋„ค๋Š” ์–ด๋–ป๊ฒŒ ์„ค์ •ํ•˜๋Š”๊ฑฐ์ง€ 

3๏ธโƒฃ ๋™๋„ค ์ธ์ฆ โžก๏ธ UserTown ์—”ํ‹ฐํ‹ฐ

์œ ์ € ๋‹น ์ตœ๋Œ€ ๋‘ ๊ฐœ์˜ ๋™๋„ค ์ •๋ณด

  • userTownId userId townId
  • ๋™๋„ค ๋ฒ”์œ„ townRange
    ๐Ÿ“Œ range๋ฅผ ์“ฐ๋ฉด mysql ์˜ˆ์•ฝ์–ด๋ผ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค!! ๋‚˜๋„ ์•Œ๊ณ  ์‹ถ์ง€ ์•Š์•˜๋‹ค ๐Ÿฅน๐Ÿฅน
  • ๋™๋„ค ์ธ์ฆ ์‹œ๊ฐ„ townAuthTime
  • ๋™๋„ค ์ธ์ฆ ์—ฌ๋ถ€ isTownAuth
์œ ์ € ๋‹น ์ตœ๋Œ€ 2๊ฐœ์˜ ์ฃผ์†Œ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ ,
์ฃผ์†Œ๋งˆ๋‹ค ๋ฒ”์œ„, ์ธ์ฆ ์‹œ๊ฐ„, ์ธ์ฆ ์—ฌ๋ถ€๊ฐ€ ๋”ฐ๋กœ ๊ด€๋ฆฌ๋˜์–ด ํ…Œ์ด๋ธ” ๋ถ„๋ฆฌ 

4๏ธโƒฃ ๋ฌผ๊ฑด ์˜ฌ๋ฆฌ๊ธฐ โžก๏ธ Post ์—”ํ‹ฐํ‹ฐ

Post1

  • ํŒ๋งค ๊ฒŒ์‹œ๊ธ€ ๊ณ ์œ  ๋ฒˆํ˜ธ postId
  • ์ œ๋ชฉ title -> @Notnull
  • ์นดํ…Œ๊ณ ๋ฆฌ categoryId -> @Notnull
  • ๊ฑฐ๋ž˜๋ฐฉ์‹ tradeMethod
  • ๊ฐ€๊ฒฉ price
  • ๊ฐ€๊ฒฉ ์ œ์•ˆ ์—ฌ๋ถ€ isPriceOffer
  • ์ž์„ธํ•œ ์„ค๋ช… description -> @Notnull
  • ๊ฑฐ๋ž˜ ํฌ๋ง ์žฅ์†Œ wishPlace
  • ํŒ๋งค์ž user -> seller
  • ๋ณด์—ฌ์ค„ ๋™๋„ค ์„ค์ • townRange
  • ํŒ๋งค ์ƒํƒœ postStatus
ํŒ๋งค์ž๋Š” ๋ณธ์ธ์ด ์˜ฌ๋ฆฐ ๊ฒŒ์‹œ๊ธ€์—์„œ ํŒ๋งค ์ƒํƒœ๋ฅผ ํŒ๋งค ์™„๋ฃŒ๋กœ ๋ฐ”๊พธ๋ฉด ๊ตฌ๋งค ํ™•์ •์ธ๋ฐ
๊ตฌ๋งค์ž๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ๋˜์–ด์•ผํ•˜๋Š”์ง€ ๊ณ ๋ฏผ  
  • ๋Œ€ํ‘œ์‚ฌ์ง„ thumbnail
  • ๋‚˜๋จธ์ง€ ์‚ฌ์ง„ image1~9
  • ๋ธŒ๋žœ๋“œ brand
    โ†’ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋”ฐ๋ผ ๋ธŒ๋žœ๋“œ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ์นธ์ด ๋œจ๊ธฐ๋„ ํ•˜๊ณ  ์•ˆ ๋œจ๊ธฐ๋„ ํ•œ๋‹ค ์‹ ๊ธฐ

5๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ โžก๏ธ Category ์—”ํ‹ฐํ‹ฐ

  • ์นดํ…Œ๊ณ ๋ฆฌ ๊ณ ์œ  ๋ฒˆํ˜ธ categoryId
  • ์นดํ…Œ๊ณ ๋ฆฌ ์ด๋ฆ„ name

6๏ธโƒฃ ์ฑ„ํŒ…๋ฐฉ โžก๏ธ ChatRoom ์—”ํ‹ฐํ‹ฐ

Chat1

  • ์ฑ„ํŒ…๋ฐฉ ๊ณ ์œ  ๋ฒˆํ˜ธ chatRoomId
  • ํŒ๋งค์ž/๊ตฌ๋งค์ž ์ •๋ณด user -> seller/buyer
    โ†’ ์ฑ„ํŒ…๋ฐฉ ์ด๋ฆ„์€ ์ƒ๋Œ€๋ฐฉ ๋‹‰๋„ค์ž„
  • ํŒ๋งค ๊ฒŒ์‹œ๊ธ€ ์ •๋ณด postId
  • ์•ˆ ์ฝ์€ ์ฑ„ํŒ… ์ˆ˜

7๏ธโƒฃ ์ฑ„ํŒ… ๋‚ด์šฉ โžก๏ธ Chat ์—”ํ‹ฐํ‹ฐ

  • ์ฑ„ํŒ… ๊ณ ์œ  ๋ฒˆํ˜ธ chatId
  • ์ฑ„ํŒ…๋ฐฉ ๋ฒˆํ˜ธ chatRoomId
  • ์ฑ„ํŒ… ๋‚ด์šฉ content
  • ์ƒ๋Œ€๋ฐฉ์ด ์ฝ์—ˆ๋Š”์ง€ ์—ฌ๋ถ€ isRead
  • ๋ˆ„๊ฐ€ ๋ณด๋‚ด๊ณ  ๋ฐ›์•˜๋Š”์ง€ user -> sender/receiver
    โ†’ sender ์ปฌ๋Ÿผ๋งŒ ์žˆ์œผ๋ฉด ์ฑ„ํŒ…๋ฐฉ์ด๋ž‘ ์—ฐ๊ฒฐํ•ด์„œ ๋ฐ›์€ ์‚ฌ๋žŒ ์•Œ ์ˆ˜ ์žˆ์ง€ ์•Š๋‚˜?

8๏ธโƒฃ ๊ฑฐ๋ž˜ํ›„๊ธฐ โžก๏ธ Review ์—”ํ‹ฐํ‹ฐ

Review4

Review2 Review3
  • ๊ฑฐ๋ž˜ ํ›„๊ธฐ ๊ณ ์œ  ๋ฒˆํ˜ธ reviewId
  • ์ž‘์„ฑ์ž/๋Œ€์ƒ์ž reviewer/reviewee
  • ์–ด๋–ค ํŒ๋งค ๊ฒŒ์‹œ๊ธ€์— ๋Œ€ํ•œ ๋ฆฌ๋ทฐ์ธ์ง€ postId
  • ๊ตฌ๋งค์ž๊ฐ€ ์ ์€ ํ›„๊ธฐ์ธ์ง€ ํŒ๋งค์ž๊ฐ€ ์ ์€ ํ›„๊ธฐ์ธ์ง€ reviewType
  • ๊ฑฐ๋ž˜์„ ํ˜ธ๋„ reviewLevel
์ด ๋ฆฌ๋ทฐ๋กœ ๋งค๋„ˆ์˜จ๋„๊ฐ€ ๋ณ€ํ•˜๋Š”๋ฐ  

๐Ÿ”ข BaseEntity

  • ์ƒ์„ฑ์‹œ๊ฐ„ created์™€ ๋งˆ์ง€๋ง‰ ์ˆ˜์ •์‹œ๊ฐ„ modified ์ปฌ๋Ÿผ์€ ๊ฑฐ์˜ ๋ชจ๋“  ํ…Œ์ด๋ธ”์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ปฌ๋Ÿผ์ด๊ธฐ ๋•Œ๋ฌธ์— @MappedSuperClass๋กœ ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ
  • @MappedSuperclass
    • ๋งคํ•‘ ์ •๋ณด๋งŒ ๋ฐ›๋Š” ๋ถ€๋ชจ ํด๋ž˜์Šค, ์ƒ์†๊ณผ ๊ด€๋ จ๋œ ๊ฒƒ ์•„๋‹˜
    • ์ƒ์†๊ด€๊ณ„ ๋งคํ•‘ ์•„๋‹ˆ๊ณ  ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹ˆ์–ด์„œ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜์ง€ ์•Š๋Š”๋‹ค
      โ†’ ์กฐํšŒ, ๊ฒ€์ƒ‰ ๋‹น์—ฐํžˆ ๋ถˆ๊ฐ€(em.find(BaseEntity) ๋ถˆ๊ฐ€)
    • ๋ถ€๋ชจ ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›๋Š” ์ž์‹ ํด๋ž˜์Šค์— ๋งคํ•‘ ์ •๋ณด๋งŒ ์ œ๊ณต
    • ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„ ์—†๊ณ , ๋‹จ์ˆœํžˆ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋งคํ•‘ ์ •๋ณด๋ฅผ ๋ชจ์œผ๋Š” ์—ญํ• 

๐Ÿ“… ERD

๋‹น๊ทผERD

๐Ÿฅ• ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

โš’๏ธ Builder Pattern

๐Ÿงฑ Builder Pattern ์ด๋ž€?
  • ๋ณต์žกํ•œ Object๋“ค์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒ์„ฑ ๋””์ž์ธ ํŒจํ„ด์œผ๋กœ
    • ๋ณต์žกํ•œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค์™€ ํ‘œํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•ด,
    • ์„œ๋กœ ๋‹ค๋ฅธ ํ‘œํ˜„์ด๋ผ๋„ ์ด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋™์ผํ•œ ์ ˆ์ฐจ๋ฅผ ์ œ๊ณตํ•˜๋Š” ํŒจํ„ด
  • ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ๋™์‹œ์— ๊ฐ’์„ ์„ค์ •๊ฐ€๋Šฅํ•œ ์ƒ์„ฑ์ž๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ƒ์„ฑ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ
    • ํ•„์ˆ˜๊ฐ€ ์•„๋‹Œ ๊ฐ’๋„ null๋กœ ์ฑ„์›Œ์ฃผ๊ฑฐ๋‚˜,
    • ex.์ฃผ์†Œ๋ฅผ ๋บ€ ์ƒ์„ฑ์ž ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๋งŒ๋“ค์–ด์•ผ ํ•˜๊ณ 
    • ๋ช…ํ™•ํ•˜๊ฒŒ ์–ด๋–ค ๊ฐ’์„ ์ง€์ •ํ•˜๋Š” ์ง€ ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋…์„ฑ์ด ์ข‹์ง€ ์•Š๋‹ค
  • ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€๋…์„ฑ ์ข‹๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋„๊ตฌ
Builder()

ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ Builder ํด๋ž˜์Šค๋ฅผ ๋”ฐ๋กœ ์ •์˜ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ 
๊ฐ’์„ ์„ค์ •ํ•˜๊ณ  ์ž๊ธฐ์ž์‹ ์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•จ์ˆ˜๋ฅผ ์—ฐ์†์ ์œผ๋กœ ์ฒด์ด๋‹ํ•˜๋“ฏ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ—๏ธ @Builder ์‚ฌ์šฉ
  • @Builder
    ๋นŒ๋” ํด๋ž˜์Šค์™€ ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” builder() ๋ฉ”์„œ๋“œ ์ƒ์„ฑ
  • @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @Builder ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ ์–ธํ•˜๋ฉด ์ „์ฒด ์ธ์ž๋ฅผ ๊ฐ–๋Š” ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ๋งŒ๋“œ๋Š”๋ฐ, ์ด๋ฅผ private ์ƒ์„ฑ์ž๋กœ ์„ค์ •
  • ํด๋ž˜์Šค ์ „์ฒด์— Builder๋ฅผ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ  ํŠน์ • ์ƒ์„ฑ์ž์—์„œ๋งŒ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค
@Getter @Builder //ํด๋ž˜์Šค ์ „์ฒด ํ•„๋“œ๋ฅผ ๋นŒ๋”๋กœ ์‚ฌ์šฉ
public class User {
  private Long id;
  private String phone;
  private String nickname;
}
public class User {
  ...
  @Builder //phone, nickname๋งŒ ๋นŒ๋” ์‚ฌ์šฉ 
  public User(String phone, String nickname) {
    this.phone = phone;
    this.nickname = nickname;
  }
}

๐ŸŽฏํ…Œ์ŠคํŠธ(Junit5)

@DataJpaTest
  • JPA ๊ด€๋ จ๋œ Component๋งŒ ๋กœ๋“œ
    ApplicationContext ์ „์ฒด๊ฐ€ ์•„๋‹Œ JPA์— ํ•„์š”ํ•œ ์„ค์ •๋“ค์— ๋Œ€ํ•ด์„œ๋งŒ Bean์„ ๋“ฑ๋กํ•œ๋‹ค
    โ†’ ์ปดํฌ๋„ŒํŠธ ์Šค์บ”์„ ํ•˜์ง€ ์•Š์•„, @Component ๋นˆ๋“ค์ด ๋“ฑ๋ก๋˜์ง€ ์•Š๋Š”๋‹ค
  • @Transactional ์–ด๋…ธํ…Œ์ด์…˜ ํฌํ•จ โ†’ ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ ๋กค๋ฐฑ๋„ ๊ฐ™์ด ์ˆ˜ํ–‰๋œ๋‹ค
  • ๋””ํดํŠธ๋กœ h2 ๋“œ๋ผ์ด๋ฒ„ ์‚ฌ์šฉ
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
  • ymlํŒŒ์ผ์—์„œ DB๋ฅผ MySql๋กœ ์„ค์ •ํ•ด ๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— h2 ์˜์กด์„ฑ์ด ์—†์œผ๋ฉด DataSource๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.database")
public @interface AutoConfigureTestDatabase {

@PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)
Replace replace() default Replace.ANY;

EmbeddedDatabaseConnection connection() default EmbeddedDatabaseConnection.NONE;

// ...
}
  • @AutoConfigureTestDatabase์€ @DataJpaTest์—์„œ ์„ค์ •์„ ์ž๋™์œผ๋กœ ํ•ด์ฃผ๋Š” ๋งŽ์€ ์–ด๋…ธํ…Œ์ด์…˜ ์ค‘ ํ•˜๋‚˜
  • ๋””ํดํŠธ๊ฐ’ Replace.ANY์˜ replace ์†์„ฑ๊ณผ
    ๋””ํดํŠธ๊ฐ’ EmbeddedDatabaseConnection.NONE์˜ connection ์†์„ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค
  • EmbeddedDatabaseConnection์˜ enum ๊ฐ’์—๋Š” H2, DERBY, HSQLDB ๋“ฑ์ด ์žˆ๋Š”๋ฐ MySql์€ ์—†๋‹ค
    โ†’ MySql๋กœ ์„ค์ •ํ–ˆ๋‹ค๋ฉด ์ฐพ์„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ ๋ฐœ์ƒ!!
  • replace ๊ธฐ๋ณธ๊ฐ’์ด ANY์ด๊ธฐ ๋•Œ๋ฌธ์— Embedded Database ๋ฅผ ์ฐพ๊ฒŒ ๋œ ๊ฒƒ์ด๊ณ 
    โ†’ Embedded Database๋ฅผ ์“ฐ์ง€ ์•Š๋„๋ก replace ๊ฐ’์„
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    Replace.NONE ์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์‹ค์ œ Database๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
AssertJ

AssertJ๋Š” assertion์„ ์ œ๊ณตํ•˜๋Š” ์ž๋ฐ” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์™€ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์—ฌ์ค€๋‹ค

import static org.assertj.core.api.Assertions.assertThat;
...
assertThat(actual).isEqualTo(expected)

๋ชจ๋“  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” assertThat() ๋ฉ”์†Œ๋“œ์—์„œ ์ถœ๋ฐœํ•˜๊ณ , AssertJ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฉ”์†Œ๋“œ๋ฅผ ์—ฐ์‡„ ํ˜ธ์ถœ ํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค

assertThat(ํ…Œ์ŠคํŠธ ํƒ€๊ฒŸ).๋ฉ”์†Œ๋“œ1().๋ฉ”์†Œ๋“œ2().๋ฉ”์†Œ๋“œ3();

image

[3์ฃผ์ฐจ] ๐Ÿ“ค API

๐Ÿ“ฌ ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก API

API ๋ช…์„ธ์„œ

๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก API ๋ช…์„ธ์„œ

๋กœ์ง
    public Long registerPost(RegisterPostRequestDto requestDto) {
        //๋กœ๊ทธ์ธ๋œ ์œ ์ €์˜ ์˜ฌ๋ฐ”๋ฅธ ์ •๋ณด๊ฐ€ ๋„˜์–ด์˜จ๋‹ค๊ณ  ๊ฐ€์ •
        User seller = userRepository.findById(requestDto.getUser_id()).get();

        Post post = requestDto.toEntity(seller);

        TradeMethod tradeMethod = TradeMethod.valueOf(requestDto.getTradeMethod());
        post.setTradeMethod(tradeMethod);

        Category category = categoryRepository.findByName(requestDto.getCategory());
        post.setCategory(category);

        postRepository.save(post);

        return post.getId();
    }
  1. RequestBody๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก์— ํ•„์š”ํ•œ ์ •๋ณด ๋ฐ›๊ธฐ
    ๋ถ€๋“์ดํ•˜๊ฒŒ ์‚ฌ์šฉ์ž ์ •๋ณด๋„ RequestBody๋กœ ๋ฐ›์Œ
  2. RegisterPostRequestDto - toEntity ๋ฉ”์†Œ๋“œ : DTO๋กœ ๋ฐ›์€ ์ •๋ณด Post Entity๋กœ ๋ฐ”๊ฟ”์ฃผ๊ธฐ
    ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ์œ„ํ•ด userId๋กœ User Entity ์ฐพ์•„์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋งŒ ๋”ฐ๋กœ ๋„˜๊ฒจ์ค€๋‹ค
     public Post toEntity(User seller) {
         return Post.builder()
                 .seller(seller)
                 .thumbnail(thumbnail)
                 .title(title)
                 .price(price)
                 .isPriceOffer(isPriceOffer)
                 .description(description)
                 .wishPlace(wishPlace)
                 .townRange(townRange)
                 .build();
     }
  3. TradeMethod ๊ฑฐ๋ž˜ํ•˜๊ธฐ/๋‚˜๋ˆ”ํ•˜๊ธฐ์˜ ๊ฑฐ๋ž˜๋ฐฉ์‹์€ String์œผ๋กœ ๋„˜์–ด์˜ค๋Š”๋ฐ Enum๊ฐ’์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋”ฐ๋กœ ์„ค์ •ํ•ด์ค€๋‹ค
    ์นดํ…Œ๊ณ ๋ฆฌ๋„ String์œผ๋กœ ๋„˜์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— CategoryRepository์—์„œ ์—”ํ‹ฐํ‹ฐ ์ฐพ์•„์„œ ์—ฐ๊ด€ ๊ด€๊ณ„ ์„ค์ •ํ•ด์ฃผ๊ธฐ
  4. ๊ทธ๋ฆฌ๊ณ  save ํ•ด์ฃผ๊ณ  ์ผ๋‹จ Service์—์„œ๋Š” postId ๋ฆฌํ„ดํ•ด์ฃผ์—ˆ๋‹น Controller์—์„œ๋Š” ok ๋ฐ˜ํ™˜
ํฌ์ŠคํŠธ๋งจ

๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ํฌ์ŠคํŠธ๋งจ

MySQL

๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก DB

๐Ÿ—‚๏ธ ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ API

๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€

API ๋ช…์„ธ์„œ

๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ API

๐Ÿคฏ ๊ณ ๋ฏผ
  1. ์ •๋ ฌ์กฐ๊ฑด์ด ์ตœ์‹ ์ˆœ์ด ์•„๋‹Œ ๊ฒƒ ๊ฐ™๊ธด ํ•œ๋ฐ ์šฐ์„  Pageable ์ ์šฉํ•œ findAll๋กœ ๊ฐฑ์‹ ์ˆœ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋ ค๊ณ  ํ–ˆ๋‹ค
  2. ๊ทผ๋ฐ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ๊ทผ์ฒ˜ ๋™๋„ค์˜ ๊ฒŒ์‹œ๋ฌผ๋งŒ ๊ฐ€์ ธ์™€์•ผํ•˜๊ณ 
  3. ๋˜ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ๊นŒ ์‚ฌ์šฉ์ž๊ฐ€ ๋‘ ๊ฐœ์˜ ๋™๋„ค๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ
    ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๋™๋„ค๋ž‘
    ํŒ๋งค์ž๊ฐ€ ์–ด๋А ๋™๋„ค๋ฅผ ํ˜„์žฌ๋กœ ์„ค์ •ํ•˜๊ณ  ์˜ฌ๋ฆฐ ๊ฒŒ์‹œ๋ฌผ์ธ์ง€๋„ ์•Œ์•„์•ผํ•  ๊ฑฐ ๊ฐ™์€๋ฐ
    ๊ทธ๊ฑฐ๋Š” ํฌ์ŠคํŠธ ์—”ํ‹ฐํ‹ฐ์— ์ปฌ๋Ÿผ์ด ์žˆ์–ด์•ผํ•  ๊ฒƒ ๊ฐ™๋‹ค
  4. ํƒ€์šด ์—”ํ‹ฐํ‹ฐ์— ์œ„๋„์™€ ๊ฒฝ๋„๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธด ํ–ˆ๋Š”๋ฐ
    ์˜ˆ๋ฅผ ๋“ค์–ด ๊ทผ์ฒ˜ ๋™๋„ค ๋ฒ”์œ„๋ฅผ ์œ„๋„ยฑ50, ๊ฒฝ๋„ยฑ50 ์œผ๋กœ ์„ค์ •ํ–ˆ์„ ๋•Œ
    ๊ทธ๋ž˜์„œ ์ •๋ง๋กœ ๊ทธ ์œ„์น˜์˜ ๋™๋„ค ์ด๋ฆ„์„ ์•Œ๋ ค๋ฉด api๊ฐ€ ํ•„์š”ํ•  ๊ฒƒ ๊ฐ™๋‹ค
๋กœ์ง
@Transactional(readOnly = true)
public PostListResponseDto getPostList(Pageable pageable) {
    Page<Post> findPosts = postRepository.findByIsDel(false, pageable);

    Page<PostDto> postDtos = findPosts.map(post -> new PostDto(post,
        chatRoomRepository.getTotalChatRoom(post),
        userTownRepository.findByUser(post.getSeller()).get(0).getTown().getTownName()));
        //ํŽธ์˜์ƒ ์ฒซ ๋ฒˆ์งธ ์ฃผ์†Œ๋กœ ๊ฐ€์ •

    return new PostListResponseDto(postDtos.getTotalPages(), postDtos.getNumber(), postDtos.getContent());
}
  1. ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ๋™๋„ค๋กœ ์„ค์ •๋œ ๊ทผ์ฒ˜ ๋™๋„ค์˜ ๊ฒฐ๊ณผ๋งŒ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์€ ์ ์šฉํ•˜์ง€ ๋ชปํ–ˆ๋‹ค
     Page<Post> findByIsDel(boolean isDel, Pageable pageable);
    ๊ทธ๋ƒฅ ์ •๋ ฌ ์กฐ๊ฑด์„ modifiedAt์˜ ASC ์ˆœ์„œ๋กœ Page ๊ฐ์ฒด ์ƒ์„ฑ + ์‚ญ์ œ ์—ฌ๋ถ€ ํ™•์ธ
    ๋ฌดํ•œ์Šคํฌ๋กค๋กœ ๊ตฌํ˜„์ด ๋˜์–ด์žˆ๋Š”๋ฐ, ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ํ”„๋ก ํŠธ ์ธก์—์„œ ์Šคํฌ๋กค ์ด๋ฒคํŠธ๊ฐ€ ์ผ์–ด๋‚˜๊ฑฐ๋‚˜ ํ•˜๋Š” ์ƒํ™ฉ์—
    ๋ฒก์œผ๋กœ ๋‹ค์Œ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ๋กœ ์š”์ฒญํ•˜๋ฉด, ์ผ์ • ๊ฐœ์ˆ˜์˜ ๊ฒŒ์‹œ๋ฌผ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ๋‹ค์Œ ํŽ˜์ด์ง€ ๋ฐ˜ํ™˜
    ์ž˜ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๋ฌดํ•œ์Šคํฌ๋กค ํ˜•์‹์ด๋“  ๊ฒŒ์‹œํŒ ํ˜•์‹์ด๋“  ๊ทธ๊ฒƒ์€ ํ”„๋ก ํŠธ๊ฐ€ ํ•ด์•ผํ•˜๋Š” ์ผ์ด ์•„๋‹๊นŒ..? โ†’
  2. ์ฐพ์•„์˜จ ๊ฒŒ์‹œ๋ฌผ๋“ค์—์„œ map์œผ๋กœ ๊ฐ ๊ฒŒ์‹œ๋ฌผ ํ•˜๋‚˜์”ฉ์˜ ์ •๋ณด๋ฅผ ๋‹ด์€ PostDto ์ƒ์„ฑ
    • post Entity ์ž์ฒด๋ฅผ ๋„˜๊ฒจ์„œ ๊ฐ ์ •๋ณด ๋ฝ‘๊ณ ,
    @Query("SELECT COALESCE(COUNT(cr.id), 0) FROM ChatRoom cr WHERE cr.post = :post")
    int getTotalChatRoom(@Param("post") Post post);
    • ์ฑ„ํŒ…๋ฐฉ ๊ฐœ์ˆ˜๋Š” ChatRoomRepository์— ์ฟผ๋ฆฌ ์ƒ์„ฑํ•ด์„œ ๊ณ„์‚ฐ
    • ํŒ๋งค์ž ๋™๋„ค ์ •๋ณด : post Entity์˜ seller ์ •๋ณด๋ฅผ ์ด์šฉํ•ด
      UserTownRepository์—์„œ findByUser๋กœ UserTown ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฝ‘์€ ๋‹ค์Œ์—,
      ํŽธ์˜์ƒ 0๋ฒˆ์งธ ์ธ๋ฑ์Šค ๊ฐ’์˜ UserTown Entity โ†’ ์˜ Town์œผ๋กœ ๋„˜์–ด๊ฐ€์„œ ๋™๋„ค ์ด๋ฆ„ ๊ฐ’ ๋ฐ›์•„์˜ค๊ธฐ..
  3. ๋งˆ์ง€๋ง‰์œผ๋กœ PostListResponseDto์— Page ๊ฐ์ฒด๊ฐ€ ์ œ๊ณตํ•ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด
    ์ „์ฒด ํŽ˜์ด์ง€ ์ˆ˜์™€, ํ˜„์žฌ ํŽ˜์ด์ง€ ์ˆ˜,
    ๊ทธ๋ฆฌ๊ณ  ๊ฐ ๊ฒŒ์‹œ๋ฌผ ์ •๋ณด์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋‹ด์•„์„œ ResponseBody๋กœ ๋ฐ˜ํ™˜
    ์œ„์‹œ๋ฆฌ์ŠคํŠธ ์—†๋‹ค
MySQL

๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ DB

ํฌ์ŠคํŠธ๋งจ

๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ ํฌ์ŠคํŠธ๋งจ
๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ2
3๋ฒˆ ๊ฒŒ์‹œ๊ธ€์€ isDel=1๋กœ ์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์ด๋ผ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹น๐Ÿ‘๐Ÿป๐Ÿ‘๐Ÿป

๐Ÿ” ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ API - ๊ฒ€์ƒ‰ํ• ๊นŒ ์ƒ์„ธํ• ๊นŒ ๊ณ ๋ฏผ ์ค‘

๊ฒŒ์‹œ๋ฌผ ์ƒ์„ธ

API ๋ช…์„ธ์„œ

ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ API ๋ช…์„ธ์„œ

๋กœ์ง
public PostResponseDto getPost(Long postId) {
   Optional<Post> findPost = postRepository.findById(postId);
   if (findPost.isPresent() && !findPost.get().isDel()) {
       //์กฐํšŒ์ˆ˜ ์˜ฌ๋ ค์ฃผ๊ธฐ!
       postRepository.updateView(postId);

       Post post = findPost.get();

       //ํŽธ์˜์ƒ ์ฒซ ๋ฒˆ์งธ ์ฃผ์†Œ๋กœ ๊ฐ€์ •..
       String sellerTown = userTownRepository.findByUser(post.getSeller()).get(0).getTown().getTownName();

       return new PostDetailResponseDto(postId, post, sellerTown, chatRoomRepository.getTotalChatRoom(post));
   }
   else {
       throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "์ž˜๋ชป๋œ ๊ฒŒ์‹œ๋ฌผ ์š”์ฒญ");
   }
}
  1. @PathVariable๋กœ ๋ฐ›์•„์˜จ postId๋ฅผ ์ด์šฉํ•ด postRepository์—์„œ ๊ฒŒ์‹œ๋ฌผ ์ฐพ๊ธฐ
  2. ๊ฒŒ์‹œ๋ฌผ์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์˜ ์กฐํšŒ์ˆ˜ ์˜ฌ๋ ค์ฃผ๊ธฐ + ์‚ญ์ œ๋˜์ง€ ์•Š์•˜์œผ๋ฉด!
     @Modifying
     @Query("UPDATE Post p set p.view = p.view + 1 where p.id = :postId")
     void updateView(@Param("postId") Long postId);
  3. ๊ทธ๋ฆฌ๊ณ  post Entity ๋ฐ›์•„์˜ค๊ณ , ํŒ๋งค์ž ์ฃผ์†Œ ์ •๋ณด ์ฐพ์€ ๊ฑฐ๋ž‘
    ์ฑ„ํŒ…๋ฐฉ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์—์„œ ์ฑ„ํŒ…๋ฐฉ ๊ฐœ์ˆ˜ ์ฐพ์•„์„œ PostDetailResponseDto ์ƒ์„ฑํ•ด์„œ ๋ฐ˜ํ™˜
     public PostDetailResponseDto(Long postId, Post post, String sellerTown, int totalChatRoom) {
         this.post_id = postId;
    
         this.seller_profileImage = post.getSeller().getProfileImage();
         this.seller_nickname = post.getSeller().getNickname();
         this.seller_town = sellerTown;
         this.seller_manners = post.getSeller().getManners();
    
         this.title = post.getTitle();
         this.category = post.getCategory().getName();
         this.description = post.getDescription();
         this.wishplace = post.getWishPlace();
         this.view = post.getView();
    
         this.total_ChatRoom = totalChatRoom;
     }
  4. ๊ฒŒ์‹œ๋ฌผ์ด ์—†์œผ๋ฉด 404 ๋ฐ˜ํ™˜
ํฌ์ŠคํŠธ๋งจ

ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ ํฌ์ŠคํŠธ๋งจ
์กฐํšŒ์ˆ˜๊ฐ€ 1๋กœ ์ฆ๊ฐ€ํ–ˆ๊ณ  ์ฑ„ํŒ…๋ฐฉ ๊ฐœ์ˆ˜๋„ 0์œผ๋กœ ์ž˜ ๋ฐ˜ํ™˜๋จ๐Ÿ˜Š๐Ÿ˜Š ์‚ญ์ œ๋œ ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ
์‚ญ์ œ๋œ ๊ฒŒ์‹œ๊ธ€์€ 404 BAD REQUEST

โŒ ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ API

API ๋ช…์„ธ์„œ

ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ API ๋ช…์„ธ์„œ

๋กœ์ง
    public void deletePost(Long postId) {
        postRepository.deletePost(postId);
    }
  • Post Entity์— isDel ์ปฌ๋Ÿผ ์ถ”๊ฐ€
    DB์—์„œ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ isDel ์ปฌ๋Ÿผ์„ ์ด์šฉํ•ด ๋…ผ๋ฆฌ์ ์œผ๋กœ ์‚ญ์ œํ•˜๋Š” ๋กœ์ง์œผ๋กœ ๊ตฌํ˜„
    Post Entity๋Š” ๋ฆฌ๋ทฐ, ์ฑ„ํŒ…๋ฐฉ, ๊ทธ๋ฆฌ๊ณ  ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ ์œ„์‹œ๋ฆฌ์ŠคํŠธ ๋“ฑ
    ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋…ผ๋ฆฌ์ ์œผ๋กœ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด ๋‚ซ๋‹ค๊ณ  ํŒ๋‹น
     @Modifying
     @Query("UPDATE Post p SET p.isDel = true WHERE p.id = :postId")
     void deletePost(@Param("postId") Long postId);
ํฌ์ŠคํŠธ๋งจ

ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ํฌ์ŠคํŠธ๋งจ
์‚ญ์ œ๋œ ํŠน์ • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ2
์‚ญ์ œ ํ›„ ๋‹ค์‹œ ์กฐํšŒํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์กฐํšŒํ•  ์ˆ˜ ์—†์Œ!!

MySQL

์‚ญ์ œ ํ›„ DB
DB์—๋„ ์ž˜ ๋ฐ˜์˜๋˜์–ด ์žˆ์Œ๐Ÿ˜†๐Ÿ˜†

๐Ÿšจ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

  1. ์ดˆ๊ธฐ DB์— ๊ฐ’์„ ์ž˜ ๋„ฃ์–ด๋†“์•„์•ผ ํ–ˆ๋‹ค
    ์‚ฌ์šฉ์ž๋ž‘ ๋™๋„ค ๋„ฃ๊ณ  UserTown ๋•Œ๋ฌธ์— ๋‘˜์ด ์—ฐ๊ฒฐํ•ด ๋‘์–ด์•ผ ํ–ˆ๊ณ , ์นดํ…Œ๊ณ ๋ฆฌ๋„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด๋‘์–ด์•ผ ํ–ˆ์Œ
  2. Category๋ž‘ Post ์—ฐ๊ด€ ๊ด€๊ณ„ @ManyToOne์œผ๋กœ ํ–ˆ๋‹ค๊ฐ€ ์™œ์ธ์ง€ @OneToOne์œผ๋กœ ๋ฐ”๊ฟจ๋Š”๋ฐ
    @ManyToOne์ด ๋งž์•˜์Œ
  3. ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ API์—์„œ ๊ณ„์† 406 not acceptable ์—๋Ÿฌ๊ฐ€ ๋–ด๋Š”๋ฐ
    DTO์— @Getter ๋ถ™์—ฌ์„œ ํ•ด๊ฒฐ
    JSON๊ณผ ๊ด€๋ จ๋œ jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†์–ด์„œ ๋‚˜๋Š” ์˜ค๋ฅ˜๋ผ๊ณ  ํ•œ๋‹ค

๋А๋‚€์ 

์ƒ๊ฐ๋ณด๋‹ค ๋‹น๊ทผ๋งˆ์ผ“์˜ DB์™€ ๋กœ์ง์€ ๋งค์šฐ ๋ณต์žกํ•œ ๊ฑฐ ๊ฐ™๋‹ค
์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š”์ง€ ์ •๋ง ๊ถ๊ธˆํ•˜๋‹ค

[4์ฃผ์ฐจ] Spring Security ์ ์šฉํ•˜๊ธฐ

1. ๋กœ๊ทธ์ธ ์ธ์ฆ ๋ฐฉ๋ฒ•

1.1. JWT - Access Token๊ณผ Refresh Token

1.1.1. ๐Ÿ›ก๏ธ JWT : Json Web Token
  • ์„œ๋กœ ๋‹ค๋ฅธ ๊ธฐ๊ธฐ์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ, Base64์˜ ํ˜•ํƒœ๋ฅผ ๊ฐ€์ง„๋‹ค
  • Header์™€ Body(๋˜๋Š” Payload), ๊ทธ๋ฆฌ๊ณ  Signature ์„ธ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ ์ง„๋‹ค
๐Ÿ“‘ Header
  • JWT์˜ metadata๋“ค์„ ๋‚˜ํƒ€๋‚ธ๋‹ค
  • Sign์— ์‚ฌ์šฉ๋œ Algorithms, format, ๊ทธ๋ฆฌ๊ณ  ContentType ๋“ฑ์˜ ์ •๋ณด
๐Ÿ“„ Payload (Body)
  • Claim ๋‹จ์œ„๋กœ ์ €์žฅ

Claim

  • ์‚ฌ์šฉ์ž์˜ ์†์„ฑ์ด๋‚˜ ๊ถŒํ•œ, ์ •๋ณด์˜ ํ•œ ์กฐ๊ฐ ๋˜๋Š” Json์˜ ํ•„๋“œ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค
  • Claim์—๋Š” JWT ์ƒ์„ฑ์ž๊ฐ€ ์›ํ•˜๋Š” ์ •๋ณด๋“ค์„ ์ž์œ ๋กญ๊ฒŒ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š”๋ฐ
    > Json ํ˜•์‹์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ผ ํ•„๋“œ๋„ ๊ฐ€๋Šฅํ•˜๊ณ ,
    > Object์™€ ๊ฐ™์€ complexibleํ•œ ํ•„๋“œ๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค > > ```java Claims claims = Jwts.claims(); //์ผ์ข…์˜ Map claims.put("userName", userName); ... Jwts.builder() .setClaims(claims) ...
  • Claim์— userName์„ ๋‹ด์•„๋‘๋ฉด ๋”ฐ๋กœ ์‚ฌ์šฉ์ž id๋ฅผ ์ž…๋ ฅ๋ฐ›์ง€ ์•Š์•„๋„ ํ† ํฐ์— ๋“ค์–ด์žˆ๋Š” ๊ฐ’์„ ๊บผ๋‚ผ ์ˆ˜ ์žˆ๋‹ค
๐Ÿ“ Signature
  • Header์™€ Body๋Š” Base64 ํ˜•ํƒœ๋กœ ์ธ์ฝ”๋”ฉ๋˜์–ด ์•”ํ˜ธํ™”๋˜์–ด ์žˆ์ง€ ์•Š์€๋ฐ
    ๊ณต๊ฒฉ์ž๊ฐ€ ๋‚ด์šฉ์„ ๋ฐ”๊ฟ€ ์ˆ˜๊ฐ€ ์žˆ๋‹ค
  • Signature๋กœ ์„œ๋ช…์„ ํ†ตํ•ด ์•”ํ˜ธํ™” ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค
  • ์„œ๋ช… ์ดํ›„ Header์™€ Body์˜ ๋‚ด์šฉ์ด ๋ฐ”๋€๋‹ค๋ฉด Signature์˜ ๊ฒฐ๊ณผ๊ฐ’์ด ๋ฐ”๋€Œ์–ด ๋ฐ›์•„๋“ค์—ฌ์ง€์ง€ ์•Š๋Š”๋‹ค
1.1.2. Access Token

access token ์ธ์ฆ

  • ๊ฐ„ํŽธํ•˜๊ณ , ์„ธ์…˜์ด๋‚˜ ์ฟ ํ‚ค์™€ ๋‹ฌ๋ฆฌ ์ถ”๊ฐ€์ ์ธ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๊ณ ,
    ํ•œ ๋ฒˆ ๋ฐœ๊ธ‰๋˜๋ฉด ์œ ํšจ๊ธฐ๊ฐ„์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€๋Š” ๊ณ„์† ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ,

  • ์ค‘๊ฐ„์— ์‚ญ์ œ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— Access Token์ด ํƒˆ์ทจ๋˜๋ฉด, ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ† ํฐ์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์€ ๋ˆ„๊ตฌ๋‚˜ ๊ถŒํ•œ ์ธ์ฆ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค๋Š” ๋ฌธ์ œ์ ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค

โ†’ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด Access Token์˜ ๋งŒ๋ฃŒ ๊ธฐ๊ฐ„์„ ์งง๊ฒŒ ์ฃผ๊ณ , Refresh Token์„ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ฐœ๊ธ‰ํ•ด ํ•ด๊ฒฐ

1.1.3. Refresh token

refresh token ์ธ์ฆ

  • Refresh Token์€ Access Token์— ๋น„ํ•ด ํ›จ์”ฌ ๋” ๊ธด ์œ ํšจ ๊ธฐ๊ฐ„์œผ๋กœ ๋ฐœ๊ธ‰๋˜๋ฉฐ,
    Refresh Token์˜ ๊ฒฝ์šฐ ์ ‘๊ทผ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ Access Token ์žฌ๋ฐœ๊ธ‰์—๋งŒ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ํŠน์ง•์ด ์žˆ๋‹ค

Access Token ์œ ํšจ ๊ธฐ๊ฐ„ 30๋ถ„ ~ 1์‹œ๊ฐ„ ์ •๋„

Refresh Token ์œ ํšจ ๊ธฐ๊ฐ„ 1์ฃผ์ผ ~ 1๋‹ฌ ์ •๋„

  • Refresh Token ์—ญ์‹œ ํƒˆ์ทจ๋  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š”๋ฐ,
    ์ตœ์ดˆ ๋กœ๊ทธ์ธ ์‹œ ๋กœ๊ทธ์ธ ์š”์ฒญ ip๋ฅผ ์ €์žฅํ•˜๊ณ ,
    ์žฌ๋ฐœ๊ธ‰ ์š”์ฒญ์ด ์™”์„ ๋•Œ, ์š”์ฒญ์ด ์˜จ ip์™€ ์ €์žฅ๋œ ip๋ฅผ ๋น„๊ตํ•˜์—ฌ
    ๋‹ค๋ฅธ ๊ฒฝ์šฐ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๋“ฑ์˜ ์ถ”๊ฐ€์ ์ธ ์กฐ์น˜๋ฅผ ์ทจํ•  ์ˆ˜ ์žˆ๋‹ค

1.2. OAuth

OAuth๋ž€?
  • Open Authorization
  • ์ธํ„ฐ๋„ท ์‚ฌ์šฉ์ž๋“ค์ด ํŠน์ • ์›น ์‚ฌ์ดํŠธ๋ฅผ ์ ‘๊ทผํ•˜๊ณ ์ž ํ•  ๋•Œ, ์ ‘๊ทผํ•˜๋ ค๋Š” ์›น ์‚ฌ์ดํŠธ์— ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ ,
    ์„œ๋“œํŒŒํ‹ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(๊ตฌ๊ธ€, ์นด์นด์˜ค, ํŽ˜์ด์Šค๋ถ ๋“ฑ)์˜ ์—ฐ๊ฒฐ์„ ํ†ตํ•ด '์ธ์ฆ ๋ฐ ๊ถŒํ•œ'์„ ๋ถ€์—ฌ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ํ”„๋กœํ† ์ฝœ
  • ์™ธ๋ถ€์„œ๋น„์Šค์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ๋ถ€์—ฌ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฒ”์šฉ์ ์ธ ํ”„๋กœํ† ์ฝœ
Spring Boot OAuth 2 Client์™€ Spring Boot OAuth 2 Server
  • Spring Boot OAuth 2 Client

    • ์™ธ๋ถ€ OAuth 2.0 ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ
    • ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ์œผ๋กœ OAuth 2.0 ํ”„๋กœํ† ์ฝœ์„ ๋”ฐ๋ฅด๋Š” ์„œ๋น„์Šค์˜ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค
  • Spring Boot OAuth 2 Server

    • OAuth 2.0 ์„œ๋ฒ„๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๋ชจ๋“ˆ
    • ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ์œผ๋กœ OAuth 2.0 ํ”„๋กœํ† ์ฝœ์„ ๋”ฐ๋ฅด๋Š” ์„œ๋ฒ„๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค
๊ธฐ๋ณธ ๋™์ž‘ ์›๋ฆฌ

OAuth ๋™์ž‘ ๋ฐฉ์‹

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์„œ๋“œํŒŒํ‹ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ ํƒํ•˜๋ฉด ๋กœ๊ทธ์ธ์„ ์œ„ํ•ด ํ•ด๋‹น ์›น ์‚ฌ์ดํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋œ๋‹ค
    (User โ†’ Client)
  2. ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด, ํŠน์ • ์›น์‚ฌ์ดํŠธ์—์„œ ์š”์ฒญํ•œ ํŠน์ • ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค ๊ถŒํ•œ์„ ๋ถ€์—ฌํ• ์ง€ ๋ฌป๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋˜๊ณ ,
    ์›ํ•˜๋Š” ์˜ต์…˜์„ ์„ ํƒํ•˜๋ฉด ์ธ์ฆ ์ฝ”๋“œ ๋˜๋Š” ์˜ค๋ฅ˜ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ํŠน์ • ์‚ฌ์ดํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ๋œ๋‹ค
    (Client โ†” Authorization Server)
  3. ํƒ€์‚ฌ ๋ฆฌ์†Œ์Šค์˜ ์ž‘์—…์— ๋”ฐ๋ผ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ (Client โ†” Resource Server)
Spring Boot OAuth 2 Client ์ธ์ฆ๋ฐฉ์‹
1. Authorization Code Grant ๊ถŒํ•œ ์ฝ”๋“œ ์Šน์ธ ๋ฐฉ์‹
  • ํด๋ผ์ด์–ธํŠธ๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์—์„œ ๊ถŒํ•œ ๋ถ€์—ฌ ์ฝ”๋“œ๋ฅผ ์š”์ฒญํ•˜๊ณ , ์ด๋ฅผ Access Token์œผ๋กœ ๊ตํ™˜
  • ์‚ฌ์šฉ์ž์˜ ๋ฆฌ์†Œ์Šค์— ์•ก์„ธ์Šคํ•ด์•ผ ํ•˜๋Š” ์›น ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค
  • ๊ฐ€์žฅ ๋Œ€์ค‘์ ์ด๊ณ  ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹
2. Implicit Grant ์•”์‹œ์  ์Šน์ธ ๋ฐฉ์‹
  • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Access Token์„ ์ง์ ‘ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ
    ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ(์›น ๋ธŒ๋ผ์šฐ์ € ๋“ฑ)๋ฅผ ํ†ตํ•ด ์ธ๊ฐ€ ๊ณผ์ •์„ ๊ฑฐ์ณ Access Token์„ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๋ฐฉ์‹
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ถŒํ•œ ๋ถ€์—ฌ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์š”์ฒญํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ง์ ‘ ์•ก์„ธ์Šค ํ† ํฐ์„ ์š”์ฒญํ•˜๋Š”๋ฐ,
    ๋ณด์•ˆ ์ทจ์•ฝ์  ๋•Œ๋ฌธ์— ๊ถŒ์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค
3. Client Credentials Grant ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ ์ฆ๋ช… ๋ฐฉ์‹
  • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ž์‹ ์˜ ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Access Token์„ ์ง์ ‘ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๋ฐฉ๋ฒ•
  • ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž์ฒด์˜ ์ธ์ฆ์— ์‚ฌ์šฉ๋จ
  • ์ผ๋ฐ˜์ ์ธ ๋กœ๊ทธ์ธ ๋ฐฉ๋ฒ•

1.3. ์„ธ์…˜๊ณผ ์ฟ ํ‚ค

์„ธ์…˜ ์ฟ ํ‚ค ๋กœ๊ทธ์ธ

  • ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋Š” ์„ธ์…˜ ์ €์žฅ์†Œ์— ์ €์žฅ๋˜๊ณ , ์ฟ ํ‚ค๋Š” ๊ทธ ์ €์žฅ์†Œ๋ฅผ ํ†ต๊ณผํ•  ์ˆ˜ ์žˆ๋Š” ์ถœ์ž…์ฆ ์—ญํ• 

  • ์ฟ ํ‚ค๊ฐ€ ๋‹ด๊ธด HTTP ์š”์ฒญ์ด ๋„์ค‘์— ๋…ธ์ถœ๋˜๋”๋ผ๋„ ์ฟ ํ‚ค ์ž์ฒด์—๋Š” ์œ ์˜๋ฏธํ•œ ๊ฐ’์„ ๊ฐ–๊ณ ์žˆ์ง€ ์•Š์•„์„œ ์ฟ ํ‚ค์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด์•„ ์ธ์ฆ์„ ๊ฑฐ์น˜๋Š” ๊ฒƒ ๋ณด๋‹ค ์•ˆ์ „ํ•˜๋‹ค

  • ๊ฐ๊ฐ์˜ ์‚ฌ์šฉ์ž๋Š” ๊ณ ์œ ์˜ Session ID๋ฅผ ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์ผ์ผ์ด ํšŒ์› ์ •๋ณด๋ฅผ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์—†์–ด ์„œ๋ฒ„ ์ž์›์— ์ ‘๊ทผํ•˜๊ธฐ ์šฉ์ดํ•˜๋‹ค

  • ์„ธ์…˜ ํ•˜์ด์žฌํ‚น ๊ณต๊ฒฉ

    • ์ฟ ํ‚ค์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด์•„ ์ธ์ฆ์„ ๊ฑฐ์น˜๋Š” ๊ฒƒ ๋ณด๋‹ค ์•ˆ์ „ํ•˜์ง€๋งŒ, ํ•ด์ปค๊ฐ€ ์ฟ ํ‚ค๋ฅผ ํƒˆ์ทจํ•œ ํ›„ ๊ทธ ์ฟ ํ‚ค๋ฅผ ์ด์šฉํ•ด HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์„œ๋ฒ„๋Š” ์‚ฌ์šฉ์ž๋กœ ์˜ค์ธํ•ด ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๊ฒŒ ๋œ๋‹ค
    • HTTPS ํ”„๋กœํ† ์ฝœ ์‚ฌ์šฉ๊ณผ ์„ธ์…˜์— ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๋„ฃ์–ด ์–ด๋А ์ •๋„ ๋ณด์™„ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์„œ๋ฒ„์—์„œ ์„ธ์…˜ ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ์ €์žฅ๊ณต๊ฐ„์ด ํ•„์š”ํ•˜๋‹ค

2. Access Token ๋ฐœ๊ธ‰ ๋ฐ ๊ฒ€์ฆ ๋กœ์ง ๊ตฌํ˜„

2.1. ๐ŸŒ Spring Security Architecture

Spring Security Architecture

  1. Http Request - ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์ •๋ณด์™€ ํ•จ๊ป˜ ์ธ์ฆ ์š”์ฒญ

  2. AuthenticationFilter๊ฐ€ ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ ,
    > ๊ฐ€๋กœ์ฑˆ ์ •๋ณด๋ฅผ ํ†ตํ•ด UsernamePasswordAuthenticationToken์ด๋ผ๋Š” ์ธ์ฆ์šฉ ๊ฐ์ฒด ์ƒ์„ฑํ•ด์„œ

  3. AuthenticationManager์˜ ๊ตฌํ˜„์ฒด์ธ ProviderManager์—๊ฒŒ ์ƒ์„ฑํ•œ UsernamePasswordAuthenticationToken ๊ฐ์ฒด ์ „๋‹ฌ

  4. AuthenticationManager๋Š” ๋“ฑ๋ก๋œ AuthenticationProvider๋“ค์„ ์กฐํšŒํ•˜๊ณ  ์ธ์ฆ ์š”๊ตฌ

  5. AuthenticationProvider๋Š” ์‹ค์ œ DB์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” UserDetailsService์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค

  6. UserDetailsService๋Š” AuthenticationProvider์—๊ฒŒ ๋„˜๊ฒจ๋ฐ›์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ†ตํ•ด,
    > DB์—์„œ ์ฐพ์€ ์‚ฌ์šฉ์ž ์ •๋ณด์ธ UserDetails ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค

  7. AuthenticationProvider๋“ค์€ UserDetails ๊ฐ์ฒด๋ฅผ ๋„˜๊ฒจ๋ฐ›๊ณ  ์‚ฌ์šฉ์ž ์ •๋ณด ๋น„๊ต

  8. ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด, ๊ถŒํ•œ ๋“ฑ์˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด์€ Authentication ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค

  9. ๋‹ค์‹œ ์ตœ์ดˆ์˜ AuthenticationFilter์— Authentication ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ณ 

  10. Authenticaton ๊ฐ์ฒด๋ฅผ SecurityContext์— ์ €์žฅ

1. Authentication
  • ํ˜„์žฌ ์ ‘๊ทผํ•˜๋Š” ์ฃผ์ฒด์˜ ์ •๋ณด์™€ ๊ถŒํ•œ์„ ๋‹ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค

  • Authentication ๊ฐ์ฒด๋Š” SecurityContext์— ์ €์žฅ๋˜๋ฉฐ,
    SecurityContextHolder๋ฅผ ํ†ตํ•ด SecurityContext์— ์ ‘๊ทผํ•˜๊ณ ,
    SecurityContext๋ฅผ ํ†ตํ•ด Authentication์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค

2. UsernamePasswordAuthenticationToken
  • Authentication์„ implementsํ•œ AbstractAuthenticationToken์˜ ํ•˜์œ„ ํด๋ž˜์Šค
    ์ฆ‰, Authentication์˜ ๊ตฌํ˜„์ฒด์ด๊ณ , ๊ทธ๋ž˜์„œ AuthenticationManager์—์„œ ์ธ์ฆ๊ณผ์ •์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค

  • ์ถ”ํ›„ ์ธ์ฆ์ด ๋๋‚˜๊ณ  SecurityContextHolder์— ๋“ฑ๋ก๋  Authentication ๊ฐ์ฒด

  • User์˜ ID๋ฅผ Principal ๋กœ, Password๋ฅผ Credential๋กœ ์ƒ์„ฑํ•œ ์ธ์ฆ ๊ฐœ์ฒด

    ์—ฌ๊ธฐ์—์„œ ๋งํ•˜๋Š” Principal ์—ญํ• ์„ ํ•˜๋Š” User์˜ ID ๋˜๋Š” Username์€ ๋กœ๊ทธ์ธ ์‹œ ID์™€ PW์˜ ID๋ฅผ ๋˜ฃํ•œ๋‹ค
    ๋กœ๊ทธ์ธ ์‹œ email์„ ID๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด email์ด, ์ „ํ™”๋ฒˆํ˜ธ๋ฅผ ID๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ „ํ™”๋ฒˆํ˜ธ๊ฐ€ ๊ณง Username์ด ๋œ๋‹ค

  • UsernamePasswordAuthenticationToken์˜ ์ฒซ ๋ฒˆ์งธ ์ƒ์„ฑ์ž๋Š” ์ธ์ฆ ์ „์˜ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ ,
    ๋‘ ๋ฒˆ์งธ๋Š” ์ธ์ฆ์ด ์™„๋ฃŒ๋œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
	super(null);
	this.principal = principal;
	this.credentials = credentials;
	setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
		Collection<? extends GrantedAuthority> authorities) {
	super(authorities);
	this.principal = principal;
	this.credentials = credentials;
	super.setAuthenticated(true); // must use super, as we override
}
3. AuthenticationManager
  • ๋งŒ๋“ค์–ด์ง„ UsernamePasswordAuthenticationToken์€ AuthenticationManager์˜ ์ธ์ฆ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค
  • ์ธ์ฆ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์€ AuthenticationManager๋ฅผ ํ†ตํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋˜๋Š”๋ฐ,
    ์‹ค์งˆ์ ์œผ๋กœ๋Š” AuthenticationManager์— ๋“ฑ๋ก๋œ AuthenticationProvider์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋œ๋‹ค
  • ์ธ์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด ๋‘ ๋ฒˆ์งธ ์ƒ์„ฑ์ž๋ฅผ ์ด์šฉํ•ด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ SecurityContext์— ์ €์žฅํ•œ๋‹ค
4. AuthenticationProvider
  • AuthenticationManager์˜ ๊ตฌํ˜„์ฒด
  • AuthenticationProvider์—์„œ๋Š” ์‹ค์ œ ์ธ์ฆ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ,
    ์ธ์ฆ ์ „์˜ Authentication ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์„œ ์ธ์ฆ์ด ์™„๋ฃŒ๋œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค
  • Customํ•œ AuthenticationProvider๋ฅผ ์ž‘์„ฑํ•˜๊ณ  AuthenticationManager์— ๋“ฑ๋กํ•˜๋ฉด ๋œ๋‹ค
5. ProviderManager
  • AuthenticationManager๋ฅผ implementsํ•œ ๊ตฌํ˜„์ฒด ProviderManager๋Š”
    AuthenticationProvider๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ชฉ๋ก์„ ๊ฐ–๋Š”๋‹ค
6. UserDetailsService
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  • Spring Security์˜ interface์ด๊ณ , ๊ตฌํ˜„์ฒด๋Š” ์ง์ ‘ ๊ฐœ๋ฐœํ•ด์•ผํ•œ๋‹ค (customize)
  • username์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒ€์ƒ‰ํ•œ UserDetails ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•˜๋‚˜์˜ ๋ฉ”์†Œ๋“œ loadUserByUsername ๋งŒ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ ์ด๋ฅผ implementsํ•œ ํด๋ž˜์Šค์— UserRepository๋ฅผ ์ฃผ์ž…๋ฐ›์•„ DB์™€ ์—ฐ๊ฒฐํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค
  • UserDetailsService๋Š” DB์— ์ €์žฅ๋œ ํšŒ์›์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋น„๊ตํ•˜๊ณ ,
    ์ผ์น˜ํ•˜๋ฉด UserDetails ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค
7. UserDetails
  • ์ธ์ฆ์— ์„ฑ๊ณตํ•˜์—ฌ ์ƒ์„ฑ๋œ UserDetails ๊ฐ์ฒด๋Š” Authentication ๊ฐ์ฒด๋ฅผ ๊ตฌํ˜„ํ•œ UsernamePasswordAuthenticationToken์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค
8. SecurityContextHolder
  • ๋ณด์•ˆ ์ฃผ์ฒด์˜ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์—ฌ ์‘์šฉํ”„๋กœ๊ทธ๋žจ์˜ ํ˜„์žฌ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์ •๋ณด๊ฐ€ ์ €์žฅ๋œ๋‹ค
  • SecurityContextHolder๋Š” ThreadLocal์— ์ €์žฅ๋˜์–ด, Thread๋ณ„๋กœ SecurityContextHolder ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—,
    ์‚ฌ์šฉ์ž ๋ณ„๋กœ Authentication ๊ฐ์ฒด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค
9. SecurityContext
  • ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด Authentication์„ ๋ณด๊ด€ํ•˜๋Š” ์—ญํ• 
  • SecurityContext๋ฅผ ํ†ตํ•ด Authentication์„ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ๊บผ๋‚ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.getContext().getAuthentication(authentication);
๐Ÿ‘€ ๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  Authentication ๊ฐ์ฒด๋Š”?

โ†’ UsernamePasswordAuthenticationToken ๊ฐ์ฒด

10. GrantedAuthority
  • ํ˜„์žฌ ์‚ฌ์šฉ์ž(Principal)๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ถŒํ•œ ์˜๋ฏธ
  • ROLE_ADMIN์ด๋‚˜ ROLE_USER์™€ ๊ฐ™์ด ROLE_*์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•œ๋‹ค
  • GrantedAuthority ๊ฐ์ฒด๋Š” UserDetailsService์— ์˜ํ•ด ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๊ณ ,
  • ํŠน์ • ์ž์›์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•ด ์ ‘๊ทผ ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค

2.2. SecurityFilterChain ์„ค์ •

๋ณ€๊ฒฝ
์Šคํ”„๋ง ๋ถ€ํŠธ 3.0 ์ด์ƒ๋ถ€ํ„ฐ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ 6.0.0 ์ด์ƒ์˜ ๋ฒ„์ „์ด ์ ์šฉ๋˜๋ฉฐ
Deprecated๋œ ์ฝ”๋“œ ๋ณ€๊ฒฝ

//.httpBasic().disable()
.httpBasic(HttpBasicConfigurer::disable)
  • UI์ชฝ์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ์„ค์ •
  • Http basic Auth ๊ธฐ๋ฐ˜์œผ๋กœ ๋กœ๊ทธ์ธ ์ธ์ฆ์ฐฝ์ด ๋œจ๋Š”๋ฐ, JWT๋ฅผ ์‚ฌ์šฉํ•  ๊ฑฐ๋ผ ๋œจ์ง€ ์•Š๋„๋ก ์„ค์ •
    + formLogin.disable() : formLogin ๋Œ€์‹  JWT๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— disable๋กœ ์„ค์ •
//.csrf.disable()
//.cors().and()
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
  • API๋ฅผ ์ž‘์„ฑํ•˜๋Š”๋ฐ ํ”„๋ก ํŠธ๊ฐ€ ์ •ํ•ด์ ธ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— csrf ์„ค์ • ์šฐ์„  ๊บผ๋†“๊ธฐ
CSRF
  • Cross Site Request Forgery : ์‚ฌ์ดํŠธ ๊ฐ„ ์œ„์กฐ ์š”์ฒญ
  • ์›น ์‚ฌ์ดํŠธ ์ทจ์•ฝ์  ๊ณต๊ฒฉ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์˜์ง€์™€๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ณต๊ฒฉ์ž๊ฐ€ ์˜๋„ํ•œ ํ–‰์œ„๋ฅผ ํŠน์ • ์›น ์‚ฌ์ดํŠธ์— ์š”์ฒญํ•˜๊ฒŒ ํ•˜๋Š” ๊ณต๊ฒฉ
  • Spring Security์—์„œ๋Š” CSRF์— ๋Œ€ํ•œ ์˜ˆ๋ฐฉ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค
  • ๊ทผ๋ฐ ์ด ์ข‹์€ ๊ธฐ๋Šฅ์„ ์™œ disable?
    • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๋ฌธ์„œ์—์„œ๋Š” ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์š”์ฒญ์— CSRF ๋ณดํ˜ธ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ ,
      ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํด๋ผ์ด์–ธํŠธ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ CSRF ๋ณดํ˜ธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ํ•จ
    • ์—ฌ๊ธฐ์—์„œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํด๋ผ์ด์–ธํŠธ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค โ†’ ๋Œ€๋ถ€๋ถ„์˜ REST API ์„œ๋น„์Šค๋ผ๊ณ  ์ดํ•ดํ•จ
      ์ฆ‰ ๋Œ€๋ถ€๋ถ„์˜ ๊ฐ€์ด๋“œ๋Š” REST API ์„œ๋ฒ„ ๊ธฐ์ค€์œผ๋กœ disable์„ ์ ์šฉํ•˜๊ณ  ์žˆ๋‹ค
CORS
  • Cross-Origin Resource Sharing : ์„œ๋กœ ๋‹ค๋ฅธ Orgin ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ ์‹œ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ด๋ฅผ ์ค‘์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ๋ณดํ˜ธ ๊ธฐ๋Šฅ, ํ”„๋กœํ† ์ฝœ
  • HTTP ์š”์ฒญ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Cross-Site HTTP Requests๊ฐ€ ๊ฐ€๋Šฅ (๋‹ค๋ฅธ ๋„๋ฉ”์ธ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
    ํ•˜์ง€๋งŒ Cross-Site HTTP Requests๋Š” Same Origin Policy๋ฅผ ์ ์šฉ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—,
    ํ”„๋กœํ† ์ฝœ, ํ˜ธ์ŠคํŠธ๋ช…, ํฌํŠธ๊ฐ€ ๊ฐ™์•„์•ผ๋งŒ ์š”์ฒญ์ด ๊ฐ€๋Šฅํ•˜๋‹ค
  • cors()๋กœ cors์— ๋Œ€ํ•œ ์ปค์Šคํ…€ ์„ค์ • ํ—ˆ์šฉ
    • addAllowedOrigin() : ํ—ˆ์šฉํ•  URL ์„ค์ •
    • addAllowedHeader() : ํ—ˆ์šฉํ•  Header ์„ค์ •
    • addAllowedMethod() : ํ—ˆ์šฉํ•  Http Method ์„ค์ •
//.authorizeRequests()
//.requestMatchers("/api/**").permitAll()
//.requestMatchers("/api/**/users/join", "/api/**/users/login").permitAll()
.authorizeHttpRequests(authorize -> authorize
    .requestMatchers("/api/**").permitAll()
    .requestMatchers("/api/v1/users/join", "/api/v1/users/login").permitAll())
  • ํŠน์ •ํ•œ ๊ฒฝ๋กœ์— ํŠน์ •ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์„ค์ •

  • authorizeRequests() : ์‹œํ๋ฆฌํ‹ฐ ์ฒ˜๋ฆฌ์— HttpServletRequest๋ฅผ ์ด์šฉํ•œ๋‹ค๋Š” ๊ฒƒ, ๊ฐ ๊ฒฝ๋กœ๋ณ„ ๊ถŒํ•œ ์ฒ˜๋ฆฌ

  • requestMatchers() : ํŠน์ •ํ•œ ๊ฒฝ๋กœ ์ง€์ •

    • ๋งŒ์•ฝ spring-security 5.8 ์ด์ƒ์˜ ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š”
      antMatchers, mvcMatchers, regexMatchers๊ฐ€ ๋” ์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—,
      requestMatchers๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•จ

    URL ํŒจํ„ด /* ๊ณผ /**

    • /* : ๊ฒฝ๋กœ์˜ ๋ฐ”๋กœ ํ•˜์œ„์— ์žˆ๋Š” ๋ชจ๋“  ๊ฒฝ๋กœ ๋งคํ•‘

    ex. AAA/* : AAA/BBB, AAA/CCC ํ•ด๋‹น, AAA/BBB/CCC ํ•ด๋‹นํ•˜์ง€ ์•Š์Œ

    • /** : ๊ฒฝ๋กœ์˜ ๋ชจ๋“  ํ•˜์œ„ ๊ฒฝ๋กœ(๋””๋ ‰ํ† ๋ฆฌ) ๋งคํ•‘

    ex. AAA/** : AAA/BBB, AAA/CCC, AAA/BBB/CCC, AAA/.../.../DDD/..., AAA/BBB/CCC/.../.../... ์ „๋ถ€ ํ•ด๋‹น

  • permitAll() : ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ ์ ˆ์ฐจ ์—†์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ

  • authenticated() : ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ

  • hasRole() : ์‹œ์Šคํ…œ ์ƒ์—์„œ ํŠน์ • ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ๋งŒ์ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ

  • anyRequest().authenticated() : ๋‚˜๋จธ์ง€ ๋ชจ๋“  ๋ฆฌ์†Œ์Šค๋“ค์€ ๋ฌด์กฐ๊ฑด ์ธ์ฆ์„ ์™„๋ฃŒํ•ด์•ผ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์˜๋ฏธ

//.sessionManagement()
//.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionManagement((sessionManagement) -> sessionManagement
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  • ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ session์„ ์‚ฌ์šฉํ•ด ์›น์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ,
    JWT๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— session์„ stateless๋กœ ์„ค์ •, ์„ธ์…˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

2.3. BCryptPasswordEncode ์„ค์ •

๐Ÿชข BCryptPasswordEncode
  • Spring Seurity ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค ์ค‘ ํ•˜๋‚˜๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ ํด๋ž˜์Šค
๐Ÿ”’ BCryptPasswordEncoder.encode(CharSequence rawPassword)
  • ํŒจ์Šค์›Œ๋“œ๋ฅผ ์•”ํ˜ธํ™”ํ•ด์ฃผ๋Š” ๋ฉ”์„œ๋“œ, String ๋ฐ˜ํ™˜
  • ๋˜‘๊ฐ™์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ธ์ฝ”๋”ฉํ•˜๋”๋ผ๋„ ๋งค๋ฒˆ ๋‹ค๋ฅธ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค
๐Ÿ—๏ธ matches(CharSequence rawPassword, String encodedPassword)
  • ์ œ์ถœ๋œ ์ธ์ฝ”๋”ฉ ๋˜์ง€ ์•Š์€ ํŒจ์Šค์›Œ๋“œ(์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ ์ž ํ•˜๋Š” ํŒจ์Šค์›Œ๋“œ)์™€ ์ธ์ฝ”๋”ฉ ๋œ ํŒจ์Šค์›Œ๋“œ์˜ ์ผ์น˜ ์—ฌ๋ถ€ ํ™•์ธ
  • ์ฒซ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ ์ž ํ•˜๋Š” ์ธ์ฝ”๋”ฉ ๋˜์ง€ ์•Š์€ ํŒจ์Šค์›Œ๋“œ,
    ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ธ์ฝ”๋”ฉ๋œ ํŒจ์Šค์›Œ๋“œ ์ž…๋ ฅ
  • boolean ๋ฐ˜ํ™˜

2.4. JwtTokenProvider

2.4.1. ์˜์กด์„ฑ ์ถ”๊ฐ€ ๐Ÿ˜
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
  • JWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ•ต์‹ฌ API๋ฅผ ์ œ๊ณตํ•˜๊ณ  JWT์˜ ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆ์„ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋‹ค
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
  • jjwt-impl ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์€ ์ฑ„ Jwts.builder() ๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
  • jjwt-impl์˜ ๊ตฌํ˜„์ฒด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, jjwt-jackson ์™ธ์—๋„ jjwt-gson์ด ์žˆ๋‹ค
  • jjwt-jackson ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฉด compact ๋ฉ”์„œ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋˜ ๋„์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค
    โ†’ jjwt-impl์—์„œ ๊ตฌํ˜„์ฒด๋ฅผ ์ฐพ์•„๋ณด์ง€๋งŒ ์—†๊ธฐ์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒ
์˜์กด์„ฑ์„ ์„ธ ๊ฐœ๋‚˜ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š”?

jjwt-api ๋Š” ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ์— ์žˆ์–ด์„œ implemenation ๊ณผ runtimeonly ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์˜์กด์„ฑ ์ถ”๊ฐ€๋ฅผ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค
๊ฒฝ๊ณ  ์—†์ด ์–ธ์ œ๋“  ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” ํŒจํ‚ค์ง€๋Š” runtimeonly๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒƒ์€ implemenation์œผ๋กœ ๊ด€๋ฆฌํ•ด
์•ˆ์ •์ ์œผ๋กœ jjwt-api ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๋Š” ์˜๋„
์ฆ‰, jjwt-impl, jjwt-jackson ๋˜๋Š” jjwt-gson ์€ ๊ฒฝ๊ณ ์—†์ด ์–ธ์ œ๋“  ๋ณ€ํ™”ํ•  ์ˆ˜ ์žˆ๊ณ 
jjwt-api๋Š” ํ•˜์œ„ํ˜ธํ™˜์„ฑ์„ ๋งž์ถฐ๊ฐ€๋ฉฐ ๊ฐœ๋ฐœํ•œ๋‹ค๋Š” ์˜๋ฏธ
์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ํ•˜์œ„ํ˜ธํ™˜์„ฑ์— ๋Œ€ํ•œ ์–ธ๊ธ‰๊ณผ @Deprecated๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ์œ ์ง€ํ•˜๋ ค๋Š” ๋…ธ๋ ฅ์„ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ๋‹ค

2.4.2. JWT ์ƒ์„ฑ ์‹œ ํ•„์š”ํ•œ ์ •๋ณด
Jwts ํด๋ž˜์Šค
  • JWT ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋Š” ํŒฉํ† ๋ฆฌ ํด๋ž˜์Šค
Jwts.builder()
public static String createToken(String userName, Key key, long expireTimeMs) {
    Claims claims = Jwts.claims(); //์ผ์ข…์˜ Map
    claims.put("userName", userName);

    return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + expireTimeMs))
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
}
  1. Header ์„ค์ •
    • .setHeaderParam("key", "value") ๋˜๋Š” .setHeader(header)์™€ ๊ฐ™์€ ๋ฐฉ์‹ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
Body(Payload) ์„ค์ •
  1. setClaims() : JWT์— ํฌํ•จ์‹œํ‚ฌ Custom Claims ์ถ”๊ฐ€ - ์ฃผ๋กœ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด

    • .claim("key", "value") ๋˜๋Š” .setClaims(claims)์™€ ๊ฐ™์€ ๋ฐฉ์‹ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  2. setSubject() : JWT์— ๋Œ€ํ•œ ์ œ๋ชฉ

  3. setIssuedAt() : JWT ๋ฐœํ–‰ ์ผ์ž - ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์€ java.util.Date

  4. setExpiration() : JWT์˜ ๋งŒ๋ฃŒ๊ธฐํ•œ - ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์€ java.util.Date

  5. signWith() : ์„œ๋ช…์„ ์œ„ํ•œ Key(java.security.Key) ๊ฐ์ฒด ์„ค์ •

    //.signWith(SignatureAlgorithm.HS256, key)
    .signWith(key, SignatureAlgorithm.HS256)
    signWith(io.jsonwebtoken.SignatureAlgorithm, java.lang.String)' is deprecated
    • ํŠน์ • ๋ฌธ์ž์—ด(String)์ด๋‚˜ byte๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” ๋ฉ”์„œ๋“œ๋กœ ์‚ฌ์šฉ์ด ์ค‘๋‹จ๋˜์—ˆ๋Š”๋ฐ,
      ๋งŽ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์›์‹œ์ ์ธ ์•”ํ˜ธ ๋ฌธ์ž์—ด์„ ํ‚ค ์ธ์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜๋ฉฐ ํ˜ผ๋ž€์Šค๋Ÿฌ์›Œํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํ•œ๋‹ค
    signWith(java.security.Key key, io.jsonwebtoken.SignatureAlgorithm alg)
    • String์ด ์•„๋‹ˆ๋ผ Key ๊ฐ’์„ ์ƒ์„ฑํ•˜๊ณ  ์„œ๋ช…์„ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค
  6. compact() : JWT ์ƒ์„ฑํ•˜๊ณ  ์ง๋ ฌํ™”

2.5. Secret Key ์ƒ์„ฑํ•˜๊ธฐ

๐Ÿ‘€ Secret Key ๋ž€?

ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ Key

์ฝ”๋“œ
String keyBase64Encoded = Base64.getEncoder().encodeToString(key.getBytes());
SecretKey key = Keys.hmacShaKeyFor(keyBase64Encoded.getBytes());
  • ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” plain secretKey(์•”ํ˜ธํ™” ๋˜์ง€ ์•Š์Œ, ์ฒซ ๋ฒˆ์งธ ์ค„์˜ key)๋ฅผ byte๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๊ณ ,
  • HMAC-SHA ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ†ตํ•ด ์•”ํ˜ธํ™”ํ•ด์ฃผ๋Š” Keys.hmacShaKeyFor๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”๋œ Key ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ฝ”๋“œ
io.jsonwebtoken.security.WeakKeyException
  • secretKey๊ฐ€ 256bit๋ณด๋‹ค ์ปค์•ผ ํ•œ๋‹ค๋Š” Exception - ์•ŒํŒŒ๋ฒณ ํ•œ ๊ธ€์ž๋‹น 8bit์ด๋ฏ€๋กœ 32๊ธ€์ž ์ด์ƒ์ด์–ด์•ผ ํ•œ๋‹ค๋Š” ๋œป
  • ํ•œ๊ธ€์€ ํ•œ ๊ธ€์ž ๋‹น 16bit์ธ๋ฐ 16๊ธ€์ž์ด๋ฉด ์ƒ์„ฑ๋ ๊นŒ? โ†’ ์ƒ์„ฑ๋œ๋‹ค

2.6. JWT - JWT ๊ฒ€์ฆํ•˜๊ธฐ

  1. Jwts.parserBuilder() ๋ฉ”์†Œ๋“œ๋กœ JwtParserBuilder ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
  2. JWS ์„œ๋ช… ๊ฒ€์ฆ์„ ์œ„ํ•œ SecretKey ๋˜๋Š” ๋น„๋Œ€์นญ ๊ณต๊ฐœํ‚ค ์ง€์ • > > TOKEN ๋ฐœ๊ธ‰ ์‹œ ์‚ฌ์šฉํ–ˆ๋˜ secretKey
  3. build() ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด thread-safeํ•œ JwtParser๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
  4. parseClaimsJws(jwtString) ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์˜ค๋ฆฌ์ง€๋„ signed JWT๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค
  5. ๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด Exception ๋ฐœ์ƒ
JWT TOKEN ํŒŒ์‹ฑํ•˜๊ธฐ
Jws<Claims> jws = Jwts.parserBuilder()
        .setSigningKey(key)
        .build()
        .parseClaimsJws(token); 
  • parseClaimsJws(token)

    • ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์–ด์ง„ JWT ํ† ํฐ ํŒŒ์‹ฑ
    • JWT ํ† ํฐ์˜ ๊ตฌ์„ฑ ์š”์†Œ Header, Body(Payload), Signature๋ฅผ ๋ถ„์„ํ•˜๊ณ ,
      ์„œ๋ช…์„ ํ™•์ธํ•ด JWT์˜ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ
    • JWT ํ† ํฐ ์ƒ์„ฑ ์‹œ์˜ Claim ์ •๋ณด๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋‹ค
  • parseClaimsJwt()

    • parseClaimsJws()๊ฐ€ ์•„๋‹ˆ๋ผ parseClaimsJwt()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜ค๋ฅ˜ ๋ฐœ์ƒ
    • ์ฒ˜์Œ์— TOKEN์„ ์ƒ์„ฑํ•  ๋•Œ signWith()๋ฅผ ํ†ตํ•ด ์„œ๋ช…์„ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—
      ๋ณตํ˜ธํ™” ์‹œ์—๋„ ์„œ๋ช…์— ๋Œ€ํ•œ ๊ฒ€์ฆ์„ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค
    • parseClaimsJwt()๋Š” ์„œ๋ช… ๊ฒ€์ฆ ์—†์ด ๋‹จ์ˆœํžˆ ํ—ค๋”์™€ ํด๋ ˆ์ž„๋งŒ ์ถ”์ถœํ•œ๋‹ค
    • parseClaimsJwt()๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด TOKEN ์ƒ์„ฑ ์‹œ signWith()๋ฅผ ํ†ตํ•ด ์„œ๋ช…์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š์œผ๋ฉด ๋œ๋‹ค
Claims claims = jws.getBody();
  • getBody()

    • TOKEN์˜ Claim ์ •๋ณด ๋˜๋Š” ํ† ํฐ์— ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ,
      ์ฆ‰, TOKEN ์ƒ์„ฑ ์‹œ ํฌํ•จํ•œ ์‚ฌ์šฉ์ž ์ •๋ณด, ๊ถŒํ•œ, ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋“ฑ์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋‹ค
  • ์ด ์™ธ์—๋„ getHeader()์™€ getSignature()๋ฅผ ํ†ตํ•ด ๊ฐ๊ฐ TOKEN์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ์„œ๋ช…์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ๋‹ค

Claim ์ถ”์ถœํ•˜๊ธฐ
String username = claims.get("username", String.class); // "username" ํด๋ ˆ์ž„ ๊ฐ’ ์ถ”์ถœ
String role = claims.get("role", String.class); // "role" ํด๋ ˆ์ž„ ๊ฐ’ ์ถ”์ถœ
Date expiration = claims.getExpiration();
Date issuedAt = claims.getIssuedAt();
  • get()

    • ํ‚ค์™€ ๊ฐ’์˜ ์Œ์œผ๋กœ ์ €์žฅ๋œ Claim์€ ํ‚ค๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค
      public abstract <T> T get(String claimName, Class<T> requiredType)
    • Claim ํ‚ค์™€ ํƒ€์ž…์— ๋งž๋Š” ๊ฐ’ ๋ฐ˜ํ™˜
  • ์ด ์™ธ์—๋„ TOKEN ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ์ถ”์ถœํ•˜๋Š” getExpiration()์ด๋‚˜
    TOKEN ์ƒ์„ฑ ์‹œ๊ฐ„์„ ์ถ”์ถœํ•˜๋Š” getIssuedAt() ๋“ฑ์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋‹ค

3. ํšŒ์›๊ฐ€์ž… ๋ฐ ๋กœ๊ทธ์ธ API ๊ตฌํ˜„ํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ

3.1. ๐Ÿชช ํšŒ์›๊ฐ€์ž…

ํšŒ์›๊ฐ€์ž… ๋กœ์ง
  1. ์ค‘๋ณต ์ฒดํฌ
    • UserDuplicatedException()
  2. ํšŒ์›๊ฐ€์ž…
    • BCryptPasswordEncoder.encode() - ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”ํ•ด์„œ ์ €์žฅ
    public ResponseEntity<Void> signUp(SignUpDto signUpDto) {
        //์ค‘๋ณต์ฒดํฌ
        userRepository.findByPhone(signUpDto.getPhone())
                .ifPresent(user -> {
                    throw new UserDuplicatedException();
                });

        //ํšŒ์›๊ฐ€์ž…
        userRepository.save(User.builder()
                .phone(signUpDto.getPhone())
                .nickname(signUpDto.getNickname())
                .role(Role.USER)
                .password(passwordEncoder.encode(signUpDto.getPassword()))
                .build()
        );

        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

ํšŒ์›๊ฐ€์ž… ํฌ์ŠคํŠธ๋งจ ํšŒ์›๊ฐ€์ž… ๋””๋น„

3.2. ๐Ÿ” ๋กœ๊ทธ์ธ

3.2.1. ๋กœ๊ทธ์ธ ๋กœ์ง
  1. ๋กœ๊ทธ์ธ์šฉ ID ํ™•์ธ
    • UserNotFoundException
  2. ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
    • InvalidPasswordException()
  3. TOKEN ๋ฐœํ–‰
public SignInResponseDto signIn(SignInDto signInDto) {
        //์ „ํ™”๋ฒˆํ˜ธ ํ™•์ธ
        User user = userRepository.findByPhone(signInDto.getPhone())
                .orElseThrow(UserNotFoundException::new);

        //๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ
        if (!passwordEncoder.matches(signInDto.getPassword(), user.getPassword())) {
            throw new InvalidPasswordException();
        }

        //TOKEN ๋ฐœํ–‰
        String accessToken = jwtTokenProvider.createAccessToken(user.getId(), signInDto.getPhone(), user.getRole().toString());
        
        return SignInResponseDto.builder().accessToken(accessToken).build();
    }

๋กœ๊ทธ์ธ ์•„์ด๋”” ์˜ค๋ฅ˜ ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์˜ค๋ฅ˜ ๋กœ๊ทธ์ธ ํฌ์ŠคํŠธ๋งจ

4. ํ† ํฐ์ด ํ•„์š”ํ•œ API ๊ตฌํ˜„ํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ

๐Ÿงฟ ์ธ์ฆ๊ณผ ์ธ๊ฐ€

  1. ๋ชจ๋“  POST ์ ‘๊ทผ ๋ง‰๊ธฐ
    • JwtAuthenticationFilter ์ธ์ฆ ๊ณ„์ธต ์ถ”๊ฐ€ํ•˜๊ธฐ
    • ๋ชจ๋“  ์š”์ฒญ์— ๊ถŒํ•œ ๋ถ€์—ฌํ•˜๊ธฐ
  2. TOKEN ์—ฌ๋ถ€ ํ™•์ธ
    • TOKEN ์žˆ์œผ๋ฉด ๊ถŒํ•œ ๋ถ€์—ฌ
    • TOKEN์ด ์—†์œผ๋ฉด ๊ถŒํ•œ ๋ถ€์—ฌํ•˜์ง€ ์•Š๊ธฐ
  3. TOKEN ์œ ํšจ์„ฑ ๊ฒ€์ฆ
    • TOKEN์˜ ์œ ํšจ์‹œ๊ฐ„์ด ์ง€๋‚ฌ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ
  4. TOKEN์—์„œ userName(id) ๊บผ๋‚ด์„œ Controller์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ

๐Ÿ” ์ธ์ฆ Authentication

  • ์ฆ๋ช…ํ•˜๋‹ค๋ผ๋Š” ์˜๋ฏธ๋กœ, ์˜ˆ๋ฅผ ๋“ค์–ด ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ํ•˜๋Š” ๊ณผ์ •

  • ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ๋ณธ์ธ์ด ๋งž๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •

โœ… ์ธ๊ฐ€ Authorization

  • ๊ถŒํ•œ๋ถ€์—ฌ๋‚˜ ํ—ˆ๊ฐ€์™€ ๊ฐ™์€ ์˜๋ฏธ๋กœ ์‚ฌ์šฉ๋˜๊ณ , ์–ด๋–ค ๋Œ€์ƒ์ด ํŠน์ • ๋ชฉ์ ์„ ์‹คํ˜„ํ•˜๋„๋ก ํ—ˆ์šฉ(Access) ํ•˜๋Š” ๊ฒƒ ์˜๋ฏธ

  • ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•˜๋Š” ์ž์›์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์ด ์žˆ๋Š”๊ฐ€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ณผ์ •

4.1. ๋ชจ๋“  ์š”์ฒญ์— ๊ถŒํ•œ ๋ถ€์—ฌํ•˜๊ธฐ

API ์š”์ฒญ์— ๋Œ€ํ•ด ์ ‘๊ทผ ๊ถŒํ•œ ์„ค์ •

์•ž์„œ ๋กœ๊ทธ์ธ์—์„œ ์„ค์ •ํ–ˆ๋˜ SecurityConfig์˜ SecurityFilterChain ์žฌ์ •์˜ ์ด์šฉ
โ†’ @EnableWebSecurity

.authorizeHttpRequests(authorize -> authorize
    .requestMatchers("/api/*/*/signup", "/api/*/*/signin").permitAll()
    .requestMatchers(HttpMethod.GET).permitAll()
    .requestMatchers(HttpMethod.POST, "/api/**").authenticated())
  • ํšŒ์›๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ์€ ๋ˆ„๊ตฌ๋‚˜ ๊ถŒํ•œ ์—†์ด ์–ธ์ œ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ
  • ๋ฆฌ๋ทฐ ์“ฐ๊ธฐ ๋“ฑ ๋‹ค๋ฅธ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” ๊ถŒํ•œ ํ•„์š”
JwtFilter ์ธ์ฆ ๊ณ„์ธต ์ถ”๊ฐ€ํ•˜๊ธฐ
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), 
        UsernamePasswordAuthenticationFilter.class)
  • addFilterBefore()
    • JWT ์ธ์ฆ ํ•„ํ„ฐ JwtAuthenticationFilter๋ฅผ UsernamePasswordAuthenticationFilter ์ด์ „์— ์ถ”๊ฐ€ํ•˜๋Š” ์—ญํ• 
    • ํ† ํฐ์ด ์žˆ๋Š”์ง€ ๋งค๋ฒˆ ํ•ญ์ƒ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค
    public HttpSecurity addFilterBefore(
        @NotNull jakarta.servlet.Filter filter,
        Class<? extends jakarta.servlet.Filter> beforeFilter)
๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ๊ถŒํ•œ ๋ถ€์—ฌํ•˜๊ธฐ
@Override
protected void doFilterInternal(
        HttpServletRequest request, 
        HttpServletResponse response, 
        FilterChain filterChain) throws ServletException, IOException { ... }
  • Filter ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค์—์„œ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ๋ฉ”์†Œ๋“œ ์ค‘ ํ•˜๋‚˜
  • HTTP ์š”์ฒญ์„ ํ•„ํ„ฐ๋งํ•˜๊ณ , ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• 
    1. Header์—์„œ TOKEN ๊บผ๋‚ด๊ธฐ
    2. TOKEN ์—ฌ๋ถ€์™€ ์œ ํšจ์„ฑ ํ™•์ธ
    3. TOKEN์ด ์œ ํšจํ•˜๋ฉด - ๊ถŒํ•œ ๋ถ€์—ฌ
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
  • ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ authentication์œผ๋กœ ๋ณ€๊ฒฝ
  • SecurityContextHolder.getContext()
    • ํ˜„์žฌ ์‚ฌ์šฉ์ž ๋ฐ ์ธ์ฆ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” SecurityContextHolder ๊ฐ์ฒด์—์„œ
    • ํ˜„์žฌ ์‚ฌ์šฉ์ž์™€ ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ์ €์žฅ๋˜๋Š” ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ
  • .setAuthentication(authentication)
    • ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด authentication์œผ๋กœ ์„ค์ •
filterChain.doFilter(request, response);
  • doFilter()
    public abstract void doFilter(
        jakarta.servlet.ServletRequest request,
        jakarta.servlet.ServletResponse response)
    • Filter ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํ•„ํ„ฐ์—์„œ ์ •์˜๋œ ๋ฉ”์†Œ๋“œ
    • ํ•„ํ„ฐ๊ฐ€ ์š”์ฒญ(request) ๋ฐ ์‘๋‹ต(response)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ
    • ํ•„ํ„ฐ๋Š” ์ด ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค
      ex. ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„ ๊ถŒํ•œ ํ™•์ธํ•˜๊ธฐ
    • ํ˜„์žฌ ํ•„ํ„ฐ์—์„œ ์š”์ฒญ ๋ฐ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•˜๊ณ ,
      ์ดํ›„์— ์‹คํ–‰๋  ๋‹ค์Œ ํ•„ํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด FilterChain์˜ doFilter()๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ,
      ์ด ๋•Œ, ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ ๋ฐ ์‘๋‹ต ๊ณ„์† ์ „๋‹ฌ

4.2. TOKEN ์—ฌ๋ถ€ ํ™•์ธ

  • TOKEN ์žˆ์œผ๋ฉด ๊ถŒํ•œ ๋ถ€์—ฌ
  • TOKEN์ด ์—†์œผ๋ฉด ๊ถŒํ•œ ๋ถ€์—ฌํ•˜์ง€ ์•Š๊ธฐ
TOKEN์ด ์—†์œผ๋ฉด ๊ถŒํ•œ ๋ถ€์—ฌํ•˜์ง€ ์•Š๊ธฐ
Authentication authentication = jwtTokenProvider.getAuthentication(token);
        SecurityContextHolder.getContext().setAuthentication(authentication);
ํฌ์ŠคํŠธ๋งจ

ํ† ํฐ ์—ฌ๋ถ€ ํ™•์ธ

  • ํ† ํฐ์ด ์—†์œผ๋ฉด ์ž‘๋™ํ•˜์ง€ ์•Š์Œ!
ํ† ํฐ ์—ฌ๋ถ€ ๊ทผ๋ฐ
์•„๋ฌด TOKEN์„ ๋„ฃ์–ด๋„
์ž‘๋™ํ•˜๋Š” ๋ฌธ์ œ!

4.3. TOKEN ์œ ํšจ์„ฑ ๊ฒ€์ฆ

  • TOKEN์˜ ์œ ํšจ์‹œ๊ฐ„์ด ์ง€๋‚ฌ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ
TOKEN ์œ ํšจ์‹œ๊ฐ„ ๋งŒ๋ฃŒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
    public boolean validateToken(String token) {
        //Token ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋˜๋Š” null ๋ฐ˜ํ™˜
        Date expiration = Jwts.parserBuilder()
        .setSigningKey(key)
        .build()
        .parseClaimsJws(token)
        .getBody()
        .getExpiration();
        boolean isExpired = expiration.before(new Date());

        return !isExpired;
        }
  • TOKEN ๋งŒ๋ฃŒ๋กœ ์ธํ•œ ExpiredJwtException ๋ฐœ์ƒ

4.4. TOKEN์—์„œ ๋กœ๊ทธ์ธID ๊บผ๋‚ด์„œ Controller์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ

๋กœ๊ทธ์ธID ์ถ”์ถœ
public String getUserId(String token) {
    return Jwts.parserBuilder()
    .setSigningKey(key)
    .build()
    .parseClaimsJws(token)
    .getBody()
    .getSubject();
    }
  • TOKEN์—์„œ userName(ID)์˜ Claim ์ถ”์ถœํ•˜๋Š” ๋ฉ”์†Œ๋“œ JwtUtil.getUsername() ์ƒ์„ฑ

  • ๊ทธ๋ฆฌ๊ณ  ์ถ”์ถœํ•œ ๋กœ๊ทธ์ธID๋ฅผ UsernamePasswordAuthenticationToken์— ๋„ฃ์–ด์ฃผ๋ฉด Controller์—์„œ ๋กœ๊ทธ์ธID๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

Controller์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ
import org.springframework.security.core.Authentication;
...
@PostMapping
public ResponseEntity<String> writeReview(Authentication authentication) {
    return ResponseEntity.ok().body(authentication.getName());
}

๋˜๋Š”

@PostMapping
public ResponseEntity<Void> registerPost(@RequestBody RegisterPostRequestDto requestDto, @AuthenticationPrincipal User user) {
    postService.registerPost(requestDto, user);
    return ResponseEntity.ok().build();
}

ํ† ํฐ ๋„ฃ๊ณ  ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ํฌ์ŠคํŠธ๋งจ ํ† ํฐ ๊ฒŒ์‹œ๊ธ€ ๋””๋น„

[5์ฃผ์ฐจ] ๐Ÿณ Docker - ๋กœ์ปฌ

Docker

Docker Architecture

Docker Architecture

  • Docker client : ๋„์ปค ์„ค์น˜ํ–ˆ์„ ๋•Œ ๊ทธ๊ฒŒ ๋ฐ”๋กœ client์ด๊ณ , build, pull, run ๋“ฑ์˜ ๋„์ปค ๋ช…๋ น์–ด ์ˆ˜ํ–‰
  • DOCKER_HOST : ๋„์ปค๊ฐ€ ๋„์–ด์ ธ์žˆ๋Š” ์„œ๋ฒ„ ์˜๋ฏธ, DOCKER_HOST์—์„œ ์ปจํ…Œ์ด๋„ˆ์™€ ์ด๋ฏธ์ง€ ๊ด€๋ฆฌ
  • Docker daemon : ๋„์ปค ์—”์ง„
  • Registry : ์™ธ๋ถ€(remote) ์ด๋ฏธ์ง€ ์ €์žฅ์†Œ๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ๊ณต์œ ํ•œ ์ด๋ฏธ์ง€๋ฅผ ๋‚ด๋ถ€(local) ๋„์ปค ํ˜ธ์ŠคํŠธ์— pullํ•  ์ˆ˜ ์žˆ๋‹ค
    • ์ด๋ ‡๊ฒŒ ๊ฐ€์ ธ์˜จ ์ด๋ฏธ์ง€๋ฅผ runํ•˜๋ฉด ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๋จ
    • public ์ €์žฅ์†Œ : Docker Hub, QUAY
    • private ์ €์žฅ์†Œ : AWS ๋˜๋Š” Docker Registry ์ง์ ‘ ๋„์›Œ์„œ ๋น„๊ณต๊ฐœ๋กœ ์‚ฌ์šฉ

Docker Image์™€ Container

  • ๋„์ปค ์—”์ง„์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ๋‹จ์œ„, ๋„์ปค ์—”์ง„์˜ ํ•ต์‹ฌ
  • ๋„์ปค ์ด๋ฏธ์ง€์™€ ์ปจํ…Œ์ด๋„ˆ๋Š” 1:N ๊ด€๊ณ„
  • ๋„์ปค ์ด๋ฏธ์ง€์™€ ์ปจํ…Œ์ด๋„ˆ์˜ ๊ด€๊ณ„๋Š” ์šด์˜์ฒด์ œ์—์„œ์˜ ํ”„๋กœ๊ทธ๋žจ-ํ”„๋กœ์„ธ์Šค, ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ์˜ ํด๋ž˜์Šค-์ธ์Šคํ„ด์Šค ๊ด€๊ณ„

๋„์ปค ์ด๋ฏธ์ง€์™€ ์ปจํ…Œ์ด๋„ˆ

  • Docker File โ†’ Docker Image

    • docker build ๋ช…๋ น์–ด๋กœ Docker File์„ ํ†ตํ•ด Docker Image ์ƒ์„ฑ
  • Docker Image โ†’ Docker Container

    • Docker Image๋ฅผ docker run์œผ๋กœ ์‹คํ–‰์‹œ์ผœ Docker Container ์ƒ์„ฑ
  • Docker Image

    • ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ํ•„์š”ํ•œ ์š”์†Œ
    [์ €์žฅ์†Œ ์ด๋ฆ„]/[์ด๋ฏธ์ง€ ์ด๋ฆ„]:[ํƒœ๊ทธ]
    • ์ €์žฅ์†Œ ์ด๋ฆ„ : ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋œ ์žฅ์†Œ, ์ €์žฅ์†Œ ์ด๋ฆ„์ด ๋ช…์‹œ๋˜์ง€ ์•Š์€ ์ด๋ฏธ์ง€๋Š” ๋„์ปค ํ—ˆ๋ธŒ์˜ ๊ณต์‹ ์ด๋ฏธ์ง€๋ฅผ ๋˜ฃํ•œ๋‹ค
    • ์ด๋ฏธ์ง€ ์ด๋ฆ„ : ํ•ด๋‹น ์ด๋ฏธ์ง€๊ฐ€ ์–ด๋–ค ์—ญํ• ์„ ํ•˜๋Š”์ง€ ๋‚˜ํƒ€๋‚ด๊ณ  ํ•„์ˆ˜๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค
      • ex. ubuntu:latest : ์šฐ๋ถ„ํˆฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์ด๋ฏธ์ง€
    • ํƒœ๊ทธ : ์ด๋ฏธ์ง€์˜ ๋ฒ„์ „์„ ๋‚˜ํƒ€๋‚ด๊ณ , ์ƒ๋žต ์‹œ ๋„์ปค ์—”์ง„์€ latest๋กœ ์ธ์‹
  • Docker Container

    • ๋„์ปค ์ด๋ฏธ์ง€๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค
    • ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ํ•ด๋‹น ์ด๋ฏธ์ง€์˜ ๋ชฉ์ ์— ๋งž๋Š” ํŒŒ์ผ์ด ๋“ค์–ด ์žˆ๋Š”, ํ˜ธ์ŠคํŠธ์™€ ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ๋กœ๋ถ€ํ„ฐ ๊ฒฉ๋ฆฌ๋œ ์‹œ์Šคํ…œ ์ž์› ๋ฐ ๋„คํŠธ์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋…๋ฆฝ๋œ ๊ณต๊ฐ„(ํ”„๋กœ์„ธ์Šค)์ด ์ƒ์„ฑ๋œ๋‹ค
    • ๋Œ€๋ถ€๋ถ„์˜ ๋„์ปค ์ปจํ…Œ์ด๋„ˆ๋Š” ์ƒ์„ฑ๋  ๋•Œ ์‚ฌ์šฉ๋œ ๋„์ปค ์ด๋ฏธ์ง€์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ์•Œ๋งž์€ ์„ค์ •๊ณผ ํŒŒ์ผ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋„์ปค ์ด๋ฏธ์ง€์˜ ๋ชฉ์ ์— ๋งž๋„๋ก ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ 
    • ์ปจํ…Œ์ด๋„ˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์ด๋ฏธ์ง€์—์„œ ๋ณ€๊ฒฝ๋œ ์‚ฌํ•ญ๋งŒ ์ปจํ…Œ์ด๋„ˆ ๊ณ„์ธต์— ์ €์žฅํ•˜๋ฏ€๋กœ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๋ฌด์—‡์„ ํ•˜๋“ ์ง€ ์›๋ž˜ ์ด๋ฏธ์ง€๋Š” ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค
    • ์ƒ์„ฑ๋œ ๊ฐ ์ปจํ…Œ์ด๋„ˆ๋Š” ๊ฐ๊ธฐ ๋…๋ฆฝ๋œ ํŒŒ์ผ์‹œ์Šคํ…œ์„ ์ œ๊ณต๋ฐ›๊ณ  ํ˜ธ์ŠคํŠธ์™€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด, ํŠน์ • ์ปจํ…Œ์ด๋„ˆ์—์„œ ์–ด๋–ค ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•ด๋„ ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ์™€ ํ˜ธ์ŠคํŠธ๋Š” ๋ณ€ํ™”๊ฐ€ ์—†๋‹ค
      • ex. ๊ฐ™์€ ๋„์ปค ์ด๋ฏธ์ง€๋กœ A, B ๋‘ ๊ฐœ์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค์— A ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ˆ˜์ •ํ•ด๋„ B ์ปจํ…Œ์ด๋„ˆ์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค

0. ๋„์ปค ์ปจํ…Œ์ด๋„ˆ ํ†ต์‹ ํ•˜๊ธฐ

  • ๋„์ปค๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋…๋ฆฝ์ ์ธ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ปจํ…Œ์ด๋„ˆ ๋ฐ–์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค

  • ์ปจํ…Œ์ด๋„ˆ์™€ ํ†ต์‹ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ฐ€๋™์‹œํ‚ค๋ฉด์„œ -p ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ํ˜ธ์ŠคํŠธ์˜ ํฌํŠธ์™€ ์ปจํ…Œ์ด๋„ˆ์˜ ํฌํŠธ๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค

-p ${host_port}:${container_port}
  • ์ด ์„ค์ •์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ˜ธ์ŠคํŠธ(์„œ๋ฒ„ ๋˜๋Š” PC)์—์„œ ์‚ฌ์šฉ ์ค‘์ธ ํฌํŠธ์™€ ๋ฒˆํ˜ธ๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค
docker run --name test1 -d httpd
docker run --name test1 -d -p 8080:80 httpd
  • --name test1 : test1์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ์ƒ์„ฑ
  • -d : ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ๋™์ž‘
  • -p 8080:80: ํ˜ธ์ŠคํŠธ์˜ ํฌํŠธ๋Š” 8080, ์ปจํ…Œ์ด๋„ˆ์˜ ํฌํŠธ๋Š” 80์œผ๋กœ ์„ธํŒ…ํ•ด ๋„คํŠธ์›Œํฌ ์„ค์ •
docker ps -a
docker container ls -a
  • ๋™์ผํ•œ ๋‘ ๊ฐœ์˜ ๋ช…๋ น์–ด
  • -a ์˜ต์…˜ : ์—†์œผ๋ฉด ์‹คํ–‰ ์ค‘์ธ ์ปจํ…Œ์ด๋„ˆ๋งŒ ๋ณด์—ฌ์คŒ
    • ๋ถ™์—ฌ์ฃผ๋ฉด ๋‹ค์–‘ํ•œ ์ƒํƒœ์˜ ์ปจํ…Œ์ด๋„ˆ ํ™•์ธ ๊ฐ€๋Šฅ
  • ์œ„์˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ด ์ปจํ…Œ์ด๋„ˆ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค
docker stop test1
docker rm test1
  • ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ ์ค‘์ง€ ๋ฐ ์‚ญ์ œ ๋ช…๋ น์–ด

1. Dockerfile

1.1. Dockerfile์ด๋ž€?

  • ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ
  • ์—ฌ๋Ÿฌ ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด dockerfile์„ ์ž‘์„ฑํ•ด ๋นŒ๋“œ๋ฅผ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค

1.2. dockerfile์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ฃผ์š” ๋ช…๋ น์–ด

  • FROM : base๊ฐ€ ๋˜๋Š” image ์ง€์ •, ์ฃผ๋กœ OS ์ด๋ฏธ์ง€๋‚˜ ๋Ÿฐํƒ€์ž„ ์ด๋ฏธ์ง€๋ฅผ ์ง€์ •
  • RUN : ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ปค๋งจ๋“œ๋ฅผ ์„ค์ •ํ•  ๋•Œ ์‚ฌ์šฉ
  • ADD : ์ด๋ฏธ์ง€์— ํ˜ธ์ŠคํŠธ์˜ ํŒŒ์ผ์ด๋‚˜ ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • ๋งŒ์•ฝ ์ด๋ฏธ์ง€์— ๋ณต์‚ฌํ•˜๋ ค๋Š” ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด docker๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ
  • COPY : ํ˜ธ์ŠคํŠธ ํ™˜๊ฒฝ์˜ ํŒŒ์ผ์ด๋‚˜ ํด๋”๋ฅผ ์ด๋ฏธ์ง€ ์•ˆ์œผ๋กœ ๋ณต์‚ฌํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • ADD์™€ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ ๊ฐ€์žฅ ํ™•์‹คํ•œ ์ฐจ์ด์ ์€ URL์„ ์ง€์ •ํ•˜๊ฑฐ๋‚˜ ์••์ถ•ํŒŒ์ผ์„ ์ž๋™์œผ๋กœ ํ’€์ง€ ์•Š์Œ
  • EXPOSE : ์ด๋ฏธ์ง€๊ฐ€ ํ†ต์‹ ์— ์‚ฌ์šฉํ•  ํฌํŠธ๋ฅผ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ
  • ENV : ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ง€์ • ์‹œ ์‚ฌ์šฉ
    • $name, ${name}์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    • ${name:-else} : name์ด ์ •์˜๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค๋ฉด else๊ฐ€ ์‚ฌ์šฉ๋จ
  • CMD : ๋„์ปค ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์‹คํ–‰ํ•  ์ปค๋งจ๋“œ ์ง€์ •
    • RUN๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ•  ๋•Œ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ์‹คํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์ด ๋‹ค๋ฅด๋‹ค
  • ENTRYPOINT : ๋„์ปค ์ด๋ฏธ์ง€๊ฐ€ ์‹คํ–‰๋  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋ณธ ์ปค๋งจ๋“œ ์ง€์ • (๊ฐ•์ œ)
  • WORKDIR : RUN, CMD, ENTRYPOINT ๋“ฑ์„ ์‚ฌ์šฉํ•œ ์ปค๋งจ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •
    • -W ์˜ต์…˜์œผ๋กœ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ๊ฐ€๋Šฅ
  • VOLUME : ํผ์‹œ์Šคํ„ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ
    • ํ˜ธ์ŠคํŠธ์˜ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋„์ปค ์ปจํ…Œ์ด๋„ˆ์— ์—ฐ๊ฒฐ
    • ์ฃผ๋กœ ํœ˜๋ฐœ์„ฑ์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉด ์•ˆ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋•Œ ์‚ฌ์šฉ

1.3. docker build ๋ช…๋ น์–ด

docker build ${option} ${dockerfile directory}
docker build -t test1 . 
  • dockerfile์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ docker build ์ปค๋งจ๋“œ
  • ์ด๋ฏธ์ง€์˜ ์ด๋ฆ„ test
  • .์œผ๋กœ ๋„์ปค ํŒŒ์ผ์˜ ์œ„์น˜
docker run --name test_app -p 80:80 test1
  • ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋ฅผ ์ปจํ…Œ์ด๋„ˆ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ

1.4. dockerfile

FROM openjdk:17-jdk-slim 
#์ด Docker ์ด๋ฏธ์ง€๋Š” OpenJDK 17๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•จ, Java 17์„ ์„ค์น˜ํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ์ œ๊ณต
ARG JAR_FILE=/build/libs/*.jar
#Docker ๋นŒ๋“œ ์‹œ์— ์ „๋‹ฌ๋˜๋Š” ์ธ์ž(Argument)๋กœ, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ JAR ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •
COPY ${JAR_FILE} app.jar
# ์•ž์„œ ์ •์˜ํ•œ JAR_FILE ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด ๋นŒ๋“œ๋œ JAR ํŒŒ์ผ์„ Docker ์ด๋ฏธ์ง€ ๋‚ด๋ถ€๋กœ ๋ณต์‚ฌ
# ์ด๋•Œ, app.jar๋กœ ํŒŒ์ผ์„ ๋ณต์‚ฌํ•˜๊ฒŒ ๋œ๋‹ค
ENTRYPOINT ["java","-jar", "/app.jar"]
#์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์‹คํ–‰๋˜๋Š” ๋ช…๋ น์–ด ์„ค์ • 
#์ด ๊ฒฝ์šฐ, Java๋กœ JAR ํŒŒ์ผ์„ ์‹คํ–‰ํ•˜๋Š” ๋ช…๋ น์–ด ์ง€์ •

๐Ÿšจ DB ์—ฐ๊ฒฐ ์•ˆ ๋˜๋Š” ๋ฌธ์ œ ๐Ÿคฏ๐Ÿ˜ฃ๐Ÿ˜ก๐Ÿซ ๐Ÿ˜ฑ๐Ÿฅน๐Ÿฅบ

  1. jdbc ์˜์กด์„ฑ ์ถ”๊ฐ€ โ†’ ์•„๋‹˜
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
  1. mysql ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ•ํ™” : ๋Œ€์†Œ๋ฌธ์ž, ์ˆซ์ž, ํŠน์ˆ˜๋ฌธ์ž ์กฐํ•ฉ โ†’ ์•„๋‹˜

  2. application.yml์—์„œ spring datasource url ์„ค์ • ๋ณ€๊ฒฝ โ†’ ํ•ด๊ฒฐ applicationYML

  • application.yml์—์„œ host.docker.internal:3306 ์œผ๋กœ ์—ฐ๊ฒฐ

2. docker-compose.yml

2.1. docker-compose.yml ํŒŒ์ผ์ด๋ž€?

  • ๋„์ปค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„œ๋น„์Šค, ๋„คํŠธ์›Œํฌ, ๋ณผ๋ฅจ ๋“ฑ์˜ ์„ค์ •์„ yml ํ˜•์‹์œผ๋กœ ์ €์žฅํ•˜๋Š” ํŒŒ์ผ
์„ค๋ช… ๊ณต์‹ ๋ฌธ์„œ์˜ ์˜ˆ์ œ ํŒŒ์ผ
๋„์ปค ์ปดํฌ์ฆˆ ๊ณต์‹ ์˜ˆ์ œ ํฐ ํ‹€์—์„œ์˜ ๊ตฌ์„ฑ ์š”์†Œ๋Š”
service, volumn, config,
secret, network,
๊ทธ๋ฆฌ๊ณ  version์ด ์žˆ๋Š”๋ฐ,
์ด ์ค‘ version์€ derprecated๋˜์–ด
๋” ์ด์ƒ ์„ค์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค

2.2. services

  • ์—ฌ๋Ÿฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค
services:
  frontend:
    image: awesome/webapp

  backend:
    image: awesome/database
  • 'frontend'์™€ 'backend'๋Š” ๊ฐ container๋ฅผ ์ •์˜ํ•˜๊ณ , ๊ฐ container์˜ ์ด๋ฆ„์ด ๋œ๋‹ค
  • awesome/database๋ผ๋Š” ๋„์ปค image๋ฅผ ๊ฐ€์ง€๊ณ  container๋ฅผ ๊ฐ€๋™ํ•˜๊ฒŒ ๋˜๋ฉด container์˜ ์ด๋ฆ„์ด 'backend'๊ฐ€ ๋œ๋‹ค๋Š” ์˜๋ฏธ

2.3. container๋ฅผ ์„ค์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ํ‚ค์›Œ๋“œ

  • image : ์ปจํ…Œ์ด๋„ˆ์˜ ์ด๋ฏธ์ง€ ์ •์˜
  • build : ์ด๋ฏธ์ง€๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์•„๋‹Œ dockerfile์˜ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด ๋นŒ๋“œํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
    • ์ด๋ฏธ์ง€๋ฅผ ์–ด๋””์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ,
      ์ด build๋ฅผ ํ†ตํ•ด dockerfile์˜ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด ์ง์ ‘ ๋นŒ๋“œํ•ด์„œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋„์šธ ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•
  • dockerfile : ๋นŒ๋“œํ•  dockerfile์˜ ์ด๋ฆ„์ด Dockerfile์ด ์•„๋‹Œ ๊ฒฝ์šฐ ์ด๋ฆ„์„ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
  • ports : ํ˜ธ์ŠคํŠธ์™€ ์ปจํ…Œ์ด๋„ˆ์˜ ํฌํŠธ ๋ฐ”์ธ๋”ฉ ์„ค์ •์— ์‚ฌ์šฉ๋จ
  • volumes : ํ˜ธ์ŠคํŠธ์˜ ์ง€์ •๋œ ๊ฒฝ๋กœ๋กœ ์ปจํ…Œ์ด๋„ˆ์˜ ๋ณผ๋ฅจ์„ ๋งˆ์šดํŠธ ํ•˜๋„๋ก ์„ค์ •
  • container_name : ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์„ค์ •
  • command : ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹คํ–‰๋œ ํ›„ ์ปจํ…Œ์ด๋„ˆ ์‰˜์—์„œ ์‹คํ–‰์‹œํ‚ฌ ์‰˜ ๋ช…๋ น์–ด
  • environment : ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
  • env_file : environment์™€ ๋™์ผํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, ์ด ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด envํŒŒ์ผ์„ ์ด์šฉํ•ด ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
  • depends_on : ๋‹ค๋ฅธ ์ปจํ…Œ์ด๋„ˆ์™€ ์˜์กด๊ด€๊ณ„ ์„ค์ •
  • restart : ์ปจํ…Œ์ด๋„ˆ์˜ ์žฌ์‹œ์ž‘๊ณผ ๊ด€๋ จํ•œ ์„ค์ •
    • ์–ด๋–ค ์˜ค๋ฅ˜๋กœ ์ธํ•ด ์ด๋ฏธ์ง€๊ฐ€ ์‹คํ–‰์ด ์•ˆ ๋์„ ๋•Œ ๋ฉˆ์ถœ ๊ฑด์ง€ ๋‹ค์‹œ ์‹คํ–‰ํ•  ๊ฑด์ง€

2.4. docker compose ํŒŒ์ผ ์‹คํ–‰

docker-compose up
  • ํ•ด๋‹น ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ๋กœ์—์„œ docker-compose.yml ํŒŒ์ผ์„ ์ฐพ์•„์„œ ์‹คํ–‰
docker-compose -f docker-compose-custom.yml up
  • -f ์˜ต์…˜ : docker-compose๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ docker-compose.yml์˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ,
    ๋งŒ์•ฝ ๋‹ค๋ฅธ ์ด๋ฆ„์œผ๋กœ ํŒŒ์ผ์„ ๊ด€๋ฆฌํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์˜ต์…˜์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
docker-compose up -d
  • -d ์˜ต์…˜ : ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ docker-compose๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
    • -d ์˜ต์…˜ ์—†์ด up ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ๋๋‚  ๋•Œ๊นŒ์ง€ ํ•ด๋‹น ํ„ฐ๋ฏธ๋„์€ ๋” ์ด์ƒ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜๋Š” ์˜ต์…˜

2.5. ์–ธ์ œ docker-compose๋ฅผ ์‚ฌ์šฉํ• ๊นŒ?

  • Redis ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ์˜ ์™ธ๋ถ€ ํ™˜๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ์ฆ‰, ์ธํ”„๋ผ ๊ตฌ์ถ• ์‹œ
    ๋กœ์ปฌ์— ์„ค์น˜ํ•˜๊ธฐ ์‹ซ์„ ๋•Œ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์ด์šฉํ•ด ์ปจํ…Œ์ด๋„ˆ๋กœ ์“ฐ๊ณ  ๋‚ด๋ฆฌ๋Š” ์‹์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

2.6. docker-compose.yml

version: "3"

services:
  db:
    container_name: dangn_db # ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์„ค์ •
    image: mysql:8.0 # MySQL 8.0 ๋ฒ„์ „ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
    environment: # MySQL์— ์ „๋‹ฌํ•˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜
      MYSQL_ROOT_PASSWORD: mysql # ๋ฃจํŠธ ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ์™€
      MYSQL_DATABASE: ceos_dangn # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ด๋ฆ„
    volumes: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ๋ณผ๋ฅจ ์„ค์ •
      - dbdata:/var/lib/mysql # MySQL ๋ฐ์ดํ„ฐ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ์˜ dbdata ๋ณผ๋ฅจ๊ณผ ์—ฐ๊ฒฐ
    ports: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์˜ ํฌํŠธ ๋งคํ•‘์„ ์„ค์ •
      - 3307:3306 # ํ˜ธ์ŠคํŠธ์˜ 3307 ํฌํŠธ๋ฅผ ์ปจํ…Œ์ด๋„ˆ ๋‚ด์˜ 3306 ํฌํŠธ๋กœ ๋งคํ•‘
    restart: always # ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ ํ•ญ์ƒ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋„๋ก ์„ค์ •

  web:
    container_name: dangn_web # ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„ ์„ค์ •
    build: . # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ Dockerfile์„ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€ ๋นŒ๋“œ
    ports: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์˜ ํฌํŠธ ๋งคํ•‘ ์„ค์ •
      - "8080:8080" # ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ 8080 ํฌํŠธ๋ฅผ ํ˜ธ์ŠคํŠธ์˜ 8080 ํฌํŠธ์™€ ์—ฐ๊ฒฐ
    depends_on: # ์˜์กดํ•˜๋Š” ์„œ๋น„์Šค ์„ค์ •
      - db # web ์„œ๋น„์Šค๊ฐ€ ์‹œ์ž‘๋˜๊ธฐ ์ „์— db ์„œ๋น„์Šค๊ฐ€ ๋จผ์ € ์‹œ์ž‘๋˜๋„๋ก ์„ค์ •
    environment: # ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•  ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •
      mysql_host: db # MySQL ํ˜ธ์ŠคํŠธ๋ฅผ db๋กœ ์„ค์ •
    restart: always # ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ข…๋ฃŒ๋  ๋•Œ ํ•ญ์ƒ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋„๋ก ์„ค์ •
    volumes: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ๋ณผ๋ฅจ ์„ค์ •
      - .:/app # ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ํ˜ธ์ŠคํŠธ์˜ /app ๋””๋ ‰ํ† ๋ฆฌ์™€ ์—ฐ๊ฒฐ

volumes:
  app: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ web ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ๋ณผ๋ฅจ
  dbdata: # ํ˜ธ์ŠคํŠธ ์‹œ์Šคํ…œ๊ณผ db ์ปจํ…Œ์ด๋„ˆ ๊ฐ„์— MySQL ๋ฐ์ดํ„ฐ๋ฅผ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ๋ณผ๋ฅจ

docker-compose ์‹คํ–‰

Containers Images Volumes
์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€ ๋ณผ๋ฅจ

3. AWS - ์™„์ „ ๊ฐ„๋‹จ ๋ฒ„์ „..

3.1. root ๊ณ„์ •์œผ๋กœ ์ด๋™ํ•˜๊ณ  git clone ํ•ด์ฃผ๊ธฐ

๋„์ปค ์ค‘๊ฐ„

3.2. java ์„ค์น˜

์ž๋ฐ”์—†์Œ

3.3. gralew ๋นŒ๋“œ

sh gradlew build

sh gradlew

3.4. build ๋””๋ ‰ํ† ๋ฆฌ ์—†์—ˆ๋Š”๋ฐ ์ƒ๊น€

build๋””๋ ‰ํ† ๋ฆฌ์—†์—ˆ๋Š”๋ฐ

์ƒ๊น€

3.5. jar ํŒŒ์ผ ์ƒ๊ฒผ๊ณ  ์Šคํ”„๋ง ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅ

jar ํŒŒ์ผ ์ƒ์„ฑ

์Šคํ”„๋ง ๋ฐ”๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Œ

์Šคํ”„๋ง ๋Œ์•„๊ฐ€๋Š” ๊ฑฐ ํ™•์ธ

3.6. ๋„์ปค ์„ค์น˜ํ•˜๊ณ  ํ™•์ธ

๋„์ปค ์„ค์น˜

๋„์ปค ์„ค์น˜ ํ™•์ธ

3.7. ๋„์ปค nginx

๋„์ปค nginx

๋ธŒ๋ผ์šฐ์ € nginx

3.8. ๋„์ปค ๋นŒ๋“œํ•˜๊ณ  ์ด๋ฏธ์ง€ ํ™•์ธ

๋„์ปค๋นŒ๋“œ

๋„์ปค ์ด๋ฏธ์ง€

3.9. ๋„์ปค์—์„œ ์Šคํ”„๋ง์ด๋ž‘ mysql

๋„์ปค์—์„œ ์Šคํ”„๋ง

๋„์ปค mysql

4. API ์ถ”๊ฐ€

4.1. ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

  • GET : /api/v1/users/profile - getUserInfo()

4.2. Spring Security ์ž์ž˜ํ•œ ์ˆ˜์ •

  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ ํ—ˆ์šฉ url ์ˆ˜์ •

4.3. ๋ฆฌ๋ทฐ ๋“ฑ๋กํ•˜๊ธฐ

  • POST : /api/v1/review/create - createReview()

[6์ฃผ์ฐจ] Github Action์„ ์ด์šฉํ•œ CI/CD

1. AWS - ํšŒ์›๊ฐ€์ž…๊ณผ MFA, Budget Alarm, Region(SEOUL) ์„ค์ •

MFA budget alert

2. EC2 : Elastic Compute Cloud

2.1. ๋ณด์•ˆ ๊ทธ๋ฃน ์ƒ์„ฑ

๋ณด์•ˆ๊ทธ๋ฃน1

  • VPC๋Š” ๊ธฐ๋ณธ default ์ด์šฉํ•จ

์ธ๋ฐ”์šด๋“œ๊ทœ์น™

  • SSH, HTTP, HTTPS, MYSQL ์— ๋Œ€ํ•ด IPv4์™€ IPv6 ๋ชจ๋‘ ์„ค์ •ํ•ด์คŒ

  • ์„ค์ • ๋ ๋ณด์•ˆ ๊ทธ๋ฃน ์ƒ์„ฑ ํด๋ฆญ

๋ณด์•ˆ ๊ทธ๋ฃน ์ƒ์„ฑ ๊ฒฐ๊ณผ

2.2. EC2 ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

ec2์ด๋ฆ„ ec2์„ค์ •1 ec2์„ค์ •2 ec2key

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒˆ ํ‚ค ํŽ˜์–ด ์ƒ์„ฑํ•ด์คŒ
  • ์ƒ์„ฑํ•ด์ค€ ํ‚ค ํŽ˜์–ด๋Š” C:\Users\yoonsseo\.ssh\ceos_dangn.pem ๊ฒฝ๋กœ์— ์ €์žฅํ•ด ์คŒ

ec2key ์ƒ์„ฑ ec2 ๋ณด์•ˆ๊ทธ๋ฃน ์—ฐ๊ฒฐ

  • ์•ž์—์„œ ๋งŒ๋“ค์–ด๋†จ๋˜ ๋ณด์•ˆ ๊ทธ๋ฃน ์—ฐ๊ฒฐ

๋ณผ๋ฅจ

  • ์Šคํ† ๋ฆฌ์ง€ ํฌ๊ธฐ๋Š” 30GB (ํ”„๋ฆฌํ‹ฐ์–ด ๊ฐ€๋Šฅ ์ตœ๋Œ€ ์šฉ๋Ÿ‰)๋กœ ์„ค์ •ํ•ด์คŒ

ec2 ์ƒ์„ฑ ์™„๋ฃŒ

  • EC2 ์ƒ์„ฑ ํ™•์ธ

2.3. ํƒ„๋ ฅ์  IP ์ฃผ์†Œ ํ• ๋‹น ๋ฐ ์—ฐ๊ฒฐ

ํƒ„๋ ฅ1 ํƒ„๋ ฅ2

3. RDS

3.0. ์˜ค๋ฅ˜

  • ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋– ์„œ ์„œ๋ธŒ๋„ท์ด๋ž‘ ์„œ๋ธŒ๋„ท ๊ทธ๋ฃน ์„ค์ •ํ•ด์คŒ ์˜ค๋ฅ˜1

3.1. VPC ์„œ๋ธŒ๋„ท

subnet0 subnet2

3.2. ์„œ๋ธŒ๋„ท ๊ทธ๋ฃน

์„œ๋ธŒ๋„ท๊ทธ๋ฃธ1 ์„œ๋ธŒ๋„ท๊ทธ๋ฃน2

3.3. RDS

RDS0 RDS1 RDS2 rds3

  • ๋งˆ์Šคํ„ฐ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ์•”ํ˜ธ๋Š” ๋‚˜์ค‘์— DB ์—ฐ๊ฒฐ ์‹œ ์‚ฌ์šฉ

RDS4

  • ์œ„ ํ…œํ”Œ๋ฆฟ์—์„œ ํ”„๋ฆฌํ‹ฐ์–ด ์„ ํƒํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•œ ์˜ต์…˜ ์•„๋ฌด๊ฑฐ๋‚˜ ์„ ํƒ

RDS5

  • ์Šคํ† ๋ฆฌ์ง€ ์šฉ๋Ÿ‰์€ 20GB, ์Šคํ† ๋ฆฌ์ง€ ์ž๋™ ์กฐ์ •์„ ๋น„ํ™œ์„ฑํ™” (์˜๋„์น˜ ์•Š์€ ๊ณผ๊ธˆ ๋ฐฉ์ง€)

RDS6 RDS7 RDS8

  • ๋”ฐ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์Œ

4. Github Action

4.1. Core ๊ฐœ๋…

  1. Workflow

    • ์ž๋™ํ™”๋œ ์ „์ฒด ํ”„๋กœ์„ธ์Šค๋กœ, ํ•˜๋‚˜ ์ด์ƒ์˜ Job์œผ๋กœ ๊ตฌ์„ฑ๋˜๊ณ , Event์— ์˜ํ•ด ์˜ˆ์•ฝ๋˜๊ฑฐ๋‚˜ ํŠธ๋ฆฌ๊ฑฐ๋  ์ˆ˜ ์žˆ๋Š” ์ž๋™ํ™”๋œ ์ ˆ์ฐจ๋ฅผ ๋งํ•œ๋‹ค
    • Workflow ํŒŒ์ผ์€ YAML์œผ๋กœ ์ž‘์„ฑ๋˜๊ณ , Github Repository์˜ .github/workflows ํด๋” ์•„๋ž˜์— ์ €์žฅ๋œ๋‹ค
    • Github์—๊ฒŒ YAML ํŒŒ์ผ๋กœ ์ •์˜ํ•œ ์ž๋™ํ™” ๋™์ž‘์„ ์ „๋‹ฌํ•˜๋ฉด, Github Actions๋Š” ํ•ด๋‹น ํŒŒ์ผ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ทธ๋Œ€๋กœ ์‹คํ–‰์‹œํ‚จ๋‹ค
  2. Event

    • Workflow๋ฅผ ํŠธ๋ฆฌ๊ฑฐ(์‹คํ–‰)ํ•˜๋Š” ํŠน์ • ํ™œ๋™์ด๋‚˜ ๊ทœ์น™
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ์ปค๋ฐ‹์„ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ํ‘ธ์‹œํ•˜๊ฑฐ๋‚˜ ํ’€ ์š”์ฒญ์ด ์ƒ์„ฑ ๋  ๋•Œ GitHub์—์„œ ํ™œ๋™์ด ์‹œ์ž‘๋  ์ˆ˜ ์žˆ๋‹ค
  3. Job

    • Job์€ ์—ฌ๋Ÿฌ Step์œผ๋กœ ๊ตฌ์„ฑ๋˜๊ณ , ๋‹จ์ผ ๊ฐ€์ƒ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋œ๋‹ค
    • ๋‹ค๋ฅธ Job์— ์˜์กด ๊ด€๊ณ„๋ฅผ ๊ฐ€์งˆ ์ˆ˜๋„ ์žˆ๊ณ , ๋…๋ฆฝ์ ์œผ๋กœ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰๋  ์ˆ˜๋„ ์žˆ๋‹ค
  4. Step

    • Job ์•ˆ์—์„œ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค ๋‹จ์œ„
    • Step์—์„œ ๋ช…๋ น์„ ๋‚ด๋ฆฌ๊ฑฐ๋‚˜, Action์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
  5. Action

    • Job์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ Step๋“ค์˜ ์กฐํ•ฉ์œผ๋กœ ๊ตฌ์„ฑ๋œ ๋…๋ฆฝ์ ์ธ ๋ช…๋ น
    • Workflow์˜ ๊ฐ€์žฅ ์ž‘์€ ๋นŒ๋“œ ๋‹จ์œ„
    • Workflow์—์„œ Action์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Action์ด Step์„ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค
    • Action์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์ปค์Šคํ…€ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ๋‹ค
    • ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๊ฑฐ๋‚˜, ๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค์— ์žˆ๋Š” Action์„ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค
  6. Runner

    • Gitbub Action Runner ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„ค์น˜๋œ ๋จธ์‹ ์œผ๋กœ, Workflow๊ฐ€ ์‹คํ–‰๋  ์ธ์Šคํ„ด์Šค

4.2. .github/workflows/gradle.yml

4.2.1. name
  • ๊นƒํ—™ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์˜ ์•ก์…˜ ํƒญ์— ๋…ธ์ถœ๋˜๋Š” Workflow์˜ ์ด๋ฆ„์œผ๋กœ ์˜ต์…”๋„ํ•œ ๊ฐ’
name: Deploy Development Server
4.2.2. on
  • ์–ด๋–ค ์กฐ๊ฑด์— Workflow๋ฅผ ์ž๋™์œผ๋กœ Trigger ์‹œํ‚ฌ์ง€ Event ๋ช…์‹œ
  • push(Branch or Tag), pull_request, schedule์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
    • push ์ด๋ฒคํŠธ๋ฅผ ๋ช…์‹œํ•˜๋ฉด, ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๊นƒ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ push ํ•˜๋Š” ์‹œ์ ๋งˆ๋‹ค job์ด ์‹คํ–‰๋œ๋‹ค
  • ๋‹จ์ผ Event๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ , array๋กœ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค
on: push
# ๋˜๋Š”
on: [pull_request, issues]
## develop ๋ธŒ๋žœ์น˜์— push๊ฐ€ ๋˜๋ฉด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค
on:
  push:
    branches: [ "develop" ]
  • ํŠน์ •ํ•œ ๋ธŒ๋žœ์น˜๋‚˜, tag, ๋˜๋Š” path์—์„œ๋งŒ ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜๋„ ์žˆ๊ณ ,
    ์•„๋ž˜ ์˜ˆ์‹œ์™€ ๊ฐ™์ด paths๋กœ ํŠน์ • ํŒจํ„ด์„ ์„ค์ •ํ•˜์—ฌ ํ•ด๋‹น ํŒจํ„ด์— ์ผ์น˜ํ•˜๋Š” ํŒŒ์ผ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ Workflow๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ํ•˜๊ณ ,
    !paths๋‚˜ paths-ignore๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฌด์‹œํ•  ํŒจํ„ด์„ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค
on:
    push:
      branches: [ master, dev ]
    pull_request:
      branches: [ master ]
      paths:
        - "**.js"
      paths-ignore:
        - "doc/**"
4.2.3. permissions
  • ์›Œํฌ ํ”Œ๋กœ์šฐ๊ฐ€ ๊นƒ ๋ ˆํฌ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์„ค์ •ํ•œ๋‹ค
permissions:
  contents: read
4.2.4. jobs
jobs:
  build:
    runs-on: ubuntu-latest
    steps:

      - name: checkout
        uses: actions/checkout@v3

      ## ์—ฌ๋Ÿฌ๋ถ„์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์„ธ์š”
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      ## gradle build
      - name: Build with Gradle
        run: ./gradlew bootJar
  • build ๋ผ๋Š” job์„ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ ์•„๋ž˜์— 3๊ฐœ์˜ step์ด ์กด์žฌํ•˜๋Š” ๊ตฌ์กฐ
  • runs-on: ์–ด๋А ์šด์˜์ฒด์ œ์—์„œ job์„ ์‹คํ–‰ํ•  ์ง€ ์ง€์ •
  • uses : ์–ด๋–ค ์•ก์…˜์„ ์‚ฌ์šฉํ•  ์ง€ ์ง€์ •
    • ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ action(์ œ 3์ž๊ฐ€ ๋งŒ๋“  action)์„ ์‚ฌ์šฉํ•  ๋•Œ ์ง€์ •
    • actions/checkout@v3 : ์šฐ๋ฆฌ์˜ branch๋ฅผ ํ˜„์žฌ ๋น„์–ด์žˆ๋Š” ubuntu์— ๋‚ด๋ ค๋ฐ›๋„๋ก ํ•จ
    • actions/setup-java@v3 : java ๋‹ค์šด๋ฐ›๊ธฐ
  • run : bash์—์„œ ์‹คํ–‰ํ•  ๋ช…๋ น์–ด๋ฅผ ์ •์˜
    • chmod +x gradlew : gradlew ์‹คํ–‰ํ•  ๊ถŒํ•œ ๋ถ€์—ฌ
    • ./gradlew build : ํ•ด๋‹น java ์ฝ”๋“œ ๋นŒ๋“œ
## ์›น ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋ฐ ๋„์ปคํ—ˆ๋ธŒ์— push
      - name: web docker build and push
        run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t my-repo/my-web-image .
          docker push my-repo/my-web-image
          docker build -f dockerfile-nginx -t my-repo/my-nginx-image .
          docker push my-repo/my-nginx-image

      - name: executing remote ssh commands using password
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ubuntu
          key: ${{ secrets.KEY }}
          script: |
          
          ## ์—ฌ๋Ÿฌ๋ถ„์ด ์›ํ•˜๋Š” ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
            cd /home/ubuntu/
            
          ## .env ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
            sudo touch .env
            echo "${{ secrets.ENV_VARS }}" | sudo tee .env > /dev/null
          
          ## docker-compose.yaml ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
            sudo touch docker-compose.yaml
            echo "${{ vars.DOCKER_COMPOSE }}" | sudo tee docker-compose.yaml > /dev/null
            
          ## docker-compose๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
            sudo chmod 666 /var/run/docker.sock
            sudo docker rm -f $(docker ps -qa)
            sudo docker pull my-repo/my-web-image
            sudo docker pull my-repo/my-nginx-image
            docker-compose -f docker-compose.yaml --env-file ./.env up -d
            docker image prune -f
  • ๋„์ปค ๊ด€๋ จ ์Šคํฌ๋ฆฝํŠธ

4.3. secrets์™€ variables ๋“ฑ๋ก

  1. DOCKER_USERNAME : ๋„์ปค ๊ณ„์ • ์œ ์ €๋„ค์ž„

  2. DOCKER_PASSWORD : ๋„์ปค ๊ณ„์ • ๋น„๋ฐ€๋ฒˆํ˜ธ

  3. HOST : EC2์˜ ํผ๋ธ”๋ฆญ IPv4 DNS EC2 ์ฃผ์†Œ

  4. KEY : EC2๋ฅผ ์ƒ์„ฑํ•˜๋ฉฐ ๊ฐ™์ด ์ƒ์„ฑํ–ˆ๋˜ .pem ํŒŒ์ผ์˜ ๋‚ด์šฉ

  • ์ด ๋•Œ, -----BEGIN๋ถ€ํ„ฐ END ... KEY-----๊นŒ์ง€ ์ž…๋ ฅํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค
    -----BEGIN RSA PRIVATE KEY-----
    MIIEowIBAAKCAQEAidvIJTS/UYMxf3G5fWC3tPkHiD35xttdsez++y2EO5vWKtpE
    wHcNCeHzwKiadand2VLDNnKi8/r+e3oPRrDCKQI8he5siDs6qyZuHOm2qd+jiQ+S
    ZeD
    ...
    7Kzfn3eqHh+sMt4t9iX8
    gdO2R6Z0TI3dfFpNKJU2WehZ7TZEA3qDJNqTg7008IJaUcuAEeWULtDwiwx/hkZ7
    9kt5/TEA8jEoJw4gPakNlfEPEsQ2Sv7zpPPquZEGTqIjWXVMvPE0
    -----END RSA PRIVATE KEY-----
  1. ENV_VARS : ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ key-value๋กœ ๋‹ด์•„๋‘”๋‹ค
  • = ์„ ๊ธฐ์ค€์œผ๋กœ ์ขŒ์ธก์ด key, ์šฐ์ธก์ด value
DB_URL=jdbc:mysql://ceos-dangn-rds.cp0xntend9ra.ap-northeast-2.rds.amazonaws.com:3306/ceos-dangn-rds
DB_USERNAME=root
DB_PASSWORD=blahblah
  • ์ €์žฅํ•ด๋‘” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์‚ฌ์šฉํ•˜๊ธฐ : application.yaml
spring:
  datasource:
	  driver-class-name: com.mysql.cj.jdbc.Driver
	  url: ${DB_URL}
	  username: ${DB_USERNAME}
	  password: ${DB_PASSWORD}
	  hikari:
	      maximum-pool-size: 10
  1. DOCKER_COMPOSE : docker-compose.yaml ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ฐธ๊ณ ํ•˜๋Š” ๋ณ€์ˆ˜
    • ์œ„์˜ secrets๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋ก
    • docker-compose ํŒŒ์ผ ์ž‘์„ฑ ํ›„ ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋ก

4.4. dockerfile๊ณผ docker-compose, nginx.conf

4.4.1. Dockerfile
FROM openjdk:17
ARG JAR_FILE=/build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar", "/app.jar"]
4.4.2. dockerfile-nginx
FROM nginx 
# ๊ธฐ๋ณธ Nginx ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
RUN rm -rf /etc/nginx/conf.d/default.conf \
# ๊ธฐ๋ณธ Nginx ์„ค์ • ํŒŒ์ผ์„ ์‚ญ์ œ
COPY ./nginx/conf.d/nginx.conf /etc/nginx/conf.d
# ํ˜ธ์ŠคํŠธ ๋จธ์‹ ์˜ ./nginx/conf.d/nginx.conf ํŒŒ์ผ์„ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์˜ /etc/nginx/conf.d ๊ฒฝ๋กœ์— ๋ณต์‚ฌ
CMD ["nginx", "-g", "daemon off;"]
# ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์‹คํ–‰๋  ๋ช…๋ น ์ •์˜
  • Nginx๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” Docker ์ด๋ฏธ์ง€ ์ •์˜ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ
  • deamon off : Nginx๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋„๋ก ์„ค๊ณ„๋˜์–ด์žˆ๋Š”๋ฐ,
    Nginx๋ฅผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋™์ž‘ํ•˜์ง€ ์•Š๊ณ  ํ”„๋กœ์„ธ์Šค๋ฅผ foreground์—์„œ ์‹คํ–‰ํ•˜๋„๋ก ์ง€์ •
4.4.3. docker-compose.yml
version: '3'
services:

  web:
    container_name: dangn_web
    image: my-repo/my-web-image
    env_file:
      - .env
    expose:
      - 8080
    ports:
      - 8080:8080
    tty: true
    environment:
      - TZ=Asia/Seoul

  nginx:
    container_name: dangn_nginx
    image: my-repo/my-nignx-image
    ports:
      - 80:80
    depends_on:
      - web
4.4.4. etc/nginx/conf.d/nginx.conf
server {
    listen 80;
    # ์ด ์„œ๋ฒ„ ๋ธ”๋ก์€ 80๋ฒˆ ํฌํŠธ์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ฒ˜๋ฆฌ
    server_name *.compute.amazonaws.com;
    # ์ด ์„œ๋ฒ„ ๋ธ”๋ก์€ *.compute.amazonaws.com ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌ
    access_log /var/log/nginx/access.log;
    # ๊ฐ๊ฐ ์ ‘๊ทผ ๋กœ๊ทธ์™€ ์˜ค๋ฅ˜ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•  ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •
    error_log /var/log/nginx/error.log;
    # ์ด ๋ธ”๋ก์€ ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌ
    # 
    location / {
        proxy_pass http://web:8080;
        # proxy_pass ์ง€์‹œ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด ์„œ๋ฒ„๊ฐ€ ๋ฐ›์€ ์š”์ฒญ์„ http://web:8080 ์ฃผ์†Œ๋กœ ์ „๋‹ฌ
        # ์—ฌ๊ธฐ์„œ web์€ Docker ๋„คํŠธ์›Œํฌ ์ƒ์—์„œ ํ•ด๋‹น ์„œ๋น„์Šค์— ํ• ๋‹น๋œ ์ด๋ฆ„
        # ์„œ๋น„์Šค๊ฐ€ 8080 ํฌํŠธ์—์„œ ์‹คํ–‰ ์ค‘์ด๋ผ๊ณ  ๊ฐ€์ •
        proxy_set_header Host $host:$server_port;
        # proxy_set_header : ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ๋  ๋•Œ ์ถ”๊ฐ€์ ์ธ HTTP ํ—ค๋” ์„ค์ •
        # ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌ๋˜๋Š” ์š”์ฒญ์˜ Host ํ—ค๋” ์„ค์ • 
        # ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์„ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌํ•  ๋•Œ ์›๋ž˜ ํ˜ธ์ŠคํŠธ ์ •๋ณด๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค
        proxy_set_header X-Forwarded-Host $server_name;
        # ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์›๋ž˜ ํ˜ธ์ŠคํŠธ ์ฃผ์†Œ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค
        proxy_set_header X-Real-IP $remote_addr;
        # ํด๋ผ์ด์–ธํŠธ์˜ ์‹ค์ œ IP ์ฃผ์†Œ๋ฅผ ํฌํ•จํ•˜๋ฉฐ, ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ์ด ์ •๋ณด๋ฅผ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # ํด๋ผ์ด์–ธํŠธ์—์„œ ํ”„๋ก์‹œ๊นŒ์ง€์˜ ์ด์ „ ์š”์ฒญ์˜ IP ์ฃผ์†Œ๋ฅผ ํฌํ•จ
        # ์ด๋ฅผ ํ†ตํ•ด ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์›๋ž˜ IP ์ฃผ์†Œ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค
    }
}
  • reverse proxy ์—ญํ• ์„ ํ•˜๋Š” ๊ตฌ์„ฑ

5. ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

5.1. ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰..

image

  1. ๊นƒํ—ˆ๋ธŒ ์•ก์…˜์—์„œ๋Š” ๋นŒ๋“œ ์„ฑ๊ณต์œผ๋กœ ์ดˆ๋ก๋ถˆ์ด ๋œจ๋Š”๋ฐ docker ps ํ•˜๋ฉด ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋œฌ๋‹ค

image
2. docker images๋กœ ๋„์ปค ์ด๋ฏธ์ง€ ํ™•์ธ

docker run -d -p 8080:8080 --name my_ceos_container yoonsseo/ceos18dangn
  1. -d ์˜ต์…˜์ด๋ž‘ -p ์˜ต์…˜์„ ์ด์šฉํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ ์‹คํ–‰ ํ•˜๊ณ  8080์œผ๋กœ ๋งคํ•‘

image 4. ์ด์ œ docker ps ํ•˜๋ฉด ์ปจํ…Œ์ด๋„ˆ ๋ชฉ๋ก ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค

image image 5. ํฌ์ŠคํŠธ๋งจ์ด๋ž‘ MySql์—์„œ ํ™•์ธ

5.2. ์ž๋™์œผ๋กœ ์‹คํ–‰

  1. gradle.yml ์›Œํฌํ”Œ๋กœ์šฐ์— ์œ„์—์„œ ์ˆ˜๋™์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์—ˆ๋˜ ๋‹ค์Œ ๋ช…๋ น์–ด ์ถ”๊ฐ€
docker run -d -p 8080:8080 --name ceos_container yoonsseo/ceos18dangn
- name: Deploy to EC2
  uses: appleboy/ssh-action@master
  with:
    host: ${{ secrets.EC2_PUBLIC_DNS }}
    username: ubuntu
    key: ${{ secrets.PEM_KEY }}
    script: |
      cd /home/ubuntu/

      sudo touch docker-compose.yml
      echo "${{ vars.DOCKER_COMPOSE }}" | sudo tee docker-compose.yml > /dev/null

      sudo chmod 666 /var/run/docker.sock
      sudo docker rm -f $(sudo docker ps -qa)
      sudo docker pull ${{ secrets.DOCKER_USERNAME }}/ceos18dangn
      docker-compose -f docker-compose.yml up -d
      docker run -d -p 8080:8080 --name ceos_container yoonsseo/ceos18dangn
      docker image prune -f
  1. ๊ฒฐ๊ณผ

image image image

  1. ๊ทผ๋ฐ ์™œ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฉด ์•ˆ ๋˜๋Š” ๊ฑด์ง€๋Š” ์•Œ ์ˆ˜ ์—†์—ˆ๋‹ค..๐Ÿฅน๐Ÿคฏ๐Ÿ˜ฑ๐Ÿซ ๐Ÿฅฒ๐Ÿ˜ข๐Ÿฅบ๐Ÿซฃ

About

CEOS 18th Backend Study - Carrot Market

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Java 99.7%
  • Dockerfile 0.3%