JWT Token - ynjch97/YNJCH_WIKI GitHub Wiki

1. JWT Token

  • ๊ณต์‹ ์‚ฌ์ดํŠธ https://jwt.io/
  • ์ฐธ๊ณ  https://mangkyu.tistory.com/56
  • JWT ๊ตฌํ˜„ ๋ฐ”๋กœ๊ฐ€๊ธฐ โ–ผ
  • JWT(Json Web Token) : Json ํฌ๋งท์„ ์ด์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์†์„ฑ์„ ์ €์žฅํ•˜๋Š” Claim ๊ธฐ๋ฐ˜์˜ Web Token
  • ํ† ํฐ ์ž์ฒด๋ฅผ ์ •๋ณด๋กœ ์‚ฌ์šฉํ•˜๋Š” Self-Contained ๋ฐฉ์‹์œผ๋กœ ์ •๋ณด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ „๋‹ฌ
  • ์ฃผ๋กœ ํšŒ์› ์ธ์ฆ์ด๋‚˜ ์ •๋ณด ์ „๋‹ฌ์— ์‚ฌ์šฉ๋จ
  • JWT๋Š” ์•„๋ž˜์˜ ๋กœ์ง์„ ๋”ฐ๋ผ์„œ ์ฒ˜๋ฆฌ๋จ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋  ๋•Œ, JWT๋ฅผ static ๋ณ€์ˆ˜์™€ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•˜๊ฒŒ ๋จ
    • static ๋ณ€์ˆ˜์— ์ €์žฅ๋˜๋Š” ์ด์œ ๋Š” HTTP ํ†ต์‹ ์„ ํ•  ๋•Œ๋งˆ๋‹ค JWT๋ฅผ HTTP ํ—ค๋”์— ๋‹ด์•„์„œ ๋ณด๋‚ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋ฅผ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ ๊ณ„์† ๋ถˆ๋Ÿฌ์˜ค๋ฉด ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ
    • ํด๋ผ์ด์–ธํŠธ์—์„œ JWT๋ฅผ ํฌํ•จํ•ด ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์„œ๋ฒ„๋Š” ํ—ˆ๊ฐ€๋œ JWT์ธ์ง€๋ฅผ ๊ฒ€์‚ฌ
    • ๋˜ํ•œ ๋กœ๊ทธ์•„์›ƒ์„ ํ•  ๊ฒฝ์šฐ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅ๋œ JWT ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ฑฐ
    • (์‹ค์ œ ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ์—๋Š” ๋กœ๊ทธ์•„์›ƒ ์‹œ, ์‚ฌ์šฉํ–ˆ๋˜ ํ† ํฐ์„ blacklist๋ผ๋Š” DB ํ…Œ์ด๋ธ”์— ๋„ฃ์–ด ํ•ด๋‹น ํ† ํฐ์˜ ์ ‘๊ทผ์„ ๋ง‰๋Š” ์ž‘์—…์„ ํ•ด์ฃผ์–ด์•ผ ํ•จ) JWT Token ์ฒ˜๋ฆฌ ๋กœ์ง

1-1. ์„œ๋ฒ„(์„ธ์…˜) ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ

  • ๊ธฐ์กด์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์€ ์„œ๋ฒ„ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ๋ฐฉ์‹์œผ๋กœ, ์„œ๋ฒ„ ์ธก์—์„œ ์‚ฌ์šฉ์ž๋“ค์˜ ์ •๋ณด๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•จ
  • ์‚ฌ์šฉ์ž๋“ค์˜ ์ •๋ณด๋ฅผ ๊ธฐ์–ตํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„ธ์…˜์„ ์œ ์ง€ํ•ด์•ผ ํ•จ > ๋ฉ”๋ชจ๋ฆฌ๋‚˜ ๋””์Šคํฌ ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋“ฑ์„ ํ†ตํ•ด ๊ด€๋ฆฌ
  • ์„œ๋ฒ„ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์€ ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด, ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ๊ณ„์†ํ•ด์„œ ์œ ์ง€ํ•˜๊ณ  ์ด ์ •๋ณด๋ฅผ ์„œ๋น„์Šค์— ์ด์šฉ > ์ด๋Ÿฌํ•œ ์„œ๋ฒ„๋ฅผ Sateful ์„œ๋ฒ„๋ผ๊ณ  ํ•จ
  • ex. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด, ์„ธ์…˜์— ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ €์žฅํ•ด๋‘๊ณ  ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•  ๋•Œ ์‚ฌ์šฉ ์„œ๋ฒ„ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์˜ ํ๋ฆ„

1-1-1. ์„œ๋ฒ„ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ๋ฌธ์ œ์ 

  • ์„ธ์…˜
    • ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์„ ํ•  ๋•Œ, ์„œ๋ฒ„๋Š” ์ด๋Ÿฌํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๊ณ  ์ด๋ฅผ ์„ธ์…˜(Session)์ด๋ผ๊ณ  ํ•จ
    • ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜๋Š”๋ฐ, ๋กœ๊ทธ์ธ ์ค‘์ธ ์‚ฌ์šฉ์ž๊ฐ€ ๋Š˜์–ด๋‚  ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„์˜ RAM์— ๋ถ€ํ•˜๊ฐ€ ๊ฑธ๋ฆฌ๊ฒŒ ๋จ
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ์„ ํ•˜๊ธฐ๋„ ํ•˜๋‚˜, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฌด๋ฆฌ๋ฅผ ์ค„ ์ˆ˜ ์žˆ์Œ
  • ํ™•์žฅ์„ฑ
    • ์‚ฌ์šฉ์ž๊ฐ€ ๋Š˜์–ด๋‚˜๊ฒŒ ๋˜๋ฉด ๋” ๋งŽ์€ ํŠธ๋ž˜ํ”ฝ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋Œ๋ฆฌ๊ฑฐ๋‚˜ ์ปดํ“จํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋“ฑ ์„œ๋ฒ„๋ฅผ ํ™•์žฅํ•ด์•ผ ํ•จ
    • ์„ธ์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์„ธ์…˜์„ ๋ถ„์‚ฐ์‹œํ‚ค๋Š” ์‹œ์Šคํ…œ์„ ์„ค๊ณ„ํ•ด์•ผ ํ•จ
  • CORS(Cross-Origin Resource Sharing)
    • ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•  ๋•Œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ฟ ํ‚ค๋Š” ๋‹จ์ผ ๋„๋ฉ”์ธ ๋ฐ ์„œ๋ธŒ ๋„๋ฉ”์ธ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Œ > ์ฟ ํ‚ค๋ฅผ ์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์›€

1-2. ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ

  • ์ธ์ฆ๋ฐ›์€ ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ , ์„œ๋ฒ„์— ์š”์ฒญ์„ ํ•  ๋•Œ ํ—ค๋”์— ํ† ํฐ์„ ํ•จ๊ป˜ ๋ณด๋‚ด๋„๋ก ํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ง„ํ–‰
  • ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ์„œ๋ฒ„๋‚˜ ์„ธ์…˜์— ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ๋งŒ์œผ๋กœ ์ž‘์—…์„ ์ฒ˜๋ฆฌ
  • ์„œ๋ฒ„ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ๊ณผ ๋‹ฌ๋ฆฌ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ Statelessํ•œ ๊ตฌ์กฐ
  • ์ด๋Ÿฌํ•œ ์ธ์ฆ ๋ฐฉ์‹์„ ํ†ตํ•ด ์ˆ˜๋งŽ์€ ๋ฌธ์ œ์  ํ•ด๊ฒฐ ๊ฐ€๋Šฅ (ex. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์ด ๋˜์–ด์žˆ๋Š”์ง€ ์•ˆ๋˜์–ด์žˆ๋Š”์ง€ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  ์†์‰ฝ๊ฒŒ ์‹œ์Šคํ…œ์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Œ)

1-2-1. ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ์ž‘๋™ ๊ณผ์ •

  • ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ
  • ์„œ๋ฒ„ ์ธก์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ฒ€์ฆ
  • ์ •๋ณด๊ฐ€ ์ •ํ™•ํ•˜๋‹ค๋ฉด ์„œ๋ฒ„ ์ธก์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ Signed ํ† ํฐ ๋ฐœ๊ธ‰ (Signed๋Š” ํ•ด๋‹น ํ† ํฐ์ด ์„œ๋ฒ„์—์„œ ์ •์ƒ์ ์œผ๋กœ ๋ฐœ๊ธ‰๋œ ํ† ํฐ์ž„์„ ์ฆ๋ช…ํ•˜๋Š” Signature๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ)
  • ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ „๋‹ฌ๋ฐ›์€ ํ† ํฐ์„ ์ €์žฅํ•ด๋‘๊ณ , ์„œ๋ฒ„์— ์š”์ฒญ์„ ํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ํ† ํฐ์„ ์„œ๋ฒ„์— ํ•จ๊ป˜ ์ „๋‹ฌ (์ด๋•Œ Http ์š”์ฒญ ํ—ค๋”์— ํ† ํฐ์„ ํฌํ•จ์‹œํ‚ด)
  • ์„œ๋ฒ„๋Š” ํ† ํฐ์„ ๊ฒ€์ฆ, ์š”์ฒญ์— ์‘๋‹ต
    ํ† ํฐ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์˜ ํ๋ฆ„

1-2-2. ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ์žฅ์ 

  • ๋ฌด์ƒํƒœ์„ฑ(Stateless) & ํ™•์žฅ์„ฑ(Scalability)
    • ํ† ํฐ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๋Š” ์™„์ „ํžˆ Stateless
    • ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„์˜ ์—ฐ๊ฒฐ๊ณ ๋ฆฌ๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ™•์žฅํ•˜๊ธฐ์— ๋งค์šฐ ์ ํ•ฉ
    • ๋งŒ์•ฝ ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ์„œ๋ฒ„ ์ธก ์„ธ์…˜์— ์ €์žฅ๋œ ๊ฒฝ์šฐ์— ์„œ๋ฒ„๋ฅผ ํ™•์žฅํ•˜์—ฌ ๋ถ„์‚ฐ์ฒ˜๋ฆฌ ํ•œ๋‹ค๋ฉด, ํ•ด๋‹น ์‚ฌ์šฉ์ž๋Š” ์ฒ˜์Œ ๋กœ๊ทธ์ธ ํ–ˆ์—ˆ๋˜ ์„œ๋ฒ„์—๋งŒ ์š”์ฒญ์„ ๋ฐ›๋„๋ก ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํž˜
    • ํ† ํฐ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์–ด๋– ํ•œ ์„œ๋ฒ„๋กœ ์š”์ฒญ์ด ์™€๋„ ์ƒ๊ด€์ด ์—†์Œ
  • ๋ณด์•ˆ์„ฑ
    • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์ฟ ํ‚ค๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ฒŒ ๋˜๋ฏ€๋กœ, ์ฟ ํ‚ค ์‚ฌ์šฉ์— ์˜ํ•œ ์ทจ์•ฝ์ ์ด ์‚ฌ๋ผ์ง
    • ํ† ํฐ ํ™˜๊ฒฝ์˜ ์ทจ์•ฝ์ ์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ด์— ๋Œ€๋น„ํ•ด์•ผ ํ•จ
  • ํ™•์žฅ์„ฑ(Extensibility)
    • ์‹œ์Šคํ…œ์˜ ํ™•์žฅ์„ฑ์„ ์˜๋ฏธํ•˜๋Š” Scalability์™€ ๋‹ฌ๋ฆฌ Extensibility๋Š” ๋กœ๊ทธ์ธ ์ •๋ณด๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๋ถ„์•ผ์˜ ํ™•์ •์„ ์˜๋ฏธ
    • ํ† ํฐ ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ์‹œ์Šคํ…œ์—์„œ๋Š” ํ† ํฐ์— ์„ ํƒ์ ์ธ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•˜์—ฌ ๋ฐœ๊ธ‰ํ•  ์ˆ˜ ์žˆ์Œ
    • OAuth์˜ ๊ฒฝ์šฐ Facebook, Google ๋“ฑ๊ณผ ๊ฐ™์€ ์†Œ์…œ ๊ณ„์ •์„ ์ด์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์›น์„œ๋น„์Šค์—์„œ๋„ ๋กœ๊ทธ์ธ์„ ํ•  ์ˆ˜ ์žˆ์Œ
  • ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ ๋ฐ ๋„๋ฉ”์ธ
    • ์„œ๋ฒ„ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์‹œ์Šคํ…œ์˜ ๋ฌธ์ œ์  ์ค‘ ํ•˜๋‚˜์ธ CORS๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Œ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ์„œ๋น„์Šค์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ์—ฌ๋Ÿฌ ๋””๋ฐ”์ด์Šค๋ฅผ ํ˜ธํ™˜์‹œํ‚ค๊ณ  ๋” ๋งŽ์€ ์ข…๋ฅ˜์˜ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ฒŒ ๋จ
    • ํ† ํฐ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์–ด๋–ค ๋””๋ฐ”์ด์Šค, ์–ด๋–ค ๋„๋ฉ”์ธ์—์„œ๋„ ํ† ํฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•œ ํ›„์— ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
    • ์ด๋Ÿฐ ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด assests ํŒŒ์ผ(Image, html, css, js ๋“ฑ)์€ ๋ชจ๋‘ CDN์—์„œ ์ œ๊ณตํ•˜๊ณ , ์„œ๋ฒ„ ์ธก์—์„œ๋Š” API๋งŒ ๋‹ค๋ฃจ๋„๋ก ์„ค๊ณ„ ๊ฐ€๋Šฅ

2. JWT ๊ตฌ์กฐ

  • Header, Payload, Signature์˜ ์„ธ ๋ถ€๋ถ„
  • Json ํ˜•ํƒœ์ธ ๊ฐ ๋ถ€๋ถ„์€ Base64๋กœ ์ธ์ฝ”๋”ฉ ๋˜์–ด ํ‘œํ˜„๋จ
  • ๊ฐ๊ฐ์˜ ๋ถ€๋ถ„์„ ์ด์–ด ์ฃผ๊ธฐ ์œ„ํ•ด . ๊ตฌ๋ถ„์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ๋ถ„
  • ์ถ”๊ฐ€๋กœ Base64๋Š” ์•”ํ˜ธํ™”๋œ ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๊ณ , ๊ฐ™์€ ๋ฌธ์ž์—ด์— ๋Œ€ํ•ด ํ•ญ์ƒ ๊ฐ™์€ ์ธ์ฝ”๋”ฉ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ Base64

2-1. Header(ํ—ค๋”)

  • ํ† ํฐ์˜ ํ—ค๋”๋Š” typ๊ณผ alg ๋‘ ๊ฐ€์ง€ ์ •๋ณด๋กœ ๊ตฌ์„ฑ
  • alg๋Š” ํ—ค๋”(Header)๋ฅผ ์•”ํ˜ธํ™” ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ณ , Signature๋ฅผ ํ•ด์‹ฑํ•˜๊ธฐ ์œ„ํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ
    • typ : ํ† ํฐ์˜ ํƒ€์ž…์„ ์ง€์ • ex) JWT
    • alg : ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ฐฉ์‹์„ ์ง€์ •ํ•˜๋ฉฐ, ์„œ๋ช…(Signature) ๋ฐ ํ† ํฐ ๊ฒ€์ฆ์— ์‚ฌ์šฉ ex) HS256(SHA256) ๋˜๋Š” RSA
{ "alg": "HS256", "typ": JWT }

2-2. PayLoad(ํŽ˜์ด๋กœ๋“œ)

  • ํ† ํฐ์˜ ํŽ˜์ด๋กœ๋“œ์—๋Š” ํ† ํฐ์—์„œ ์‚ฌ์šฉํ•  ์ •๋ณด์˜ ์กฐ๊ฐ๋“ค์ธ ํด๋ ˆ์ž„(Claim)์ด ๋‹ด๊ฒจ ์žˆ์Œ
  • ํด๋ ˆ์ž„์€ ์ด 3๊ฐ€์ง€๋กœ ๋‚˜๋ˆ„์–ด์ง€๋ฉฐ, Json(Key/Value) ํ˜•ํƒœ๋กœ ๋‹ค์ˆ˜์˜ ์ •๋ณด๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ์Œ

2-2-1. ๋“ฑ๋ก๋œ ํด๋ ˆ์ž„(Registered Claim)

  • ๋“ฑ๋ก๋œ ํด๋ ˆ์ž„์€ ํ† ํฐ ์ •๋ณด๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฏธ ์ •ํ•ด์ง„ ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ๋“ค๋กœ, ๋ชจ๋‘ ์„ ํƒ์ ์œผ๋กœ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅ
  • ๋˜ํ•œ JWT๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด key๋Š” ๋ชจ๋‘ ๊ธธ์ด 3์˜ String
  • ์—ฌ๊ธฐ์„œ subject๋กœ๋Š” uniqueํ•œ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์‚ฌ์šฉ์ž ์ด๋ฉ”์ผ์„ ์ฃผ๋กœ ์‚ฌ์šฉ
    • iss : ํ† ํฐ ๋ฐœ๊ธ‰์ž(issuer)
    • sub : ํ† ํฐ ์ œ๋ชฉ(subject)
    • aud : ํ† ํฐ ๋Œ€์ƒ์ž(audience)
    • exp : ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„(expiration), NumericDate ํ˜•์‹์œผ๋กœ ๋˜์–ด ์žˆ์–ด์•ผ ํ•จ ex) 1480849147370
    • nbf : ํ† ํฐ ํ™œ์„ฑ ๋‚ ์งœ(not before), ์ด ๋‚ ์ด ์ง€๋‚˜๊ธฐ ์ „์˜ ํ† ํฐ์€ ํ™œ์„ฑํ™”๋˜์ง€ ์•Š์Œ
    • iat : ํ† ํฐ ๋ฐœ๊ธ‰ ์‹œ๊ฐ„(issued at), ํ† ํฐ ๋ฐœ๊ธ‰ ์ดํ›„์˜ ๊ฒฝ๊ณผ ์‹œ๊ฐ„์„ ์•Œ ์ˆ˜ ์žˆ์Œ
    • jti : JWT ํ† ํฐ ์‹๋ณ„์ž(JWT ID), ์ค‘๋ณต ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋ฉฐ, ์ผํšŒ์šฉ ํ† ํฐ(Access Token) ๋“ฑ์— ์‚ฌ์šฉ

2-2-2. ๊ณต๊ฐœ ํด๋ ˆ์ž„(Public Claim)

  • ๊ณต๊ฐœ ํด๋ ˆ์ž„์€ ์‚ฌ์šฉ์ž ์ •์˜ ํด๋ ˆ์ž„์œผ๋กœ, ๊ณต๊ฐœ์šฉ ์ •๋ณด๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋จ
  • ์ถฉ๋Œ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด URI ํฌ๋งท์„ ์ด์šฉํ•˜๋ฉฐ, ์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ
{ "https://mangkyu.tistory.com": true }

2-2-3. ๋น„๊ณต๊ฐœ ํด๋ ˆ์ž„(Private Claim)

  • ๋น„๊ณต๊ฐœ ํด๋ ˆ์ž„์€ ์‚ฌ์šฉ์ž ์ •์˜ ํด๋ ˆ์ž„์œผ๋กœ, ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด์— ์ž„์˜๋กœ ์ง€์ •ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅ
  • ์˜ˆ์‹œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ
{ "token_type": access }

2-3. Signature(์„œ๋ช…)

  • ์„œ๋ช…(Signature)์€ ํ† ํฐ์„ ์ธ์ฝ”๋”ฉํ•˜๊ฑฐ๋‚˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ์œ ํ•œ ์•”ํ˜ธํ™” ์ฝ”๋“œ
  • ์œ„์—์„œ ๋งŒ๋“  ํ—ค๋”(Header)์™€ ํŽ˜์ด๋กœ๋“œ(Payload)์˜ ๊ฐ’์„ ๊ฐ๊ฐ BASE64๋กœ ์ธ์ฝ”๋”ฉํ•˜๊ณ , ์ธ์ฝ”๋”ฉํ•œ ๊ฐ’์„ ๋น„๋ฐ€ ํ‚ค๋ฅผ ์ด์šฉํ•ด ํ—ค๋”(Header)์—์„œ ์ •์˜ํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ํ•ด์‹ฑ์„ ํ•˜๊ณ , ์ด ๊ฐ’์„ ๋‹ค์‹œ BASE64๋กœ ์ธ์ฝ”๋”ฉํ•˜์—ฌ ์ƒ์„ฑ
  • ์ƒ์„ฑ๋œ JWT์˜ ์˜ˆ์‹œ
    JWT ์˜ˆ์‹œ
  • ์ƒ์„ฑ๋œ ํ† ํฐ์€ HTTP ํ†ต์‹ ์„ ํ•  ๋•Œ Authorization์ด๋ผ๋Š” key์˜ value๋กœ ์‚ฌ์šฉ๋จ
  • ์ผ๋ฐ˜์ ์œผ๋กœ value์—๋Š” Bearer์ด ์•ž์— ๋ถ™์—ฌ์ง
{ "Authorization": "Bearer {์ƒ์„ฑ๋œ ํ† ํฐ ๊ฐ’}", }

2-4. JWT ๋‹จ์  ๋ฐ ๊ณ ๋ ค์‚ฌํ•ญ

  • Self-contained : ํ† ํฐ ์ž์ฒด์— ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์–‘๋‚ ์˜ ๊ฒ€์ด ๋  ์ˆ˜ ์žˆ์Œ
  • ํ† ํฐ ๊ธธ์ด : ํ† ํฐ์˜ ํŽ˜์ด๋กœ๋“œ(Payload)์— 3์ข…๋ฅ˜์˜ ํด๋ ˆ์ž„์„ ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ •๋ณด๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ํ† ํฐ์˜ ๊ธธ์ด๊ฐ€ ๋Š˜์–ด๋‚˜ ๋„คํŠธ์›Œํฌ์— ๋ถ€ํ•˜๋ฅผ ์ค„ ์ˆ˜ ์žˆ์Œ
  • Payload ์ธ์ฝ”๋”ฉ : ํŽ˜์ด๋กœ๋“œ(Payload) ์ž์ฒด๋Š” ์•”ํ˜ธํ™” ๋œ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, BASE64๋กœ ์ธ์ฝ”๋”ฉ ๋œ ๊ฒƒ
    • ์ค‘๊ฐ„์— Payload๋ฅผ ํƒˆ์ทจํ•˜์—ฌ ๋””์ฝ”๋”ฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, JWE๋กœ ์•”ํ˜ธํ™”ํ•˜๊ฑฐ๋‚˜ Payload์— ์ค‘์š” ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์ง€ ์•Š์•„์•ผ ํ•จ
  • Stateless : JWT๋Š” ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด์ง€๋ฉด ์ œ์–ด๊ฐ€ ๋ถˆ๊ฐ€๋Šฅ
    • ์ฆ‰, ํ† ํฐ์„ ์ž„์˜๋กœ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๊ผญ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•จ
  • Tore Token : ํ† ํฐ์€ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํ† ํฐ์„ ์ €์žฅํ•ด์•ผ ํ•จ

3. JWT ๊ตฌํ˜„ - Spring Security ์ฒ˜๋ฆฌ ๊ณผ์ • 1

  • ์ฐธ๊ณ  https://mangkyu.tistory.com/57
  • SpringBoot๋กœ SpringSecurity ๊ธฐ๋ฐ˜์˜ JWT ํ† ํฐ ๊ตฌํ˜„ํ•˜๊ธฐ
  • Spring Security ์•„ํ‚คํ…์ณ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ Spring Security ์•„ํ‚คํ…์ณ

3-1. ์‚ฌ์ „ ์„ธํŒ…

  • build.gradle
dependencies { 
   implementation 'io.jsonwebtoken:jjwt:0.9.1' 
   implementation 'org.mariadb.jdbc:mariadb-java-client' 
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 
   implementation 'org.springframework.boot:spring-boot-starter-security' 
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 
   implementation 'org.springframework.boot:spring-boot-starter-web' 
}
  • ์ •์  ์ž์›์„ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค ์ƒ์„ฑ ๋ฐ ์„ค์ •
@Configuration 
public class WebMvcConfig implements WebMvcConfigurer { 
   private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/static/", "classpath:/public/", "classpath:/"
   , "classpath:/resources/", "classpath:/META-INF/resources/", "classpath:/META-INF/resources/webjars/" }; 
   
   @Override 
   public void addViewControllers(ViewControllerRegistry registry) { 
      // /์— ํ•ด๋‹นํ•˜๋Š” url mapping์„ /common/test๋กœ forwardํ•œ๋‹ค. 
      registry.addViewController( "/" ).setViewName( "forward:/index" ); 
      // ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ฐ€์žฅ ๋†’๊ฒŒ ์žก๋Š”๋‹ค. 
      registry.setOrder(Ordered.HIGHEST_PRECEDENCE); 
   } 
   
   @Override 
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
      registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS); 
   } 
}
  • SpringSecurity์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ์„ค์ •๋“ค์„ ์ถ”๊ฐ€
  • SpringSecurity์— ๋Œ€ํ•œ ์„ค์ • ํด๋ž˜์Šค
    • configure ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์ •์  ์ž์›๋“ค์— ๋Œ€ํ•ด์„œ๋Š” Security๋ฅผ ์ ์šฉํ•˜์ง€ ์•Š์Œ์„ ์ถ”๊ฐ€
    • configure ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์–ด๋–ค ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” ๋กœ๊ทธ์ธ์„ ์š”๊ตฌํ•˜๊ณ , ์–ด๋–ค ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ๋กœ๊ทธ์ธ์„ ์š”๊ตฌํ•˜์ง€ ์•Š์„์ง€ ์„ค์ •
    • form ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ์„ ๋น„ํ™œ์„ฑํ™”
@Configuration 
@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
   // ์ •์  ์ž์›์— ๋Œ€ํ•ด์„œ๋Š” Security ์„ค์ •์„ ์ ์šฉํ•˜์ง€ ์•Š์Œ. 
   @Override 
   public void configure(WebSecurity web) {
      web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); 
   } 
   
   @Override 
   protected void configure(HttpSecurity http) throws Exception { 
      http.csrf().disable().authorizeRequests() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ
         .anyRequest().permitAll() 
         .and() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋ฉด ์„ธ์…˜์ด ํ•„์š” ์—†์œผ๋ฏ€๋กœ STATELESS๋กœ ์„ค์ •ํ•˜์—ฌ Session์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
         .and() 
         // form ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ์— ๋Œ€ํ•ด ๋น„ํ™œ์„ฑํ™”
         .formLogin().disable(); 
   } 
      
   @Bean 
   public BCryptPasswordEncoder bCryptPasswordEncoder() { 
      return new BCryptPasswordEncoder(); 
   } 
}

3-2. ๋กœ๊ทธ์ธ ์š”์ฒญ

  • ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ํ›„ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ์š”์ฒญ
  • ๋กœ๊ทธ์ธ API๋ฅผ ํ˜ธ์ถœ, Json์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณด๋‚ด๋Š” ์ƒํ™ฉ์œผ๋กœ ๊ฐ€์ •

3-3. UserPasswordAuthenticationToken ๋ฐœ๊ธ‰

  • ์ „์†ก์ด ์˜ค๋ฉด AuthenticationFilter๋กœ ์š”์ฒญ์ด ๋จผ์ € ์˜ค๊ฒŒ ๋˜๊ณ , ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ UserPasswordAuthenticationToken์„ ๋ฐœ๊ธ‰ํ•ด์ฃผ์–ด์•ผ ํ•จ
  • ์•ˆ์ „์„ ์œ„ํ•ด์„œ ํ”„๋ก ํŠธ์—”๋“œ, ๋ฐฑ์—”๋“œ์—์„œ ์•„์ด๋””์™€ ํŒจ์Šค์›Œ๋“œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Œ
  • ํ•ด๋‹น Filter๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Œ
@Log4j2 
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 
   public CustomAuthenticationFilter(final AuthenticationManager authenticationManager) { 
      super.setAuthenticationManager(authenticationManager); 
   } 
   
   @Override 
   public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException{ 
      final UsernamePasswordAuthenticationToken authRequest;
      try {
         final User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
         authRequest = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPw()); 
      } catch (IOException exception) {
         throw new InputNotFoundException();
      } 
      setDetails(request, authRequest); 
      return this.getAuthenticationManager().authenticate(authRequest);
   } 
}
  • User ๊ฐ์ฒด
@Entity 
@Table(name = "USER") 
@Getter 
@Builder 
@AllArgsConstructor 
@NoArgsConstructor(access = AccessLevel.PROTECTED) 
public class User extends Common implements Serializable {
   @Column(nullable = false, unique = true, length = 50) 
   private String email; 
   
   @Setter 
   @Column(nullable = false) 
   private String pw;
   
   @Setter 
   @Column(nullable = false, length = 50) 
   @Enumerated(EnumType.STRING) 
   private UserRole role; 
}
  • ์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ’์ด ์ œ๋Œ€๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ InputNotFoundException ์ƒ์„ฑ
public class InputNotFoundException extends RuntimeException { 
   public InputNotFoundException(){ 
      super(); 
   } 
}
  • Filter ์ ์šฉ
    • UsernamePasswordAuthenticationFilter ํ•„ํ„ฐ ์ด์ „์— ์ ์šฉ์‹œ์ผœ์•ผ ํ•จ
    • ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น CustomAuthenticationFilter๊ฐ€ ์ˆ˜ํ–‰๋œ ํ›„์— ์ฒ˜๋ฆฌ๋  Handler ์—ญ์‹œ Bean์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  CustomAuthenticationFilter์˜ ํ•ธ๋“ค๋Ÿฌ๋กœ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•จ
    • ํ•ด๋‹น ์ฝ”๋“œ๋“ค์€ WebSecurityConfig์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ถ”๊ฐ€
@Log4j2 
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { 
   @Override 
   public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) { 
      final User user = ((MyUserDetails) authentication.getPrincipal()).getUser(); 
      final String token = TokenUtils.generateJwtToken(user); 
      response.addHeader(AuthConstants.AUTH_HEADER, AuthConstants.TOKEN_TYPE + " " + token);
   } 
}
  • CustomLoginSuccessHandler๋Š” AuthenticationProvider๋ฅผ ํ†ตํ•ด ์ธ์ฆ์ด ์„ฑ๊ณต๋  ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ๋จ
  • ์ธ์ฆ๊ณผ ๊ด€๋ จํ•ด ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ƒ์ˆ˜๋Š” ์•„๋ž˜์˜ AuthConstants ํด๋ž˜์Šค์— ์ •์˜
@NoArgsConstructor(access = AccessLevel.PRIVATE) 
public final class AuthConstants { 
   public static final String AUTH_HEADER = "Authorization"; 
   public static final String TOKEN_TYPE = "BEARER"; 
}
  • ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด TokenUtils๋ฅผ ํ†ตํ•ด ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ , response์— ์ด๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ฐ˜ํ™˜
@Configuration 
@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
   // ์ •์  ์ž์›์— ๋Œ€ํ•ด์„œ๋Š” Security ์„ค์ •์„ ์ ์šฉํ•˜์ง€ ์•Š์Œ
   @Override 
   public void configure(WebSecurity web) { 
      web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); 
   } 
   
   @Override 
   protected void configure(HttpSecurity http) throws Exception { 
      http.csrf().disable().authorizeRequests() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ 
         .anyRequest().permitAll() 
         .and() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋ฉด ์„ธ์…˜์ด ํ•„์š” ์—†์œผ๋ฏ€๋กœ STATELESS๋กœ ์„ค์ •ํ•˜์—ฌ Session์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
         .and() 
         // form ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ์— ๋Œ€ํ•ด ๋น„ํ™œ์„ฑํ™” 
         .formLogin().disable() 
      .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); 
   } 
   
   @Bean 
   public BCryptPasswordEncoder bCryptPasswordEncoder() {
      return new BCryptPasswordEncoder();
   } 
   
   @Bean 
   public CustomAuthenticationFilter customAuthenticationFilter() throws Exception { 
      CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager()); 
      customAuthenticationFilter.setFilterProcessesUrl("/user/login"); 
      customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler()); 
      customAuthenticationFilter.afterPropertiesSet(); 
      return customAuthenticationFilter; 
   } 
   
   @Bean 
   public CustomLoginSuccessHandler customLoginSuccessHandler() {
      return new CustomLoginSuccessHandler(); 
   } 
}
  • CustomAuthenticationFilter๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ณผ์ •์—์„œ UserName, UserPassword ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ • ๊ฐ€๋Šฅ
  • ์ด๋Ÿฌํ•œ ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฉด UsernamePasswordToken์ด ๋ฐœ๊ธ‰๋˜๊ฒŒ ๋จ

3-4. UsernamePasswordToken์„ Authentication Manager์—๊ฒŒ ์ „๋‹ฌ

  • AuthenticationFilter๋Š” ์ƒ์„ฑํ•œ UsernamePasswordToken์„ AuthenticationManager์—๊ฒŒ ์ „๋‹ฌ
  • AuthenticationManager๋Š” ์‹ค์ œ๋กœ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•  ์—ฌ๋Ÿฌ ๊ฐœ์˜ AuthenticationProvider๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Œ

3-5. UsernamePasswordToken์„ Authentication Provider์—๊ฒŒ ์ „๋‹ฌ

  • AuthenticationManager๋Š” ์ „๋‹ฌ๋ฐ›์€ UsernamePasswordToken์„ ์ˆœ์ฐจ์ ์œผ๋กœ AuthenticaionProvider๋“ค์—๊ฒŒ ์ „๋‹ฌํ•˜์—ฌ ์‹ค์ œ ์ธ์ฆ์˜ ๊ณผ์ •์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•จ
  • ์‹ค์ œ ์ธ์ฆ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์€ authenticate ํ•จ์ˆ˜์— ์ž‘์„ฑ์„ ํ•ด์ฃผ์–ด์•ผ ํ•จ
  • SpringSecurity์—์„œ๋Š” Username์œผ๋กœ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•œ ๋‹ค์Œ์—, ๋น„๋ฐ€๋ฒˆํ˜ธ์˜ ์ผ์น˜ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์‚ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™
  • ๋•Œ๋ฌธ์— ๋จผ์ € UsernamePasswordToken ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ ์•„์ด๋””๋ฅผ ์กฐํšŒํ•ด์•ผ ํ•จ
  • ๊ทธ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Œ
@RequiredArgsConstructor 
@Log4j2 
public class CustomAuthenticationProvider implements AuthenticationProvider { 
   @Override 
   public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
      final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; 
      // AuthenticaionFilter์—์„œ ์ƒ์„ฑ๋œ ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์กฐํšŒํ•จ 
      final String email = token.getName(); 
   } 
   
   @Override 
   public boolean supports(Class<?> authentication) { 
      return authentication.equals(UsernamePasswordAuthenticationToken.class);
   }
}

3-6. UserDetailsService๋กœ ์กฐํšŒํ•  ์•„์ด๋””๋ฅผ ์ „๋‹ฌ

  • AuthenticationProvider์—์„œ ์•„์ด๋””๋ฅผ ์กฐํšŒํ•˜์˜€์œผ๋ฉด, UserDetailsService๋กœ๋ถ€ํ„ฐ ์•„์ด๋””๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด์•ผ ํ•จ
  • UserDetailsService๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ implementsํ•œ ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑ
  • ์‹ค์ œ ๋ฐ˜ํ™˜๊ฐ’์„ ์ž‘์„ฑํ•˜๋Š” ๋ถ€๋ถ„์€ 7๋ฒˆ ํ™•์ธ
@RequiredArgsConstructor 
@Service 
public class UserDetailsServiceImpl implements UserDetailsService { 
   private final UserRepository userRepository; 
   
   @Override 
   public MyUserDetails loadUserByUsername(String email) {

   } 
}
  • User์™€ ๊ด€๋ จ๋œ SQL์„ ์ฒ˜๋ฆฌํ•˜๋Š” JpaRepository๋ฅผ ๊ตฌํ˜„ํ•œ UserRepository
@Repository
public interface UserRepository extends JpaRepository <User, Long> {
   User findByEmailAndPw(String email, String pw); 
   Optional<User> findByEmail(String email); 
}

3-7. ์•„์ด๋””๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DB์—์„œ ๋ฐ์ดํ„ฐ ์กฐํšŒ

  • ์ „๋‹ฌ๋ฐ›์€ ์•„์ด๋””๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DB์—์„œ ์กฐํšŒํ•˜๋Š” ๊ตฌํ˜„์ฒด๋Š” User VO
  • UserDetailsService์˜ ๋ฐ˜ํ™˜๊ฐ’์€ UserDetails ์ธํ„ฐํŽ˜์ด์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ implementsํ•˜์—ฌ ๊ตฌํ˜„ํ•œ MyUserDetails๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ
@RequiredArgsConstructor 
@Getter 
public class MyUserDetails implements UserDetails { 
   @Delegate 
   private final User user; 
   private final Collection<? extends GrantedAuthority> authorities;
   
   @Override 
   public Collection<? extends GrantedAuthority> getAuthorities() { 
      return authorities; 
   } 
   
   @Override public String getPassword() { 
      return user.getPw();
   } 
   
   @Override public String getUsername() { 
      return user.getEmail(); 
   } 
   
   @Override public boolean isAccountNonExpired() { 
      return user.getIsEnable(); 
   } 
   
   @Override public boolean isAccountNonLocked() { 
      return user.getIsEnable();
   } 
   
   @Override public boolean isCredentialsNonExpired() { 
      return user.getIsEnable(); 
   } 
   
   @Override public boolean isEnabled() { 
      return user.getIsEnable(); 
   } 
}
  • ์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” UserRepository๋กœ๋ถ€ํ„ฐ ์กฐํšŒํ•œ ๊ฒฐ๊ณผ๋ฅผ Optional๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— map ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ ์ƒˆ๋กœ์šด UserDetails ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์žˆ์Œ

3-8. ์ธ์ฆ ์ฒ˜๋ฆฌ ํ›„ ์ธ์ฆ๋œ ํ† ํฐ์„ AuthenticationManager์—๊ฒŒ ๋ฐ˜ํ™˜

  • CustomAuthenticationProvider์—์„œ UserDetailsService๋ฅผ ํ†ตํ•ด ์กฐํšŒํ•œ ์ •๋ณด์™€ ์ž…๋ ฅ๋ฐ›์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์—ฌ, ์ผ์น˜ํ•œ๋‹ค๋ฉด ์ธ์ฆ๋œ ํ† ํฐ์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ด์ฃผ์–ด์•ผ ํ•จ
  • DB์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์•”ํ˜ธํ™”๊ฐ€ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ž…๋ ฅ์œผ๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜จ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ PasswordEncoder๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”ํ•˜์—ฌ DB์—์„œ ์กฐํšŒํ•œ ์‚ฌ์šฉ์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธํ™” ๋งค์นญ๋˜๋Š”์ง€ ํ™•์ธ
  • ๋งŒ์•ฝ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งค์นญ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋Š” BadCredentialsException์„ ๋ฐœ์ƒ์‹œ์ผœ ์ฒ˜๋ฆฌ
@RequiredArgsConstructor 
@Log4j2 
public class CustomAuthenticationProvider implements AuthenticationProvider { 
   private final UserDetailsService userDetailsService; 
   private final BCryptPasswordEncoder passwordEncoder; 
   
   @Override 
   public Authentication authenticate(final Authentication authentication) throws AuthenticationException { 
      final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication; 

      // AuthenticaionFilter์—์„œ ์ƒ์„ฑ๋œ ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์กฐํšŒํ•จ 
      final String userEmail = token.getName(); 
      final String userPw = (String) token.getCredentials(); 
      
      // UserDetailsService๋ฅผ ํ†ตํ•ด DB์—์„œ ์•„์ด๋””๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ
      final MyUserDetails userDetails = (MyUserDetails) userDetailsService.loadUserByUsername(userEmail);
      
      if (!passwordEncoder.matches(userPw, userDetails.getPassword())) { 
         throw new BadCredentialsException(userDetails.getUsername() + "Invalid password"); 
      } 
      
      return new UsernamePasswordAuthenticationToken(userDetails, userPw, userDetails.getAuthorities());
   } 
   
   @Override 
   public boolean supports(Class<?> authentication) { 
      return authentication.equals(UsernamePasswordAuthenticationToken.class); 
   }
}
  • ์œ„์™€ ๊ฐ™์ด ์™„์„ฑ๋œ CustomAuthenticaionProvider๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•ด์ฃผ์–ด์•ผ ํ•จ > WebSecurityConfig์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ
@Configuration 
@EnableWebSecurity
@RequiredArgsConstructor 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
   private final UserDetailsService userDetailsService; 
   
   // ์ •์  ์ž์›์— ๋Œ€ํ•ด์„œ๋Š” Security ์„ค์ •์„ ์ ์šฉํ•˜์ง€ ์•Š์Œ. 
   @Override 
   public void configure(WebSecurity web) { 
      web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); 
   } 
   
   @Override 
   protected void configure(HttpSecurity http) throws Exception { 
      http.csrf().disable().authorizeRequests() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ 
         .anyRequest().permitAll() 
         .and() 
         // ํ† ํฐ์„ ํ™œ์šฉํ•˜๋ฉด ์„ธ์…˜์ด ํ•„์š” ์—†์œผ๋ฏ€๋กœ STATELESS๋กœ ์„ค์ •ํ•˜์—ฌ Session์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
         .and() 
         // form ๊ธฐ๋ฐ˜์˜ ๋กœ๊ทธ์ธ์— ๋Œ€ํ•ด ๋น„ํ™œ์„ฑํ™”
         .formLogin() .disable() 
         .addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
   } 
   
   @Bean 
   public BCryptPasswordEncoder bCryptPasswordEncoder() { 
      return new BCryptPasswordEncoder();
   } 
   
   @Bean 
   public CustomAuthenticationFilter customAuthenticationFilter() throws Exception { 
      CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager()); 
      customAuthenticationFilter.setFilterProcessesUrl("/user/login"); 
      customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler()); 
      customAuthenticationFilter.afterPropertiesSet(); 
      return customAuthenticationFilter; 
   } 
   
   @Bean 
   public CustomLoginSuccessHandler customLoginSuccessHandler() { 
      return new CustomLoginSuccessHandler(); 
   } 
   
   @Bean 
   public CustomAuthenticationProvider customAuthenticationProvider() { 
      return new CustomAuthenticationProvider(userDetailsService, bCryptPasswordEncoder());
   } 
   
   @Override 
   public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) { 
      authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider()); 
   } 
}

3-9. ์ธ์ฆ๋œ ํ† ํฐ์„ AuthenticationFilter์—๊ฒŒ ์ „๋‹ฌ

  • AuthenticaitonProvider์—์„œ ์ธ์ฆ์ด ์™„๋ฃŒ๋œ UsernamePasswordAuthenticationToken์„ AuthenticationFilter๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ 
  • AuthenticationFilter์—์„œ๋Š” LoginSuccessHandler๋กœ ์ „๋‹ฌ

3-10. ์ธ์ฆ๋œ ํ† ํฐ์„ ๊ธฐ๋ฐ˜์œผ๋กœ JWT ๋ฐœ๊ธ‰

  • LoginSuccessHandler๋กœ ๋„˜์–ด์˜จ ์š”์ฒญ์€ /user/loginSuccess๋กœ redirect ๋จ
  • ์ „๋‹ฌ๋ฐ›์€ Authentication ์ •๋ณด๋ฅผ ํ™œ์šฉํ•ด Json Web Token์„ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, ํ† ํฐ๊ณผ ๊ด€๋ จ๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” TokenUtils๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ์Œ
@Log4j2 
@NoArgsConstructor(access = AccessLevel.PRIVATE) 
public final class TokenUtils { 
   private static final String secretKey = "ThisIsA_SecretKeyForJwtExample"; 
   
   public static String generateJwtToken(User user) { 
      JwtBuilder builder = Jwts.builder() 
         .setSubject(user.getEmail()) 
         .setHeader(createHeader()) 
         .setClaims(createClaims(user)) 
         .setExpiration(createExpireDateForOneYear()) 
         .signWith(SignatureAlgorithm.HS256, createSigningKey()); 
      
      return builder.compact(); 
   } 
   
   public static boolean isValidToken(String token) {
      try { 
         Claims claims = getClaimsFormToken(token); 
         log.info("expireTime :" + claims.getExpiration()); 
         log.info("email :" + claims.get("email")); 
         log.info("role :" + claims.get("role")); 
         return true; 
      } catch (ExpiredJwtException exception) { 
         log.error("Token Expired"); 
         return false; 
      } catch (JwtException exception) { 
         log.error("Token Tampered"); 
         return false; 
      } catch (NullPointerException exception) { 
         log.error("Token is null"); 
         return false; 
      }
   } 
   
   public static String getTokenFromHeader(String header) { 
      return header.split(" ")[1];
   } 
   
   private static Date createExpireDateForOneYear() { 
      // ํ† ํฐ ๋งŒ๋ฃŒ์‹œ๊ฐ„์€ 30์ผ์œผ๋กœ ์„ค์ • 
      Calendar c = Calendar.getInstance(); 
      c.add(Calendar.DATE, 30); 
      return c.getTime();
   } 
   
   private static Map<String, Object> createHeader() { 
      Map<String, Object> header = new HashMap<>(); 
      header.put("typ", "JWT"); 
      header.put("alg", "HS256"); 
      header.put("regDate", System.currentTimeMillis()); 
      return header;
   } 
   
   private static Map<String, Object> createClaims(User user) { 
      // ๊ณต๊ฐœ ํด๋ ˆ์ž„์— ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ์„ ์„ค์ •ํ•˜์—ฌ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Œ
      Map<String, Object> claims = new HashMap<>(); 
      claims.put("email", user.getEmail()); 
      claims.put("role", user.getRole()); 
      return claims;
   } 
   
   private static Key createSigningKey() { 
      byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secretKey);
      return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName()); 
   } 
   
   private static Claims getClaimsFormToken(String token) { 
      return Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secretKey)) 
        .parseClaimsJws(token).getBody(); 
   } 
   
   private static String getUserEmailFromToken(String token) { 
      Claims claims = getClaimsFormToken(token); 
      return (String) claims.get("email"); 
   } 
   
   private static UserRole getRoleFromToken(String token) { 
      Claims claims = getClaimsFormToken(token); 
      return (UserRole) claims.get("role");
   } 
}
  • ์ธ์ฆ์ด ์„ฑ๊ณต๋˜๊ณ  ๋‚˜๋ฉด CustomLoginSuccessHandler์—์„œ Token์ด ์ƒ์„ฑ๋˜๊ณ , ์ƒ์„ฑ๋œ ํ† ํฐ์„ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋จ

4. JWT ๊ตฌํ˜„ - Spring Security ์ฒ˜๋ฆฌ ๊ณผ์ • 2

โš ๏ธ **GitHub.com Fallback** โš ๏ธ