[Security] JWT(JSON Web Token)

Updated:

Categories:

Tags: , ,

๐Ÿ“Œ ๊ฐœ์ธ์ ์ธ ๊ณต๊ฐ„์œผ๋กœ ๊ณต๋ถ€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ณต์Šตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค :๐Ÿ˜ธ
[ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋ณต๋ฐ›์œผ์‹ค๊ฑฐ์—์š”]

JWT(JSON Web Token)

ํ† ํฐ ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช…

์„ธ์…˜๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช…

์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช… ๋ฐฉ์‹์€ ์„œ๋ฒ„ ์ธก์— ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์„ธ์…˜ ํ˜•ํƒœ๋กœ ์„ธ์…˜ ์ €์žฅ์†Œ์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช…์˜ ํŠน์ง•
    • ์„ธ์…˜์€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์„œ๋ฒ„ ์ธก ์„ธ์…˜ ์ €์žฅ์†Œ์—์„œ ๊ด€๋ฆฌํ•œ๋‹ค.
    • ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ์„ธ์…˜์˜ ๊ณ ์œ  ID์ธ ์„ธ์…˜ ID๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ฟ ํ‚ค์— ์ €์žฅ๋˜์–ด request ์ „์†ก ์‹œ, ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€๋ฅผ ์ฆ๋ช…ํ•˜๋Š” ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.
    • ์„ธ์…˜ ID๋งŒ ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์ƒ๋Œ€์ ์œผ๋กœ ์ ์€ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์„œ๋ฒ„ ์ธก์—์„œ ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ๋ณด์•ˆ์„ฑ ์ธก๋ฉด์—์„œ ์กฐ๊ธˆ ๋” ์œ ๋ฆฌํ•˜๋‹ค.
    • ์„œ๋ฒ„์˜ ํ™•์žฅ์„ฑ ๋ฉด์—์„œ๋Š” ์„ธ์…˜ ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.
    • ์„ธ์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์ง€๋ฉด ์งˆ์ˆ˜๋ก ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์ด ๊ฐ€์ค‘๋  ์ˆ˜ ์žˆ๋‹ค.
    • SSR(Server Side Rendering) ๋ฐฉ์‹์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•œ ๋ฐฉ์‹์ด๋‹ค

ํ† ํฐ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช…

ํ† ํฐ ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช… ๋ฐฉ์‹์€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ํ† ํฐ์— ์ €์žฅํ•˜๊ณ , ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ด ์ ‘๊ทผ ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋œ ํŠน์ • ๋ฆฌ์†Œ์Šค์—๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

  • ํ† ํฐ ๊ธฐ๋ฐ˜ ์ž๊ฒฉ ์ฆ๋ช…์˜ ํŠน์ง•
    • ํ† ํฐ์— ํฌํ•จ๋œ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ์„œ๋ฒ„ ์ธก์—์„œ ๋ณ„๋„์˜ ๊ด€๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ์ƒ์„ฑ๋œ ํ† ํฐ์„ ํ—ค๋”์— ํฌํ•จํ•ด request ์ „์†ก ์‹œ, ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์ธ์ง€๋ฅผ ์ฆ๋ช…ํ•˜๋Š” ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.
    • ํ† ํฐ ๋‚ด์— ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด ๋“ฑ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์„ธ์…˜์— ๋น„ํ•ด ์ƒ๋Œ€์ ์œผ๋กœ ๋งŽ์€ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ์„ ์‚ฌ์šฉํ•œ๋‹ค.
    • ๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋ฒ„ ์ธก์—์„œ ํ† ํฐ์„ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ณด์•ˆ์„ฑ ์ธก๋ฉด์—์„œ ์กฐ๊ธˆ ๋” ๋ถˆ๋ฆฌํ•˜๋‹ค.
    • ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž request์˜ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์˜ ํ™•์žฅ์„ฑ ๋ฉด์—์„œ ์œ ๋ฆฌํ•˜๊ณ , ์„ธ์…˜ ๋ถˆ์ผ์น˜ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • ํ† ํฐ์— ํฌํ•จ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ํ† ํฐ์˜ ํŠน์„ฑ์ƒ ์•”ํ˜ธํ™”๊ฐ€ ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉ์ž์—๊ฒŒ ํ† ํฐ์ด ํƒˆ์ทจ๋  ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ทธ๋Œ€๋กœ ์ œ๊ณตํ•˜๋Š” ์…ˆ์ด ๋˜๋ฏ€๋กœ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ํ† ํฐ์— ํฌํ•จํ•˜์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค.
    • ๊ธฐ๋ณธ์ ์œผ๋กœ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ํ† ํฐ์„ ๋ฌดํšจํ™”์‹œํ‚ฌ ์ˆ˜ ์—†๋‹ค.
    • CSR(Client Side Rendering) ๋ฐฉ์‹์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ํ•ฉํ•œ ๋ฐฉ์‹์ด๋‹ค

JWT(JSON Web Token)

JWT๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ์•ˆ๋œ ์ธํ„ฐ๋„ท ํ‘œ์ค€ ์ธ์ฆ ๋ฐฉ์‹์œผ๋กœ์จ ํ† ํฐ ์ธ์ฆ ๋ฐฉ์‹์—์„œ ๊ฐ€์žฅ ๋ฒ”์šฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ JSON ํฌ๋งท์˜ ํ† ํฐ ์ •๋ณด๋ฅผ ์ธ์ฝ”๋”ฉ ํ›„, ์ธ์ฝ”๋”ฉ ๋œ ํ† ํฐ ์ •๋ณด๋ฅผ Secret Key๋กœ ์„œ๋ช…(Sign)ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ Web Token์œผ๋กœ์จ ์ธ์ฆ ๊ณผ์ •์— ์‚ฌ์šฉ

JWT ๊ณต์‹ ์‚ฌ์ดํŠธ : https://jwt.io/

JWT์˜ ์ข…๋ฅ˜

JWT๋Š” ๋ณดํ†ต ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‘ ๊ฐ€์ง€ ์ข…๋ฅ˜์˜ ํ† ํฐ์„ ์‚ฌ์šฉ์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์— ์ด์šฉํ•œ๋‹ค โ‡’ Access Token & Refresh Token

  1. ์•ก์„ธ์Šค ํ† ํฐ(Access Token)
    • ๋ณดํ˜ธ๋œ ์ •๋ณด๋“ค(์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ, ์—ฐ๋ฝ์ฒ˜, ์‚ฌ์ง„ ๋“ฑ)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ์— ์‚ฌ์šฉ
    • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฒ˜์Œ ์ธ์ฆ์„ ๋ฐ›๊ฒŒ ๋  ๋•Œ(๋กœ๊ทธ์ธ ์‹œ), Access Token๊ณผ Refresh Token ๋‘ ๊ฐ€์ง€๋ฅผ ๋‹ค ๋ฐ›์ง€๋งŒ, ์‹ค์ œ๋กœ ๊ถŒํ•œ์„ ์–ป๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ํ† ํฐ์€ Access Token
    • ์ฃผ์š” ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Access Token์—๋Š” ๋น„๊ต์  ์งง์€ ์œ ํšจ ๊ธฐ๊ฐ„์„ ์ฃผ์–ด ํƒˆ์ทจ๋˜๋”๋ผ๋„ ์˜ค๋žซ๋™์•ˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋„๋ก ํ•œ๋‹ค. โ‡’ Access Token์˜ ์œ ํšจ๊ธฐ๊ฐ„์ด ๋งŒ๋ฃŒ๋œ๋‹ค๋ฉด Refresh Token์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด Access Token์„ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค. ์ด๋•Œ, ์‚ฌ์šฉ์ž๋Š” ๋‹ค์‹œ ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  2. ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ(Refresh Token)
    • ์•ก์„ธ์Šค ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰๋ฐ›๊ธฐ ์œ„ํ•œ ์ •๋ณด๋งŒ ๋‹ด๊ณ  ์žˆ์Œ.
    • Access Token์˜ ์œ ํšจ๊ธฐ๊ฐ„์ด ๋งŒ๋ฃŒ๋œ๋‹ค๋ฉด Refresh Token์„ ์‚ฌ์šฉํ•˜์—ฌ
    • ๋ฆฌํ”„๋ ˆ์‰ฌํ† ํฐ์€ ์œ ํšจ๊ธฐ๊ฐ„์ด ๊ธธ๊ธฐ ๋•Œ๋ฌธ์— ํƒˆ์ทจ๋‹นํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌํ”„๋ ˆ์‰ฌํ† ํฐ์„ ์ผํšŒ์šฉ์œผ๋กœ ๋งŒ๋“ ๋‹ค.
    • ์ฆ‰ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€์ง„ ์•Š์ง€๋งŒ. ์•ก์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์„œ ๋ฆฌํ”„๋ ˆ์‹œ์—์„œ ์•ก์„ธ์Šคํ† ํฐ์„ ์š”์ฒญ ํ•  ๋•Œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ํ•จ๊ป˜ ์žฌ๋ฐœํ–‰ ํ•œ๋‹ค.
    • ์‚ฌ์šฉ์ž์˜ ํŽธ์˜๋ณด๋‹ค ์ •๋ณด๋ฅผ ์ง€ํ‚ค๋Š” ๊ฒƒ์ด ๋” ์ค‘์š”ํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Refresh Token์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ณณ์ด ๋งŽ๋‹ค.

JWT ๊ตฌ์กฐ

JWT๋Š” ์œ„ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด .์œผ๋กœ ๋‚˜๋ˆ„์–ด์ง„ ์„ธ ๋ถ€๋ถ„์ด ์กด์žฌํ•œ๋‹ค.

  1. Header
    • Header๋Š” ์ด๊ฒƒ์ด ์–ด๋–ค ์ข…๋ฅ˜์˜ ํ† ํฐ์ธ์ง€(์ง€๊ธˆ์˜ ๊ฒฝ์šฐ์—” JWT), ์–ด๋–ค ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ Signํ• ์ง€ ์ •์˜ํ•œ๋‹ค.
    • JSON Web Token์ด๋ผ๋Š” ์ด๋ฆ„์— ๊ฑธ๋งž๊ฒŒ JSON ํฌ๋งท ํ˜•ํƒœ๋กœ ์ •์˜๋˜์–ด ์žˆ๋‹ค.

      1
      2
      3
      4
      
        {
          "alg": "HS256",
          "typ": "JWT"
        }
      
    • ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™”

    ์„œ๋ฒ„์—์„œ๋Š” ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋กœ ํ™•์ธํ•œ๋‹ค โ†’ ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ๊ฒ€์ฆ์˜ ๋Œ€์ƒ์ด๋‹ค ,

    JWT์˜ ํ—ค๋”์™€ ํ”Œ๋ ˆ์ด ๋กœ๋“œ๋Š” ํ•ด์‹œํ•จ์ˆ˜ ๊ฒ€์ฆ๊ณผ์ •์—์„œ ๊ฑธ๋Ÿฌ์ง

    ๋‘๊ฐ€์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณตํ˜ธํ™”ํ•˜๋”๋ผ๋„ ๋ณดํ†ต ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ๋ณด๋‚ผ๋•Œ ํ—ค๋”์— ๋‹ด์•„์„œ ๋ณด

  2. Payload

    1
    2
    3
    4
    5
    
     {
       "sub": "someInformation",
       "name": "phillip",
       "iat": 151623391
     }
    
    • Payload์—๋Š” ์„œ๋ฒ„์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋‹ค.
    • ์–ด๋–ค ์ •๋ณด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ์ง€์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๋‹ด์„ ์ˆ˜๋„ ์žˆ๊ณ , ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„ ๋“ฑ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋‹ค.
    • Payload๋Š” ๋‹ค์Œ์œผ๋กœ ์„ค๋ช…ํ•  Signature๋ฅผ ํ†ตํ•ด ์œ ํšจ์„ฑ์ด ๊ฒ€์ฆ๋  ์ •๋ณด์ด๊ธด ํ•˜์ง€๋งŒ, ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ๋‹ด์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. (์†์‰ฝ๊ฒŒ ์ฝ”๋”ฉ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ)
    • ์ฒซ ๋ฒˆ์งธ ๋ถ€๋ถ„๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ์œ„ JSON ๊ฐ์ฒด๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉํ•˜๋ฉด JWT์˜ ๋‘ ๋ฒˆ์งธ ๋ธ”๋ก์ด ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.
  3. Signature
    • base64๋กœ ์ธ์ฝ”๋”ฉ ๋œ ์ฒซ ๋ฒˆ์งธ, ๊ทธ๋ฆฌ๊ณ  ๋‘ ๋ฒˆ์งธ ๋ถ€๋ถ„์ด ์™„์„ฑ๋˜์—ˆ๋‹ค๋ฉด, Signature์—์„œ๋Š” ์›ํ•˜๋Š” ๋น„๋ฐ€ ํ‚ค(Secret Key)์™€ Header์—์„œ ์ง€์ •ํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ Header์™€ Payload์— ๋Œ€ํ•ด์„œ ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธํ™”๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.
    • ์ด๋ ‡๊ฒŒ ์•”ํ˜ธํ™”๋œ ๋ฉ”์‹œ์ง€๋Š” ํ† ํฐ์˜ ์œ„๋ณ€์กฐ ์œ ๋ฌด๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด, ๋งŒ์•ฝ HMAC SHA256 ์•Œ๊ณ ๋ฆฌ์ฆ˜(์•”ํ˜ธํ™” ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜)์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด Signature๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ƒ์„ฑ๋จ

      1
      
        HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret);
      

JWT ์‚ฌ์šฉ ์˜ˆ์‹œ

JWT๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ์— ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ƒˆ๋กœ ๋‹ค์šด๋กœ๋“œํ•œ A๋ผ๋Š” ์•ฑ์ด Gmail๊ณผ ์—ฐ๋™๋˜์–ด ์ด๋ฉ”์ผ์„ ์ฝ์–ด์™€์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค.

์ด ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๋Š”

  1. Gmail ์ธ์ฆ์„œ๋ฒ„์— ๋กœ๊ทธ์ธ ์ •๋ณด(์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  2. ์ธ์ฆ์— ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ, JWT๋ฅผ ๋ฐœ๊ธ‰๋ฐ›์Šต๋‹ˆ๋‹ค.
  3. A ์•ฑ์€ JWT๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ์„ ์ฝ๊ฑฐ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์ ˆ์ฐจ

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋‹ด์•„ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  2. ์•„์ด๋””/๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ผ ์•”ํ˜ธํ™”๋œ ํ† ํฐ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • Access Token๊ณผ Refresh Token์„ ๋ชจ๋‘ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
      • ํ† ํฐ์— ๋‹ด๊ธธ ์ •๋ณด(Payload)๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•  ์ •๋ณด, ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ ์ •๋ณด ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      • Refresh Token ์ด์šฉํ•ด ์ƒˆ๋กœ์šด Access Token์„ ์ƒ์„ฑํ•  ๊ฒƒ์ด๋ฏ€๋กœ ๋‘ ์ข…๋ฅ˜์˜ ํ† ํฐ์ด ๊ฐ™์€ ์ •๋ณด๋ฅผ ๋‹ด์„ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.
  3. ํ† ํฐ์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•˜๋ฉด, ํด๋ผ์ด์–ธํŠธ๋Š” ํ† ํฐ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    • ์ €์žฅํ•˜๋Š” ์œ„์น˜๋Š” Local Storage, Session Storage, Cookie ๋“ฑ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ํด๋ผ์ด์–ธํŠธ๊ฐ€ HTTP Header(Authorization Header) ๋˜๋Š” ์ฟ ํ‚ค์— ํ† ํฐ์„ ๋‹ด์•„ request๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  5. ์„œ๋ฒ„๋Š” ํ† ํฐ์„ ๊ฒ€์ฆํ•˜์—ฌ โ€œ์•„ ์šฐ๋ฆฌ๊ฐ€ ๋ฐœ๊ธ‰ํ•ด ์ค€ ํ† ํฐ์ด ๋งž๋„ค!โ€๋ผ๋Š” ํŒ๋‹จ์ด ๋  ๊ฒฝ์šฐ, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ํ›„ ์‘๋‹ต์„ ๋ณด๋‚ด์ค€๋‹ค.

์žฅ๋‹จ์ 

JWT๋ฅผ ํ†ตํ•œ ์ธ์ฆ์˜ ์žฅ์ 

  1. ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ (Stateless), ํ™•์žฅ์— ์šฉ์ดํ•œ(Scalable) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์šฉ์ดํ•˜๋‹ค.
    • ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. (ํ† ํฐ์ด ์ •์ƒ์ ์œผ๋กœ ๊ฒ€์ฆ๋˜๋Š”์ง€๋งŒ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค)
    • ํด๋ผ์ด์–ธํŠธ๋Š” request๋ฅผ ์ „์†กํ•  ๋•Œ๋งˆ๋‹ค ํ† ํฐ์„ ํ—ค๋”์— ํฌํ•จ์‹œํ‚ค๋ฉด ๋œ๋‹ค
      • ์—ฌ๋Ÿฌ ๋Œ€์˜ ์„œ๋ฒ„๋ฅผ ์ด์šฉํ•œ ์„œ๋น„์Šค๋ผ๋ฉด ํ•˜๋‚˜์˜ ํ† ํฐ์œผ๋กœ ์—ฌ๋Ÿฌ ์„œ๋ฒ„์—์„œ ์ธ์ฆ์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— JWT๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํšจ๊ณผ์ ์ด๋‹ค.

      ๋งŒ์•ฝ์— ์„ธ์…˜ ๋ฐฉ์‹์ด๋ผ๋ฉด ๋ชจ๋“  ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

  2. ํด๋ผ์ด์–ธํŠธ๊ฐ€ request๋ฅผ ์ „์†กํ•  ๋•Œ๋งˆ๋‹ค ์ž๊ฒฉ ์ฆ๋ช… ์ •๋ณด๋ฅผ ์ „์†กํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
    • HTTP Basic ๊ฐ™์€ ์ธ์ฆ ๋ฐฉ์‹์€ request๋ฅผ ์ „์†กํ•  ๋•Œ๋งˆ๋‹ค ์ž๊ฒฉ ์ฆ๋ช… ์ •๋ณด๋ฅผ ํฌํ•จํ•ด์•ผ ํ•˜์ง€๋งŒ JWT์˜ ๊ฒฝ์šฐ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ํ•œ ๋ฒˆ์˜ ์ธ์ฆ๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋œ๋‹ค.
  3. ์ธ์ฆ์„ ๋‹ด๋‹นํ•˜๋Š” ์‹œ์Šคํ…œ์„ ๋‹ค๋ฅธ ํ”Œ๋žซํผ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์šฉ์ดํ•˜๋‹ค
    • ์‚ฌ์šฉ์ž์˜ ์ž๊ฒฉ ์ฆ๋ช… ์ •๋ณด๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๊ณ , Github, Google ๋“ฑ์˜ ๋‹ค๋ฅธ ํ”Œ๋žซํผ์˜ ์ž๊ฒฉ ์ฆ๋ช… ์ •๋ณด๋กœ ์ธ์ฆํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅ
    • ํ† ํฐ ์ƒ์„ฑ์šฉ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ํšŒ์‚ฌ์—์„œ ํ† ํฐ ๊ด€๋ จ ์ž‘์—…์„ ๋งก๊ธฐ๋Š” ๊ฒƒ ๋“ฑ ๋‹ค์–‘ํ•œ ํ™œ์šฉ์ด ๊ฐ€๋Šฅ
  4. ๊ถŒํ•œ ๋ถ€์—ฌ์— ์šฉ์ดํ•˜๋‹ค
    • ํ† ํฐ์˜ Payload(๋‚ด์šฉ๋ฌผ) ์•ˆ์— ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ๊ฒƒ์ด ์šฉ์ดํ•˜๋‹ค

JWT๋ฅผ ํ†ตํ•œ ์ธ์ฆ์˜ ๋‹จ์ 

  1. Payload๋Š” ๋””์ฝ”๋”ฉ์ด ์šฉ์ดํ•˜๋‹ค
    • Payload๋Š” base64๋กœ ์ธ์ฝ”๋”ฉ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ† ํฐ์„ ํƒˆ์ทจํ•˜์—ฌ Payload๋ฅผ ๋””์ฝ”๋”ฉํ•˜๋ฉด ํ† ํฐ ์ƒ์„ฑ ์‹œ ์ €์žฅํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Payload์—๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.
  2. ํ† ํฐ์˜ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ง€๋ฉด ๋„คํŠธ์›Œํฌ์— ๋ถ€ํ•˜๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋‹ค
    • ํ† ํฐ์— ์ €์žฅํ•˜๋Š” ์ •๋ณด์˜ ์–‘์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ํ† ํฐ์˜ ๊ธธ์ด๋Š” ๊ธธ์–ด์ง„๋‹ค.

      ๋”ฐ๋ผ์„œ request๋ฅผ ์ „์†กํ•  ๋•Œ๋งˆ๋‹ค ๊ธธ์ด๊ฐ€ ๊ธด ํ† ํฐ์„ ํ•จ๊ป˜ ์ „์†กํ•˜๋ฉด ๋„คํŠธ์›Œํฌ์— ๋ถ€ํ•˜๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

  3. ํ† ํฐ์€ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜์ง€ ์•Š๋Š”๋‹ค.
    • ์ฆ‰ ํ•œ ๋ฒˆ ์ƒ์„ฑ๋œ ํ† ํฐ์€ ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๋ฐ˜๋“œ์‹œ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
    • ๋˜ํ•œ ํ† ํฐ์ด ํƒˆ์ทจ๋œ ๊ฒฝ์šฐ ํ† ํฐ์˜ ๊ธฐํ•œ์ด ๋งŒ๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ํ† ํฐ ํƒˆ์ทจ์ž๊ฐ€ ํ•ด๋‹น ํ† ํฐ์„ ์ •์ƒ์ ์œผ๋กœ ์ด์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์„ค์ •ํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

์ปคํ”ผ ์ฃผ๋ฌธ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— JWT์ ์šฉ

์‚ฌ์ „์ž‘์—… (์˜์กด์„ฑ์ถ”๊ฐ€, ๋น„๋ฐ€๋ฒˆํ˜ธ๊ด€๋ จ ํ•„๋“œ ์ถ”๊ฐ€)

1. ์˜์กด๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

1
2
3
4
5
6
7
	// Spring Security๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด spring-boot-starter-security๋ฅผ ์ถ”๊ฐ€
	implementation 'org.springframework.boot:spring-boot-starter-security'

  // (2) JWT ๊ธฐ๋Šฅ์„ ์œ„ํ•œ jjwt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
	runtimeOnly	'io.jsonwebtoken:jjwt-jackson:0.11.5'

2. JWT ์ ์šฉ ์ „ Spring Security๋ฅผ ์ด์šฉํ•ด์„œ ์ตœ์†Œํ•œ์˜ ๋ณด์•ˆ๊ตฌ์„ฑ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.springboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        http
                .headers().frameOptions().sameOrigin()
                //๋™์ผ ์ถœ์ฒ˜๋กœ๋ถ€ํ„ฐ ๋“ค์–ด์˜ค๋Š” request๋งŒ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง์„ ํ—ˆ์šฉ
                .and()
                .csrf().disable() //๋ฐฐํฌํ•  ๋•Œ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋จ.
                .cors(withDefaults()) //corsConfigurationSource๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋“ฑ๋ก๋œ Bean์„ ์ด์šฉ
                //CORS๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ CorsFilter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ธ๋ฐ 
                // CorsConfigurationSource Bean์„ ์ œ๊ณตํ•จ์œผ๋กœ์จ CorsFilter๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
                .formLogin().disable() //ํผ๋กœ๊ทธ์ธ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŸ๋‹ค.
                .httpBasic().disable()
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
                //์ธ์ฆ์ธ๊ฐ€ ํ›„ ๋ณ€๊ฒฝ์˜ˆ์ •
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    //Cors์„ค์ • ์ถ”๊ฐ€, corsConfigurationSource๋ฅผ ๋นˆ์œผ๋กœ ๋™๋ก
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
            //๋ชจ๋“  ์ถœ์ฒ˜(Origin)์— ๋Œ€ํ•ด ์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ฐ˜์˜ HTTP ํ†ต์‹ ์„ ํ—ˆ์šฉํ•˜๋„๋ก ์„ค์ •
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
            //ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ง€์ •ํ•œ HTTP Method์— ๋Œ€ํ•œ HTTP ํ†ต์‹ ์„ ํ—ˆ์šฉ
        UrlBasedCorsConfigurationSource source  = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
       return source;

    }
}

Password ํ•„๋“œ์ถ”๊ฐ€

  1. MemberDto.Post

    1
    2
    
      @NotBlank
             private String password;
    
  2. Member -password, Authorize ์ถ”๊ฐ€

    1
    2
    3
    4
    5
    
     @Column(length = 100, nullable = false)
         private String password;
    
     @ElementCollection(fetch = FetchType.EAGER)
         private List<String>role = new ArrayList<>();
    
  3. MemberService

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
     @Transactional
     @Service
     public class MemberService {
         private final MemberRepository memberRepository;
         private final ApplicationEventPublisher publisher;
            
         // ์ถ”๊ฐ€
         private final PasswordEncoder passwordEncoder;
         private final JwtAuthorityUtils jwtAuthorityUtils;
            
         public MemberService(MemberRepository memberRepository,
                              ApplicationEventPublisher publisher, PasswordEncoder passwordEncoder, JwtAuthorityUtils jwtAuthorityUtils) {
             this.memberRepository = memberRepository;
             this.publisher = publisher;
              
           //์ถ”๊ฐ€
             this.passwordEncoder = passwordEncoder;
             this.jwtAuthorityUtils = jwtAuthorityUtils;
         }
        
     public Member createMember(Member member) {
             verifyExistsEmail(member.getEmail());
        
             //ํŒจ์Šค์›Œ๋“œ ์•”ํ˜ธํ™” ํ•„์š” - ๋‹จ๋ฐฉํ–ฅ ์•”ํ˜ธ
             String encrypedPassword =  passwordEncoder.encode(member.getPassword());
             member.setPhone(encrypedPassword);
        
             //DB์— UserRoles ์ €์žฅ
             List<String> roles = jwtAuthorityUtils.createRoles(member.getEmail());
             member.setRoles(roles);
        
             Member savedMember = memberRepository.save(member);
        
             
             publisher.publishEvent(new MemberRegistrationApplicationEvent(this, savedMember));
             return savedMember;
         }
    

๋ฉฑ๋“ฑ์„ฑ์„ ๊ฐ€์ง€๋Š” ๊ฒƒ. (์„œ๋ฒ„์— ๋™์ผํ•œ ์š”์ฒญ์„ ํ•˜๋Š”์ง€๋ฅผ ๋ด์•ผ ํ•จ.= GET,POST,DELETE

  1. JwtAuthorityUtils ํด๋ž˜์Šค ์ƒ์„ฑ

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
     package com.springboot.auth.utils;
        
     import org.springframework.beans.factory.annotation.Value;
     import org.springframework.security.core.authority.AuthorityUtils;
     import org.springframework.security.core.GrantedAuthority;
     import org.springframework.security.core.authority.SimpleGrantedAuthority;
     import org.springframework.stereotype.Component;
     import java.util.List;
     import java.util.stream.Collectors;
        
     @Component
     public class JwtAuthorityUtils {
         @Value("${mail.address.admin}")
         private String adminMailAddress;
        
         private final List<GrantedAuthority> ADMIN_ROLES
                 = AuthorityUtils.createAuthorityList("ROLE_ADMIN","ROLEUSER");
        
         private final List<GrantedAuthority>USER_ROLES
                 = AuthorityUtils.createAuthorityList("ROLE_USER");
         private final List<String> ADMIN_ROLES_STRING = List.of("ADMIN","USER");
         private final List<String> USER_ROLES_STRING = List.of("USER");
        
         public List<GrantedAuthority> createAutrorities(String email){
             if(email.equals(adminMailAddress)){
                 return ADMIN_ROLES;
             }
             return USER_ROLES;
         }
        
         public List<GrantedAuthority>createAuthority(List<String>roles){
             return roles.stream()
                     .map(role-> new SimpleGrantedAuthority("ROLE_"+role))
                     .collect(Collectors.toList());
         }
         public List<String>createRoles(String email){
             if(email.equals(adminMailAddress)){
                 return ADMIN_ROLES_STRING;
             }
             return USER_ROLES_STRING;
         }
     }
        
    

๋กœ๊ทธ์ธ ์ธ์ฆ

์‚ฌ์šฉ์ž์˜ Username(์ด๋ฉ”์ผ ์ฃผ์†Œ)๊ณผ Password๋กœ ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ•˜๋ฉด ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ JWT๋ฅผ ์ƒ์„ฑ ๋ฐ ๋ฐœ๊ธ‰ํ•˜๊ธฐ

์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณต ํ›„, JWT๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋˜๋Š” ๊ณผ์ •

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„ ์ธก์— ๋กœ๊ทธ์ธ ์ธ์ฆ ์š”์ฒญ(Username/Password๋ฅผ ์„œ๋ฒ„ ์ธก์— ์ „์†ก)
  2. ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ๋‹ด๋‹นํ•˜๋Š” Security Filter(JwtAuthenticationFilter)๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด ์ˆ˜์‹ 
  3. Security Filter๊ฐ€ ์ˆ˜์‹ ํ•œ ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด๋ฅผ AuthenticationManager์—๊ฒŒ ์ „๋‹ฌํ•ด ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ์œ„์ž„
  4. AuthenticationManager๊ฐ€ Custom UserDetailsService(MemberDetailsService)์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ UserDetails ์กฐํšŒ๋ฅผ ์œ„์ž„
  5. Custom UserDetailsService(MemberDetailsService)๊ฐ€ ์‚ฌ์šฉ์ž์˜ ํฌ๋ฆฌ๋ด์…œ์„ DB์—์„œ ์กฐํšŒํ•œ ํ›„, AuthenticationManager์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ UserDetails๋ฅผ ์ „๋‹ฌ
  6. AuthenticationManager๊ฐ€ ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด์™€ UserDetails์˜ ์ •๋ณด๋ฅผ ๋น„๊ตํ•ด ์ธ์ฆ ์ฒ˜๋ฆฌ
  7. JWT ์ƒ์„ฑ ํ›„, ํด๋ผ์ด์–ธํŠธ์˜ ์‘๋‹ต์œผ๋กœ ์ „๋‹ฌ

4๋ฒˆ๊ณผ 6๋ฒˆ์€ Spring Security์˜ AuthenticationManager๊ฐ€ ๋Œ€์‹  ์ฒ˜๋ฆฌํ•ด์คŒ

1๏ธโƒฃ Custom UserDetailsService ๊ตฌํ˜„

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.springboot.auth.userDetails;

import com.springboot.auth.utils.JwtAuthorityUtils;
import com.springboot.exception.BusinessLogicException;
import com.springboot.exception.ExceptionCode;
import com.springboot.member.entity.Member;
import com.springboot.member.repository.MemberRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Collection;
import java.util.Optional;

public class MemberDetailsService implements UserDetailsService {
    private final MemberRepository memberRepository;
    private final JwtAuthorityUtils authorityUtils;

    public MemberDetailsService(MemberRepository memberRepository, JwtAuthorityUtils authorityUtils) {
        this.memberRepository = memberRepository;
        this.authorityUtils = authorityUtils;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //DB์—์„œ ์กฐํšŒ
        Optional<Member> optionalMember = memberRepository.findByEmail(username);
        Member findMember = optionalMember.orElseThrow(()-> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
        //Member๋Š” DB์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์ด๋ฏ€๋กœ - ์•”ํ˜ธํ™” ๋˜์–ด์žˆ๋‹ค.
        return new MemberDetails(findMember);
    }

    //Member๋ฅผ ํ™•์žฅ
    private final class MemberDetails extends Member implements UserDetails{
        //MemberDetails๋Š” ์ƒ์„ฑ์ž -> ์ด๊ฑด ๋ฐ”๋กœ ์œ„์˜ ๋ฐ˜ํ™˜๋œ ๊ฐ’ 
        // = DB์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ.  = ์•”ํ˜ธํ™” ๋˜์–ด์žˆ์Œ.
        
        MemberDetails(Member member){
            setMemberId(member.getMemberId());
            setEmail(member.getEmail());
            setPassword(member.getPassword());
            setRoles(member.getRoles());
        }

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorityUtils.createAuthority(this.getRoles());
        }

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

        @Override
        public boolean isAccountNonExpired() {
            return true;  //false์—์„œ ์ˆ˜์ •
        }

        @Override
        public boolean isAccountNonLocked() {
            return true; //false์—์„œ ์ˆ˜์ •
        }

        @Override
        public boolean isCredentialsNonExpired() {
            return true; //false์—์„œ ์ˆ˜์ •
        }

        @Override
        public boolean isEnabled() {
            return true; //false์—์„œ ์ˆ˜์ •
        }
    }
}

2๏ธโƒฃ ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด ์—ญ์ง๋ ฌํ™”(Deserialization)๋ฅผ ์œ„ํ•œ LoginDTO ํด๋ž˜์Šค ์ƒ์„ฑ

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „์†กํ•œ Username/Password ์ •๋ณด๋ฅผ Security Filter์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ญ์ง๋ ฌํ™”(Deserialization) ํ•˜๊ธฐ ์œ„ํ•œ DTO ํด๋ž˜์Šค์ด๋‹ค.

1
2
3
4
5
6
7
8
9
10
package com.springboot.auth.dto;

import lombok.Getter;

@Getter //dto์—๋Š” Getter ํ•„์ˆ˜.
public class LoginDto {
    private String username;
    private String password;
}

3๏ธโƒฃ JWT๋ฅผ ์ƒ์„ฑํ•˜๋Š” JwtTokenizer ๊ตฌํ˜„

  1. JwtTokenizer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
     package com.springboot.auth.jwt;
        
     import lombok.Getter;
     import org.springframework.beans.factory.annotation.Value;
        
     public class JwtTokenizer {
         @Getter
         @Value("${jwt.key}") //yml์— ์ €์žฅ ํ•„์ˆ˜
         private String secretKey;
         @Getter
         @Value("${jwt.access-token-expiration-minute}") //yml์— ์ €์žฅ ํ•„์ˆ˜
         private int accessTokenExpirationMinutes;
         @Getter
         @Value("${jwt.refresh-token-expiration-minute}") //yml์— ์ €์žฅ ํ•„์ˆ˜
         private int refreshTokenExpirationMinutes;
        
     }
        
    
  2. application.yml
    • ${JWT_SECRET_KEY}๋Š” ๋‹จ์ˆœํ•œ ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๋ผ OS์˜ ์‹œ์Šคํ…œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ์ฝ์–ด์˜ค๋Š” ์ผ์ข…์˜ ํ‘œํ˜„์‹

  3. ์‹œ์Šคํ…œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ํŽธ์ง‘ (์‹œ์Šคํ…œํ™˜๊ฒฝ๋ณ€์ˆ˜ํŽธ์ง‘- ํ™˜๊ฒฝ๋ณ€์ˆ˜ - ์‹œ์Šคํ…œ๋ณ€์ˆ˜ ์ถ”๊ฐ€)
    • JWT์˜ ์„œ๋ช…์— ์‚ฌ์šฉ๋˜๋Š” Secret Key ์ •๋ณด๋Š” ๋ฏผ๊ฐํ•œ(sensitive) ์ •๋ณด์ด๋ฏ€๋กœ ์‹œ์Šคํ…œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜์˜ ๋ณ€์ˆ˜๋กœ ๋“ฑ๋ก

4๏ธโƒฃ ๋กœ๊ทธ์ธ ์ธ์ฆ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” Custom Security Filter ๊ตฌํ˜„

JwtAuthenticationFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//UsernamePasswordAuthenticationfilter๋ฅผ ๊ต์ฒดํ•˜๋Š” ๊ฒƒ.
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    //ํผ ๋กœ๊ทธ์ธ ๋ฐฉ์‹์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋””ํดํŠธ Security Filter๋กœ์จ,
    //ํผ ๋กœ๊ทธ์ธ์ด ์•„๋‹ˆ๋”๋ผ๋„ Username/Password ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด
    // UsernamePasswordAuthenticationFilter๋ฅผ ํ™•์žฅํ•ด์„œ ๊ตฌํ˜„

    private final AuthenticationManager authenticationManager;
    // ๋กœ๊ทธ์ธ ์ธ์ฆ ์ •๋ณด(Username/Password)๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ UserDetailsService์™€ ์ธํ„ฐ๋ž™์…˜ ํ•œ ๋’ค ์ธ์ฆ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จ
    private final JwtTokenizer jwtTokenizer;
    //ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ์ฆ์— ์„ฑ๊ณตํ•  ๊ฒฝ์šฐ, JWT๋ฅผ ์ƒ์„ฑ ๋ฐ ๋ฐœ๊ธ‰

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenizer jwtTokenizer) {
        this.authenticationManager = authenticationManager;
        this.jwtTokenizer = jwtTokenizer;
    }

    @SneakyThrows //try-catch๋ฅผ ๋Œ€์ฒด
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
        //ํด๋ผ์ด์–ธํŠธ์—์„œ ์ „์†กํ•œ Username๊ณผ Password๋ฅผ DTO ํด๋ž˜์Šค๋กœ ์—ญ์ง๋ ฌํ™”(Deserialization) ํ•˜๊ธฐ ์œ„ํ•ด ObjectMapper ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
        ObjectMapper objectMapper = new ObjectMapper();
        LoginDto loginDto = objectMapper.readValue(request.getInputStream(), LoginDto.class);
        //ServletInputStream์„ LoginDto ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋กœ ์—ญ์ง๋ ฌํ™”ํ•œ๋‹ค.

        // token ๋งŒ๋“œ๋Š” ์ด์œ  UsernamePasswordAuthenticationToken ์„ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ, ๊ตฌํ˜„์ฒด๋„ ์‹ค์ œ๋กœ ๋งŒ๋“ค์–ด์ ธ์žˆ์Œ
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());

        return authenticationManager.authenticate(authenticationToken);

    }

    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authentication) throws ServletException, IOException {
        Member member = (Member) authentication.getPrincipal();

        //token ๋งŒ๋“ค๊ธฐ
        String accessToken= delegateAceessToken(member);
        String refreshToken = delegateRefreshToken(member);
        response.setHeader("Authorization", "Bearer " + accessToken);
        response.setHeader("Refresh", refreshToken);
        this.getSuccessHandler().onAuthenticationSuccess(request,response,authentication);
    }

    protected String delegateAceessToken(Member member){
        Map<String,Object> claims = new HashMap<>();
        claims.put("username",member.getEmail());
        claims.put("roles", member.getRoles());
                //token์•ˆ์— ์ ˆ๋Œ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋„ฃ์ง€์•Š์Œ, playload ๋””์ฝ”๋”ฉ์€ ์—„์ฒญ์‰ฝ๋‹ค.
        String subject = member.getEmail();
        Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getAccessTokenExpirationMinutes());
        String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
        String accessToken = jwtTokenizer.generateAccessToken(claims, subject, expiration, base64EncodedSecretKey);
        return accessToken;

        //๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์ ˆ๋Œ€ ํ‰๋ฌธํ™” ํ•˜๋ฉด ์•ˆ๋จ.
        //์Šคํ”„๋ง์—์„œ ์ธ์ฆ์ธ๊ฐ€๋Š” ๋ฌด์กฐ๊ฑด security์‚ฌ์šฉ
    }

    protected String delegateRefreshToken(Member member) {

        String subject = member.getEmail();
        Date expiration = jwtTokenizer.getTokenExpiration(jwtTokenizer.getRefreshTokenExpirationMinutes());
        String base64EncodedSecretKey = jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
        String refreshToken = jwtTokenizer.generateRefreshToken(subject, expiration, base64EncodedSecretKey);
        return refreshToken;
    }
    //์‹คํŒจํ–ˆ์„๋• ์ž๋™์œผ๋กœ ๋“ฑ๋ก ๋œ๋‹ค.
    //์„ฑ๊ณตํ–ˆ์„๋•Œ๋Š” ์„ฑ๊ณตํ•˜๊ณ  ๋‚˜์„œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ช…์‹์ ์œผ๋กœ ๋„ฃ์–ด์ฃผ์–ด์•ผ

}

5๏ธโƒฃ Custom Filter ์ถ”๊ฐ€๋ฅผ ์œ„ํ•œ SecurityConfiguration ์„ค์ • ์ถ”๊ฐ€

SecurityConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Configuration
public class SecurityConfiguration {
    private final JwtTokenizer jwtTokenizer;

    public SecurityConfiguration(JwtTokenizer jwtTokenizer) {
        this.jwtTokenizer = jwtTokenizer;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        http
                .headers().frameOptions().sameOrigin()
                .and()
                .csrf().disable() //๋ฐฐํฌํ•  ๋•Œ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋จ.
                .cors(withDefaults())
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin().disable() //ํผ๋กœ๊ทธ์ธ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŸ๋‹ค.
                .httpBasic().disable()
                .apply(new CustomFilterConfigurer())
                .and()
	              .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
         
        return http.build();

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    //Cors์„ค์ • ์ถ”๊ฐ€, corsConfigurationSource๋ฅผ ๋นˆ์œผ๋กœ ๋™๋ก
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
        UrlBasedCorsConfigurationSource source  = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
       return source;
    }

    public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
            //AbstractHttpConfigurer๋ฅผ ์ƒ์†ํ•ด์„œ Custom Configurer๋ฅผ ๊ตฌํ˜„
            //AbstractHttpConfigurer๋ฅผ ์ƒ์†ํ•˜๋Š” ํƒ€์ž…๊ณผ HttpSecurityBuilder๋ฅผ ์ƒ์†ํ•˜๋Š” ํƒ€์ž…์„ ์ œ๋„ˆ๋ฆญ ํƒ€์ž…์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
        @Override  //configure() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์„œ Configuration์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•
        public void configure(HttpSecurity builder){
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
            //getSharedObject()๋ฅผ ํ†ตํ•ด์„œ Spring Security์˜ ์„ค์ •์„ ๊ตฌ์„ฑํ•˜๋Š” SecurityConfigurer ๊ฐ„์— ๊ณต์œ ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

            JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
            //  JwtAuthenticationFilter์—์„œ ์‚ฌ์šฉ๋˜๋Š” AuthenticationManager์™€ JwtTokenizer๋ฅผ DIํ•ด์ค€๋‹ค.

            jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์—†์„๋•Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์ ์šฉ๋จ ("/login)

            builder.addFilter(jwtAuthenticationFilter);
            // addFilter() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด JwtAuthenticationFilter๋ฅผ Spring Security Filter Chain์— ์ถ”๊ฐ€
        }
    }
}

์˜จ์ „ํžˆ ์ดํ•ดํ•˜๊ธฐ ํž˜๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ ‡๊ฒŒ ์“ฐ๋Š” ๊ตฌ๋‚˜ ํ•˜๊ณ  ์™ธ์šฐ๊ธฐโ€ฆ.?

๋กœ๊ทธ์ธ ์‹œ๋„

  1. ํšŒ์›๊ฐ€์ž…

  2. ๋กœ๊ทธ์ธ

  3. ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž˜๋ชป ์ž…๋ ฅ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒ

    HttpStatus.UNAUTHORIZED(401) ์ƒํƒœ ์ฝ”๋“œ๋Š” ์ธ์ฆ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋Š” HTTP status

    โ†’ ์ถ”๊ฐ€ ๊ฐœ์„  ํ•„์š”

๋กœ๊ทธ์ธ ์ธ์ฆ ์„ฑ๊ณต ๋ฐ ์‹คํŒจ์— ๋”ฐ๋ฅธ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ

1๏ธโƒฃ AuthenticationSuccessHandler ๊ตฌํ˜„

1
2
3
4
5
6
7
8
@Slf4j
public class MemberAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
        log.info("# Authenticated successfully!");
    }
}

2๏ธโƒฃ AuthenticationFailureHandler ๊ตฌํ˜„

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//component ์—†๋Š”๊ฑด ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žก์•„์ฃผ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—,
//์ธ์ฆ์„ ๋‹ด๋‹นํ•˜๋Š” JwtAuthenticationFilter์— ์ด ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•จ.
@Slf4j
public class MemberAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //๋ณดํ†ต ๋กœ๊น… ๊ธฐ๋Šฅ ํฌํ•จ
        log.info("Authenticated failed");
        log.error("Authenticated failed", exception.getMessage());
        sendErrorResponse(response);

    }

    private void sendErrorResponse(HttpServletResponse response) throws IOException {
        Gson gson = new Gson();
        ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.UNAUTHORIZED);
        //HttpStatus.UNAUTHORIZED ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ „๋‹ฌ
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        // Content Type์ด โ€œapplication/jsonโ€์ด๋ผ๋Š” ๊ฒƒ์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋„๋ก MediaType.APPLICATION_JSON_VALUE๋ฅผ HTTP Header์— ์ถ”๊ฐ€
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        // response์˜ status๊ฐ€ 401 ์ž„์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋„๋ก HttpStatus.UNAUTHORIZED.value()์„ HTTP Header์— ์ถ”๊ฐ€
        
        response.getWriter().write(gson.toJson(errorResponse,ErrorResponse.class));
        //Gson์„ ์ด์šฉํ•ด ErrorResponse ๊ฐ์ฒด๋ฅผ JSON ํฌ๋งท ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ ํ›„, 
        //์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑ
    }
}

3๏ธโƒฃ AuthenticationSuccessHandler์™€ AuthenticationFailureHandler ์ถ”๊ฐ€

AuthenticationFailureHandler ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ JwtAuthenticationFilter์— ๋“ฑ๋กํ•˜๋ฉด ๋กœ๊ทธ์ธ ์ธ์ฆ ์‹œ, ๋‘ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  @Configuration
public class SecurityConfiguration {
    private final JwtTokenizer jwtTokenizer;

    public SecurityConfiguration(JwtTokenizer jwtTokenizer) {
        this.jwtTokenizer = jwtTokenizer;
    }
    
    ...
  
  public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
        @Override
        public void configure(HttpSecurity builder) throws Exception {
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);

            JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
            jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login");
            jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());  // (3) ์ถ”๊ฐ€
            jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());  // (4) ์ถ”๊ฐ€
            builder.addFilter(jwtAuthenticationFilter);
        }
    }
}

AuthenticationSuccessHandler์™€ AuthenticationFailureHandler ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๊ฐ€ ๋‹ค๋ฅธ Security Filter์—์„œ ์‚ฌ์šฉ์ด ๋œ๋‹ค๋ฉด ApplicationContext์— Bean์œผ๋กœ ๋“ฑ๋กํ•ด์„œ DI ๋ฐ›๋Š” ๊ฒŒ ๋งž๋‹ค.

๐Ÿ’ก ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ์ธ์ฆ์„ ์œ„ํ•œ Security Filter๋งˆ๋‹ค AuthenticationSuccessHandler์™€ AuthenticationFailureHandler์˜ ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ ๊ฐ๊ฐ ์ƒ์„ฑํ•  ๊ฒƒ์ด๋ฏ€๋กœ new ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด๋„ ๋ฌด๋ฐฉ

4๏ธโƒฃ AuthenticationSuccessHandler ํ˜ธ์ถœ

JwtAuthenticationFilter(AuthenticationSuccessHandler ํ˜ธ์ถœ ์ฝ”๋“œ ์ถ”๊ฐ€)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    ...
    ...

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws ServletException, IOException {
        Member member = (Member) authResult.getPrincipal();

        String accessToken = delegateAccessToken(member);
        String refreshToken = delegateRefreshToken(member);

        response.setHeader("Authorization", "Bearer " + accessToken);
        response.setHeader("Refresh", refreshToken);

        this.getSuccessHandler().onAuthenticationSuccess(request, response, authResult);  // (1) ์ถ”๊ฐ€
    }

    ...
    ...
}

์ž๊ฒฉ์ฆ๋ช… ๋ฐ ๊ฒ€์ฆ ๊ตฌํ˜„

ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ JWT๋ฅผ ์ด์šฉํ•ด ์ž๊ฒฉ ์ฆ๋ช…์ด ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ request ์ „์†ก ์‹œ, request header๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ JWT๋ฅผ ์„œ๋ฒ„ ์ธก์—์„œ ๊ฒ€์ฆํ•˜๋Š” ๊ธฐ๋Šฅ

1๏ธโƒฃ JWT ๊ฒ€์ฆ ํ•„ํ„ฐ ๊ตฌํ˜„

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.springboot.auth.filter;

import com.springboot.auth.jwt.JwtTokenizer;
import com.springboot.auth.utils.JwtAuthorityUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;

// OncePerRequestFilter๋ฅผ ํ™•์žฅํ•ด์„œ request ๋‹น ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋˜๋Š” Security Filter๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.
//๊ตฌํ˜„์ฒด๋ผ์„œ ์ƒ์†๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•  ๊ฒƒ. -ํ† ํฐ๊ณผ ๊ด€๋ จ๋œ ์„œ๋น„์Šค, ๊ถŒํ•œ์„ ๊ฐ€์ ธ์™€์•ผ ํ•จ
public class JwtVerifiedFilter extends OncePerRequestFilter {
    private final JwtTokenizer jwtTokenizer;
    private final JwtAuthorityUtils authorityUtils;

    public JwtVerifiedFilter(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
        this.jwtTokenizer = jwtTokenizer;
        this.authorityUtils = authorityUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        Map<String,Object> claims = verifyJws(request);
          //SecurityContext์•ˆ์— ์ €์žฅํ•ด์ฃผ์–ด์•ผ ํ•จ.
        setAuthenticationToContext(claims);
	        // Authentication ๊ฐ์ฒด๋ฅผ SecurityContext์— ์ €์žฅ
        filterChain.doFilter(request,response);
	       // ๋‹ค์Œ(Next) Security Filter๋ฅผ ํ˜ธ์ถœ
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request){
    // ํŠน์ • ์กฐ๊ฑด์— ๋ถ€ํ•ฉํ•˜๋ฉด(true์ด๋ฉด) ํ•ด๋‹น Filter์˜ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  
    // ๋‹ค์Œ Filter๋กœ ๊ฑด๋„ˆ ๋›ฐ๋„๋กœ ใ„ฑํ•ด์ค€๋‹ค. 
        String authorization = request.getHeader("Athorization");
	        //Authorization header์˜ ๊ฐ’์„ ์–ป์€ ํ›„
        return authorization == null || !authorization.startsWith("Bearer");
	        //Authorization header์˜ ๊ฐ’์ด null์ด๊ฑฐ๋‚˜ 
	        //Authorization header์˜ ๊ฐ’์ด โ€œBearerโ€๋กœ ์‹œ์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ํ•ด๋‹น Filter์˜ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋„๋ก ์ •์˜
			    // JWT๊ฐ€ Authorization header์— ํฌํ•จ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด 
			    // JWT ์ž๊ฒฉ์ฆ๋ช…์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์š”์ฒญ์ด๋ผ๊ณ  ํŒ๋‹จํ•˜๊ณ  
			    // ๋‹ค์Œ(Next) Filter๋กœ ์ฒ˜๋ฆฌ๋ฅผ ๋„˜๊ธฐ๋Š” ๊ฒƒ
    
    }

    private Map<String, Object> verifyJws(HttpServletRequest request){
        String jws = request.getHeader("Authorization").replace("Bearer","");
        
        String bas64EncodedSecretKey =jwtTokenizer.encodeBase64SecretKey(jwtTokenizer.getSecretKey());
				// JWT ์„œ๋ช…(Signature)์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ Secret Key๋ฅผ ์–ป๋Š”๋‹ค.
        Map<String,Object>claims = jwtTokenizer.getClaims(jws,bas64EncodedSecretKey).getBody();
        // JWT์—์„œ Claims๋ฅผ ํŒŒ์‹ฑ -> getClaims ์•ˆ์—์„œ ๊ฒ€์ฆ๊นŒ์ง€ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฒƒ.
        return claims;
    }

    private void setAuthenticationToContext(Map<String,Object>claims){
        String username = (String) claims.get("username");
        List<GrantedAuthority> authorities = authorityUtils.createAuthorities((List)claims.get("roles"));
        Authentication authentication = new UsernamePasswordAuthenticationToken(username,null,authorities);
                //password๋Š” ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.์ค‘์š”!!
        SecurityContextHolder.getContext().setAuthentication(authentication);

    }
}
  • shouldNotFilter()
    • JWT๊ฐ€ Authorization header์— ํฌํ•จ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด JWT ์ž๊ฒฉ์ฆ๋ช…์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์š”์ฒญ์ด๋ผ๊ณ  ํŒ๋‹จํ•˜๊ณ  ๋‹ค์Œ(Next) Filter๋กœ ์ฒ˜๋ฆฌ๋ฅผ ๋„˜๊ธฐ๋Š” ๊ฒƒ
    • ๋งŒ์ผ JWT ์ž๊ฒฉ ์ฆ๋ช…์ด ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ์š”์ฒญ์ธ๋ฐ ์‹ค์ˆ˜๋กœ JWT๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์•˜๋‹ค ํ•˜๋”๋ผ๋„ ์ด ๊ฒฝ์šฐ์—๋Š” Authentication์ด ์ •์ƒ์ ์œผ๋กœ SecurityContext์— ์ €์žฅ๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ Security Filter๋ฅผ ๊ฑฐ์ณ ๊ฒฐ๊ตญ Exception์„ ๋˜์ง€๊ฒŒ ๋  ๊ฒƒ

2๏ธโƒฃ SecurityConfiguration ์„ค์ • ์—…๋ฐ์ดํŠธ

  • ์„ธ์…˜ ์ •์ฑ… ์„ค์ • ์ถ”๊ฐ€
  • JwtVerificationFilter ์ถ”๊ฐ€

SecurityConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Configuration
public class SecurityConfiguration {
    private final JwtTokenizer jwtTokenizer;
    private final JwtAuthorityUtils authorityUtils;

    public SecurityConfiguration(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
        this.jwtTokenizer = jwtTokenizer;
        this.authorityUtils = authorityUtils;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        http
                .headers().frameOptions().sameOrigin()
                .and()
                .csrf().disable() //๋ฐฐํฌํ•  ๋•Œ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋จ.
                .cors(withDefaults())
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
	               // ์„ธ์…˜์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๋„๋ก ์„ค
                .and()
                .formLogin().disable() //ํผ๋กœ๊ทธ์ธ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŸ๋‹ค.
                .httpBasic().disable()
                .apply(new CustomFilterConfigurer())
                .and()
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll());
                //์ธ์ฆ์ธ๊ฐ€ ํ›„ ๋ณ€๊ฒฝ์˜ˆ์ •

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    //Cors์„ค์ • ์ถ”๊ฐ€, corsConfigurationSource๋ฅผ ๋นˆ์œผ๋กœ ๋™๋ก
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
        UrlBasedCorsConfigurationSource source  = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
       return source;
    }

    public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
        @Override
        public void configure(HttpSecurity builder){
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);

            JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
            jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์—†์„๋•Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์ ์šฉ๋จ ("/login)
            jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
            jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());

            JwtVerifiedFilter jwtVerifiedFilter = new JwtVerifiedFilter(jwtTokenizer,authorityUtils);
            //JwtVerificationFilter์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ 
            //JwtVerificationFilter์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด๋“ค์„ ์ƒ์„ฑ์ž๋กœ DI๋ฅผ ํ•ด์ค€๋‹ค.

            builder.addFilter(jwtAuthenticationFilter)
                    .addFilterAfter(jwtVerifiedFilter,JwtVerifiedFilter.class);
                    //jwtAuthenticationFilter์—์„œ ๋กœ๊ทธ์ธ ์ธ์ฆ์— ์„ฑ๊ณตํ•œ ํ›„ ๋ฐœ๊ธ‰๋ฐ›์€ JWT๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ request header(Authorization ํ—ค๋”)์— ํฌํ•จ๋˜์–ด ์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ ๋™์ž‘
        }
    }
}

  • SessionCreationPolicy์˜ ์„ค์ • ๊ฐ’
    • SessionCreationPolicy.ALWAYS : ํ•ญ์ƒ ์„ธ์…˜์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • SessionCreationPolicy.NEVER : ์„ธ์…˜์„ ์ƒ์„ฑํ•˜์ง€ ์•Š์ง€๋งŒ ๋งŒ์•ฝ์— ์ด๋ฏธ ์ƒ์„ฑ๋œ ์„ธ์…˜์ด ์žˆ๋‹ค๋ฉด ์‚ฌ์šฉํ•œ๋‹ค.
    • SessionCreationPolicy.IF_REQUIRED :ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์„ธ์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.
    • SessionCreationPolicy.STATELESS : ์„ธ์…˜์„ ์ƒ์„ฑํ•˜์ง€ ์•Š์œผ๋ฉฐ, SecurityContext ์ •๋ณด๋ฅผ ์–ป๊ธฐ ์œ„ํ•ด ๊ฒฐ์ฝ” ์„ธ์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

์„œ๋ฒ„ ์ธก ๋ฆฌ์†Œ์Šค์— ์—ญํ• (Role) ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ์ ์šฉ

SecurityConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.springboot.config;
...

@Configuration
public class SecurityConfiguration {
    private final JwtTokenizer jwtTokenizer;
    private final JwtAuthorityUtils authorityUtils;

    public SecurityConfiguration(JwtTokenizer jwtTokenizer, JwtAuthorityUtils authorityUtils) {
        this.jwtTokenizer = jwtTokenizer;
        this.authorityUtils = authorityUtils;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
        http
                .headers().frameOptions().sameOrigin()
                .and()
                .csrf().disable() //๋ฐฐํฌํ•  ๋•Œ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋จ.
                .cors(withDefaults())
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin().disable() //ํผ๋กœ๊ทธ์ธ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŸ๋‹ค.
                .httpBasic().disable()
                .apply(new CustomFilterConfigurer())
                .and()
              //  .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
                //  ์ธ์ฆ์ธ๊ฐ€ ๊ตฌํ˜„ ํ›„ ์ถ”๊ฐ€
                .authorizeHttpRequests(authorize -> authorize
                        .antMatchers(HttpMethod.POST, "/*/members").permitAll() //post์— ํ•ด๋‹นํ•œ๋‹ค๋ฉด ์ ‘๊ทผ ํ—ˆ์šฉ
                        .antMatchers(HttpMethod.PATCH, "/*/members/**").hasRole("USER")
                        .antMatchers(HttpMethod.GET, "/*/members").hasRole("ADMIN")
                        .antMatchers(HttpMethod.GET, "/*/members/**").hasAnyRole("USER", "ADMIN")
                        .antMatchers(HttpMethod.DELETE, "/*/members/**").hasRole("USER")
                        .anyRequest().permitAll()
                );
        return http.build();

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    //Cors์„ค์ • ์ถ”๊ฐ€, corsConfigurationSource๋ฅผ ๋นˆ์œผ๋กœ ๋™๋ก
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
        UrlBasedCorsConfigurationSource source  = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
       return source;
    }

    public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer,HttpSecurity>{
        @Override
        public void configure(HttpSecurity builder){
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);

            JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
            jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login"); //์—†์„๋•Œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ๊ทธ์ธ์ด ์ ์šฉ๋จ ("/login)
            jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
            jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());

            JwtVerifiedFilter jwtVerifiedFilter = new JwtVerifiedFilter(jwtTokenizer,authorityUtils);

            builder.addFilter(jwtAuthenticationFilter)
                    .addFilterAfter(jwtVerifiedFilter,JwtAuthenticationFilter.class);
        }
    }
}

  • .authorizeHttpRequests(authorize -> authorize ~ : ์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌ ์„ค์ •
    • .antMatchers(HttpMethod.POST, โ€œ/*/membersโ€).permitAll() : HttpMethod๊ฐ€ POST์— ํ•ด๋‹นํ•˜๊ณ  URL์ด */members๋กœ ํ•ด๋‹น๋œ๋‹ค๋ฉด ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ ํ—ˆ์šฉ
    • .antMatchers(HttpMethod.PATCH, โ€œ/*/members/**โ€).hasRole(โ€œUSERโ€) : PATCH ์š”์ฒญ ์‹œ members์˜ ํ•˜์œ„ URL์ด ์–ด๋–ค URL์ด ์˜ค๋”๋ผ๋„ USER ๊ถŒํ•œ ๊ฐ€์กŒ์œผ๋ฉด ์ ‘๊ทผ ํ—ˆ์šฉ
    • get ์š”์ฒญ์€ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž(USER)์™€ ๊ด€๋ฆฌ์ž(ADMIN) ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž ๋ชจ๋‘ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅ
    • delete ์š”์ฒญ์€ ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ํƒˆํ‡ด ๊ฐ™์€ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฏ€๋กœ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž(USER) ๊ถŒํ•œ๋งŒ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ—ˆ์šฉ

Spring ์นดํ…Œ๊ณ ๋ฆฌ ๋‚ด ๋‹ค๋ฅธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

Leave a comment