๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ/spring&springboot

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] ์ธ์ฆ๊ณผ ์ธ๊ฐ€(+์ฟ ํ‚ค-์„ธ์…˜, JWT)

by alswlfl 2026. 6. 26.

์ธ์ฆ๊ณผ ์ธ๊ฐ€๋ž€ ๋ฌด์—‡์ผ๊นŒ?

์ธ์ฆ๊ณผ ์ธ๊ฐ€

์ธ์ฆ๊ณผ ์ธ๊ฐ€

์ธ์ฆ(Authentication): ํ•ด๋‹น ์œ ์ €๊ฐ€ ์‹ค์ œ ์œ ์ €์ธ์ง€ ์ธ์ฆํ•˜๋Š” ๊ฐœ๋…

์ธ๊ฐ€(Authorization): ํ•ด๋‹น ์œ ์ €๊ฐ€ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ์ง€ ํ—ˆ๊ฐ€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฐœ๋…์œผ๋กœ, ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€-๊ด€๋ฆฌ์ž ๊ถŒํ•œ ๊ฐ™์€ ๊ฒƒ์ด ์žˆ์Œ

 

์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋ฉด, ๋กœ๊ทธ์ธ์€ ์ธ์ฆ์„ ํ•  ๋•Œ(๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅํ•˜๊ณ  ์ œ์ถœํ•  ๋•Œ)์ด๊ณ , ํšŒ์›/๋น„ํšŒ์› ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๊ถŒํ•œ์„ ๋ฐ›๋Š” ๊ฒƒ์€ ์ธ๊ฐ€์ž„

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์ฆ์˜ ํŠน์ˆ˜์„ฑ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์ฆ

HTTP๋Š” ๋น„์—ฐ๊ฒฐ์„ฑ(Connectionless)์™€ ๋ฌด์ƒํƒœ(Stateless)๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Œ → ์„œ๋ฒ„์˜ ๋น„์šฉ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด

  • ๋น„์—ฐ๊ฒฐ์„ฑ(Connectionless): ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ์ด ๋˜์–ด ์žˆ์ง€ ์•Š์Œ์„ ์˜๋ฏธ
    ์ฆ‰, ์„œ๋ฒ„๋Š” ํ•˜๋‚˜์˜ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ ์‘๋‹ต์„ ๋‚ด๋ณด๋‚ด๊ณ  ์—ฐ๊ฒฐ์„ ๋Š์Œ
  • ๋ฌด์ƒํƒœ(Stateless): ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ์ง€ ์•Š์Œ์„ ์˜๋ฏธ
    ์ฆ‰, ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ง์ „์— ํ˜น์€ ๊ทธ ์ „์— ์–ด๋– ํ•œ ์š”์ฒญ์„ ๋ณด๋ƒˆ๋Š”์ง€ ๊ด€์‹ฌ๋„ ์—†๊ณ  ์ „ํ˜€ ์•Œ ์ˆ˜ ์—†์Œ

HTTP๊ฐ€ ์ด๋Ÿฌํ•œ ํŠน์ง•์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ์šฐ๋ฆฌ๊ฐ€ ์ธํ„ฐ๋„ท์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด์ „ ์ •๋ณด๋“ค์ด ์ž˜ ์žˆ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ญ”๊ฐ€ ์—ฐ์†์„ฑ ์žˆ๊ฒŒ ์‚ฌ์šฉํ•ด์˜ด.

์˜ˆ๋ฅผ ๋“ค์–ด, ์œ ์ € ์ธ์ฆ๋„ ํ•˜๋‚˜์˜ ์ƒํƒœ ๊ฐ’์ธ๋ฐ ๊ทธ ์ •๋ณด๋ฅผ ์–ด๋–ป๊ฒŒ ์œ ์ง€์‹œํ‚ค๊ณ  ์žˆ๋Š” ๊ฒƒ์ธ๊ฐ€?

์ธ์ฆ์˜ ๋ฐฉ์‹

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•จ

 

1. ์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹์˜ ์ธ์ฆ

์ฟ ํ‚ค(Cookie): ํ† ํฐ ์ €์žฅ์†Œ
์„ธ์…˜: ์ธ์ฆ ์ •๋ณด
  • ์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹์€ ์„œ๋ฒ„๊ฐ€ 'ํŠน์ • ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ๋‹ค'๋ผ๋Š” ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹ ์ค‘ ํ•˜๋‚˜
  • ์ธ์ฆ๊ณผ ๊ด€๋ จ๋œ ์•ฝ๊ฐ„์˜ ์ •๋ณด๋ฅผ ์„œ๋ฒ„๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ์Œ. ์ฆ‰, ์œ ์ €์˜ ์ด์ „ ์ƒํƒœ ์ „๋ถ€๋Š” ์•„๋‹ˆ๋”๋ผ๋„ ์ธ์ฆํ•˜๊ณ  ๊ด€๋ จ๋œ ์ตœ์†Œํ•œ์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•ด์„œ ๋กœ๊ทธ์ธ์„ ์œ ์ง€์‹œํ‚ด

์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹ ์ธ์ฆ

1) ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰

2) ์„œ๋ฒ„์—์„œ ํšŒ์› ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋Œ€์กฐํ•ด์„œ ์‚ฌ์šฉ์ž ํ™•์ธํ•จ

3) ์‹ค์ œ ์œ ์ € ํ…Œ์ด๋ธ”์˜ ์ •๋ณด์™€ ์ผ์น˜ํ•œ๋‹ค๋ฉด ์ธ์ฆ์ด ํ†ต๊ณผ๋˜์—ˆ๋‹ค๊ณ  ๋ณด๊ณ , ์„ธ์…˜ ์ €์žฅ์†Œ์— ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋กœ๊ทธ์ธ์ด ๋˜์—ˆ๋‹ค๋ผ๋Š” ์ •๋ณด๋ฅผ ์ €์žฅํ•จ

4) ์„ธ์…˜ ์ €์žฅ์†Œ์—์„œ๋Š” ์œ ์ €์˜ ์ •๋ณด์™€๋Š” ๊ด€๋ จ์—†๋Š” ๋‚œ์ˆ˜์ธ ์„ธ์…˜ ID๋ฅผ ๋ฐœ๊ธ‰

5) ์„œ๋ฒ„๋Š” ๋ฐœ๊ธ‰๋ฐ›์€ ์„ธ์…˜ ID๋ฅผ ์‘๋‹ต์— ๋‹ด์•„์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด๋ƒ„

6) ํด๋ผ์ด์–ธํŠธ(์‚ฌ์šฉ์ž)๋Š” ์„ธ์…˜ ID๋ฅผ ๋ธŒ๋ผ์šฐ์ € ๋‚ด์˜ ์ฟ ํ‚ค(Cookie)๋ผ๋Š” ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€, ์š”์ฒญ์„ ํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ์„ธ์…˜ ID๋ฅผ ๊ฐ™์ด ๋ณด๋ƒ„ → ์ฃผ๋กœ HTTP Header์— ๋‹ด๊ฒจ์„œ ๋ณด๋‚ด์ง

7) ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์—์„œ ์ฟ ํ‚ค ๋ฐœ๊ฒฌํ•˜๋ฉด ์„œ๋ฒ„์˜ ์„ธ์…˜ ์ €์žฅ์†Œ์— ์ €์žฅ๋˜์–ด ์žˆ๋˜ ์ •๋ณด๋ž‘ ๋น„๊ตํ•ด์„œ ๊ฒ€์ฆํ•จ

8) ์œ ์ € ์ •๋ณด๋ฅผ ์ œ๋Œ€๋กœ ๋ฐ›์•„์™”๋‹ค๋ฉด ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์ด ๋œ ์‚ฌ์šฉ์ž๋ผ๊ณ  ์ธ์ฆ์ด ๋˜์–ด, ๊ทธ ์ดํ›„์—๋Š” ๋กœ๊ทธ์ธ์ด ๋œ ์œ ์ €์— ๋”ฐ๋ฅธ ์‘๋‹ต์„ ๋ณด๋‚ด์คŒ

 

2. JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ

JWT(JSON Web Token)์€ JSON ํ˜•์‹์œผ๋กœ ์ƒ๊ธด ์›น์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํ† ํฐ
  • JWT๋Š” ์ธ์ฆ์— ํ•„์š”ํ•œ ์ •๋ณด๋“ค์„ ์•”ํ˜ธํ™”์‹œ์ผœ ๋†“์Œ
  • JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ์€ ์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ JWT๋ฅผ HTTP Header์— ์‹ค์–ด์„œ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Œ

1) ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰

2) ์„œ๋ฒ„์—์„œ ํšŒ์› ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋Œ€์กฐํ•ด์„œ ์‚ฌ์šฉ์ž ํ™•์ธํ•จ

3) ์‹ค์ œ ์œ ์ € ํ…Œ์ด๋ธ”์˜ ์ •๋ณด์™€ ์ผ์น˜ํ•œ๋‹ค๋ฉด ์ธ์ฆ์ด ํ†ต๊ณผ๋˜์—ˆ๋‹ค๊ณ  ๋ณด๊ณ , ์œ ์ €์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ JWT๋กœ ์•”ํ˜ธํ™”ํ•จ

4) ์•”ํ˜ธํ™”๋œ ํ† ํฐ(Access Token)์ธ JWT๋ฅผ ์‘๋‹ต์— ์‹ค์–ด์„œ ๋‚ด๋ณด๋ƒ„(Body ํ˜น์€ Header)

5) ํด๋ผ์ด์–ธํŠธ(์‚ฌ์šฉ์ž)๋Š” ํ† ํฐ์„ ์ฟ ํ‚ค ์ €์žฅ์†Œ ํ˜น์€ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•ด ๋†“์•˜๋‹ค๊ฐ€, ์š”์ฒญ์„ ํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹นํ•˜๋Š” JWT ํ† ํฐ์„ ๊ฐ™์ด ๋ณด๋ƒ„

6) ์„œ๋ฒ„๋Š” ์ž‘์„ฑํ•ด๋†“์€ ๊ฒ€์ฆ ๋กœ์ง์„ ํ†ตํ•ด ํ† ํฐ์„ ๊ฒ€์ฆ ํ›„, ์œ ์ €์— ๋”ฐ๋ฅธ ์‘๋‹ต์„ ๋‚ด๋ณด๋ƒ„

 

๏นก์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹์—์„œ๋Š” ๋ฐœ๊ธ‰ํ•œ ์„ธ์…˜์„ ์ €์žฅํ•  ๊ณต๊ฐ„์ด ํ•„์š”ํ•˜์ง€๋งŒ, JWT๋Š” ๋”ฐ๋กœ ํ† ํฐ์„ ์ €์žฅํ•  ํ•„์š”๊ฐ€ ์—†์Œ

๋”ฐ๋ผ์„œ JWT๊ฐ€ ํ›จ์”ฌ ์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹๋ณด๋‹ค ์„œ๋ฒ„์— ๋ถ€๋‹ด์ด ๋œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ํšจ์œจ์ ์ž„


์ฟ ํ‚ค์™€ ์„ธ์…˜์ด๋ž€ ๋ฌด์—‡์ผ๊นŒ?

์ฟ ํ‚ค์™€ ์„ธ์…˜

์ฟ ํ‚ค์™€ ์„ธ์…˜ ๋ชจ๋‘ HTTP์— ์ƒํƒœ ์ •๋ณด๋ฅผ ์œ ์ง€(Stateful)ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์œผ๋กœ, ์ฟ ํ‚ค์™€ ์„ธ์…˜์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ ๋ณ„๋กœ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€๋ฅผ ํ•  ์ˆ˜ ์žˆ์Œ

 

์ฟ ํ‚ค(Cookie)

ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ๋  ๋ชฉ์ ์œผ๋กœ ์ƒ์„ฑํ•œ ์ž‘์€ ์ •๋ณด๋ฅผ ๋‹ด์€ ํŒŒ์ผ

  • ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ์ค€ > ๊ฐœ๋ฐœ์ž ๋„๊ตฌ > Application > Storage > Cookies์— ๋„๋ฉ”์ธ ๋ณ„๋กœ ์ €์žฅ๋˜์–ด ์žˆ์Œ

ํฌ๋กฌ ๋ธŒ๋ผ์šฐ์ € ๋‚ด ์ฟ ํ‚ค ์ €์žฅ์†Œ

[๊ตฌ์„ฑ ์š”์†Œ]

  • ์ด๋ฆ„(Name): ์ฟ ํ‚ค๋ฅผ ๊ตฌ๋ณ„ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” Key
  • ๊ฐ’(Value): ์ฟ ํ‚ค์˜ ๊ฐ’
  • ๋„๋ฉ”์ธ(Domain): ์ฟ ํ‚ค๊ฐ€ ์ €์žฅ๋œ ๋„๋ฉ”์ธ์œผ๋กœ, ์—ฌ๋Ÿฌ ๊ฒฝ๋กœ์— ๋‚˜๋ˆ ์„œ ์ฟ ํ‚ค๊ฐ€ ์ €์žฅ๋  ์ˆ˜๋„ ์žˆ์Œ
  • ๊ฒฝ๋กœ(Path): 'Domain Local/API' ์ด๋Ÿฐ์‹์œผ๋กœ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ
  • Expires/Max-Age: ๋งŒ๋ฃŒ ๊ธฐํ•œ์œผ๋กœ, ์ฟ ํ‚ค๋ฅผ ๋งŒ๋“ค ๋•Œ ๋งŒ๋ฃŒ ๊ธฐํ•œ์„ ์ค„ ์ˆ˜ ์žˆ์Œ. ๋งŒ๋ฃŒ ๊ธฐํ•œ์ด ์ง€๋‚˜๋ฉด ์‚ญ์ œ๋จ
  • ํฌ๊ธฐ(Size)
  • HttpOnly
  • Secure
  • ๋“ฑ๋“ฑ

์„ธ์…˜(Session)

์„œ๋ฒ„์—์„œ ์ผ์ •์‹œ๊ฐ„ ๋™์•ˆ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ

  • ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ ๋ณ„๋กœ ์œ ์ผ๋ฌด์ดํ•œ '์„ธ์…˜ ID'๋ฅผ ๋ถ€์—ฌํ•œ ํ›„ ํด๋ผ์ด์–ธํŠธ ๋ณ„ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์— ์ €์žฅํ•จ
  • ์„œ๋ฒ„์—์„œ ์ƒ์„ฑํ•œ '์„ธ์…˜ ID'๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ฟ ํ‚ค๊ฐ’('์„ธ์…˜ ์ฟ ํ‚ค'๋ผ๊ณ  ๋ถ€๋ฆ„)์œผ๋กœ ์ €์žฅ๋˜์–ด ํด๋ผ์ด์–ธํŠธ ์‹๋ณ„์— ์‚ฌ์šฉ๋จ

[์„ธ์…˜ ๋™์ž‘ ๋ฐฉ์‹]

์„ธ์…˜ ๋™์ž‘

1) ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์„ ํ•˜๋ฉด, ์„œ๋ฒ„์—์„œ ์„ธ์…˜ ID๋ฅผ ์ƒ์„ฑํ•ด์„œ ์ฟ ํ‚ค์— ๋‹ด์•„์„œ ์‘๋‹ต Header์— ์ „๋‹ฌํ•จ

2) ์„ธ์…˜ ID๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ์ €์žฅ๋จ(Set-Cookie: ์„ธ์…˜ ID)

3) ์ด์–ด์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋‘ ๋ฒˆ์งธ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ํ•ด๋‹นํ•˜๋Š” ์„ธ์…˜ ID ๊ฐ’์„ ๊ฐ™์ด ํฌํ•จํ•ด์„œ ๋ณด๋ƒ„

4) ์„œ๋ฒ„๋Š” ํ•ด๋‹นํ•˜๋Š” ์„ธ์…˜ ID๋ฅผ ํ™•์ธํ•˜๊ณ , ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์š”์ฒญํ•œ HTTP ์š”์ฒญ(=๊ฐ™์€ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ)์ž„์„ ํ™•์ธํ•จ

 

์ฟ ํ‚ค์™€ ์„ธ์…˜ ๋น„๊ต

  ์ฟ ํ‚ค(Cookie) ์„ธ์…˜(Session)
์„ค๋ช… ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ๋  ๋ชฉ์ ์œผ๋กœ ์ƒ์„ฑํ•œ ์ž‘์€ ์ •๋ณด๋ฅผ ๋‹ด์€ ํŒŒ์ผ ์„œ๋ฒ„์—์„œ ์ผ์ •์‹œ๊ฐ„ ๋™์•ˆ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
์ €์žฅ ์œ„์น˜ ํด๋ผ์ด์–ธํŠธ (์›น ๋ธŒ๋ผ์šฐ์ €) ์›น ์„œ๋ฒ„
์‚ฌ์šฉ ์˜ˆ ์‚ฌ์ดํŠธ ํŒ์—…์˜ "์˜ค๋Š˜ ๋‹ค์‹œ๋ณด์ง€ ์•Š๊ธฐ" ์ •๋ณด ์ €์žฅ ๋กœ๊ทธ์ธ ์ •๋ณด ์ €์žฅ
๋งŒ๋ฃŒ ์‹œ์  ์ฟ ํ‚ค ์ €์žฅ ์‹œ ๋งŒ๋ฃŒ์ผ์‹œ ์„ค์ • ๊ฐ€๋Šฅ
(๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ์‹œ๋„ ์œ ์ง€ ๊ฐ€๋Šฅ)
๋‹ค์Œ ์กฐ๊ฑด ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋งŒ์กฑ๋  ๊ฒฝ์šฐ ๋งŒ๋ฃŒ๋จ
- ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ ์‹œ๊นŒ์ง€
- ํด๋ผ์ด์–ธํŠธ ๋กœ๊ทธ์•„์›ƒ ์‹œ๊นŒ์ง€
- ์„œ๋ฒ„์— ์„ค์ •ํ•œ ์œ ์ง€๊ธฐ๊ฐ„๊นŒ์ง€ ํ•ด๋‹น ํด๋ผ์ด์–ธํŠธ์˜ ์žฌ์š”์ฒญ์ด ์—†๋Š” ๊ฒฝ์šฐ
์šฉ๋Ÿ‰ ์ œํ•œ ๋ธŒ๋ผ์šฐ์ € ๋ณ„๋กœ ๋‹ค๋ฆ„(ํฌ๋กฌ ๊ธฐ์ค€)
- ํ•˜๋‚˜์˜ ๋„๋ฉ”์ธ ๋‹น 180๊ฐœ
- ํ•˜๋‚˜์˜ ์ฟ ํ‚ค ๋‹น 4KB(=4096byte)
๊ฐœ์ˆ˜ ์ œํ•œ ์—†์Œ
(๋‹จ, ์„ธ์…˜ ์ €์žฅ์†Œ ํฌ๊ธฐ ์ด์ƒ ์ €์žฅ ๋ถˆ๊ฐ€๋Šฅ)
๋ณด์•ˆ ์ทจ์•ฝ
(ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฟ ํ‚ค ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝ, ์‚ญ์ œ ๋ฐ ๊ฐ€๋กœ์ฑ„๊ธฐ ๋‹นํ•  ์ˆ˜ ์žˆ์Œ)
๋น„๊ต์  ์•ˆ์ „
(์„œ๋ฒ„์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋Œ€์ ์œผ๋กœ ์•ˆ์ „)

 

Spring์—์„œ ์ฟ ํ‚ค-์„ธ์…˜ ๋‹ค๋ค„๋ณด๊ธฐ

์ฟ ํ‚ค ์ƒ์„ฑํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๊ธฐ

package com.sparta.springauth.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

@RestController
@RequestMapping("/api")
public class AuthController {

    public static final String AUTHORIZATION_HEADER = "Authorization";

    // ์ฟ ํ‚ค ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์„œ๋“œ
    @GetMapping("/create-cookie")
    public String createCookie(HttpServletResponse res) {
        addCookie("Robbie Auth", res);

        return "createCookie";
    }

    // ์ฟ ํ‚ค ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ
    @GetMapping("/get-cookie")
    public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
        System.out.println("value = " + value);

        return "getCookie : " + value;
    }

    public static void addCookie(String cookieValue, HttpServletResponse res) {
        try {
            cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20"); // Cookie Value ์—๋Š” ๊ณต๋ฐฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์„œ encoding ์ง„ํ–‰

            // Spring์—์„œ ์ฟ ํ‚ค ํด๋ž˜์Šค ์ œ๊ณตํ•จ
            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue); // Name-Value
            cookie.setPath("/");
            cookie.setMaxAge(30 * 60);

            // Response ๊ฐ์ฒด์— Cookie ์ถ”๊ฐ€
            res.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

์ฟ ํ‚ค ์ €์žฅํ•˜๋Š” ์ฝ”๋“œ ์‹คํ–‰ ํ™”๋ฉด
์ฟ ํ‚ค ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ ์‹คํ–‰

 

์„ธ์…˜ ์ƒ์„ฑํ•˜๊ณ  ๊ฐ€์ ธ์˜ค๊ธฐ

Servlet์—์„œ ์œ ์ผ๋ฌด์ดํ•œ ์„ธ์…˜ ID๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” HttpSession ์ œ๊ณตํ•ด์คŒ

@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
    // ์„ธ์…˜์ด ์กด์žฌํ•  ๊ฒฝ์šฐ ์„ธ์…˜ ๋ฐ˜ํ™˜, ์—†์„ ๊ฒฝ์šฐ ์ƒˆ๋กœ์šด ์„ธ์…˜์„ ์ƒ์„ฑํ•œ ํ›„ ๋ฐ˜ํ™˜
    HttpSession session = req.getSession(true);

    // ์„ธ์…˜์— ์ €์žฅ๋  ์ •๋ณด Name - Value ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
    session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");

    return "createSession";
}

@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
    // ์„ธ์…˜์ด ์กด์žฌํ•  ๊ฒฝ์šฐ ์„ธ์…˜ ๋ฐ˜ํ™˜, ์—†์„ ๊ฒฝ์šฐ null ๋ฐ˜ํ™˜
    HttpSession session = req.getSession(false);

    String value = (String) session.getAttribute(AUTHORIZATION_HEADER); // ๊ฐ€์ ธ์˜จ ์„ธ์…˜์— ์ €์žฅ๋œ Value ๋ฅผ Name ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    System.out.println("value = " + value);

    return "getSession : " + value;
}

์„ธ์…˜ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ ์‹คํ–‰ ํ™”๋ฉด
์„ธ์…˜ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ ์‹คํ–‰ ํ™”๋ฉด


JWT๋ž€ ๋ฌด์—‡์ผ๊นŒ?

JWT(JSON Web Token)๋Š” JSON ํฌ๋งท์„ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์†์„ฑ์„ ์ €์žฅํ•˜๋Š” Claim ๊ธฐ๋ฐ˜์˜ Web Token์œผ๋กœ, ์ฆ‰ ํ† ํฐ์˜ ํ•œ ์ข…๋ฅ˜์ž„

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์ฟ ํ‚ค ์ €์žฅ์†Œ๋ฅผ ์‚ฌ์šฉํ•ด JWT๋ฅผ ์ €์žฅํ•จ

JWT ์‚ฌ์šฉ ์ด์œ 

์„œ๋ฒ„๊ฐ€ 1๋Œ€์ธ ์ƒํ™ฉ๊ณผ 2๋Œ€ ์ด์ƒ์ธ ์ƒํ™ฉ

  • ์„œ๋ฒ„๊ฐ€ 1๋Œ€์ธ ๊ฒฝ์šฐ์—๋Š” Session1์ด ๋ชจ๋“  Client์˜ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์†Œ์œ ํ•˜๊ณ  ์žˆ์Œ
  • ์„œ๋ฒ„๊ฐ€ 2๋Œ€ ์ด์ƒ์ธ ๊ฒฝ์šฐ(์„œ๋ฒ„์˜ ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์„œ๋ฒ„ 2๋Œ€ ์ด์ƒ ์šด์˜์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ),
    • Session ๋งˆ๋‹ค ๋‹ค๋ฅธ Client ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ์Œ
      - Session1: Client1, Client2, Client3
      - Session2: Client4
      - Session3: Client5, Client6
    • ๋กœ๋“œ๋ฐธ๋Ÿฐ์Šค๋Š” ์„œ๋ฒ„์— ๋žœ๋ค์œผ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌํ•จ
    • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋งŒ์•ฝ Client1์˜ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์€ Server2๋‚˜ Server3์— API ์š”์ฒญ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ์˜ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

1) Sticky Session: Client๋งˆ๋‹ค ์š”์ฒญ Server๋ฅผ ๊ณ ์ •์‹œํ‚ด

 

2) ์„ธ์…˜ ์ €์žฅ์†Œ ์ƒ์„ฑํ•ด ๋ชจ๋“  ์„ธ์…˜ ์ €์žฅํ•จ

์„ธ์…˜ ์ €์žฅ์†Œ ์ƒ์„ฑ: Session Storage๊ฐ€ ๋ชจ๋“  Client์˜ ๋กœ๊ทธ์ธ ์ •๋ณด ์†Œ์œ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ์„œ๋ฒ„์—์„œ ๋ชจ๋“  Client์˜ API ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

์„ธ์…˜ ์ €์žฅ์†Œ

 

3) JWT ์‚ฌ์šฉ

๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ Server์— ์ €์žฅํ•˜์ง€ ์•Š๊ณ , Client์— ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ JWT๋กœ ์•”ํ˜ธํ™”ํ•ด ์ €์žฅ ํ›„ JWT ํ†ตํ•ด ์ธ์ฆ/์ธ๊ฐ€

JWT ์‚ฌ์šฉ

  • ๋ชจ๋“  ์„œ๋ฒ„์—์„œ ๋™์ผํ•œ Secret Key ์†Œ์œ ํ•จ
  • Secret Key๋ฅผ ํ†ตํ•œ ์•”ํ˜ธํ™” / ์œ„์กฐ ๊ฒ€์ฆ(๋ณตํ˜ธํ™” ์‹œ)

์•”ํ˜ธํ™”/์œ„์กฐ๊ฒ€์ฆ


JWT ์žฅ/๋‹จ์ 

์žฅ์ 

  • ๋™์‹œ ์ ‘์†์ž๊ฐ€ ๋งŽ์„ ๋•Œ ์„œ๋ฒ„ ์ธก ๋ถ€ํ•˜ ๋‚ฎ์ถค → ์„ธ์…˜ ์ €์žฅ์†Œ์—†์ด ์„œ๋ฒ„ ์ž์ฒด์—์„œ ๊ฒ€์ฆ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—
  • Client, Server๊ฐ€ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•  ๋•Œ ex) ์นด์นด์˜ค OAuth2 ๋กœ๊ทธ์ธ ์‹œ JWT Token ์‚ฌ์šฉ

๋‹จ์ 

  • ๊ตฌํ˜„์˜ ๋ณต์žก๋„ ์ฆ๊ฐ€
  • JWT์— ๋‹ด๋Š” ๋‚ด์šฉ์ด ์ปค์งˆ์ˆ˜๋ก ๋„คํŠธ์›Œํฌ ๋น„์šฉ ์ฆ๊ฐ€ํ•จ(ํด๋ผ์ด์–ธํŠธ → ์„œ๋ฒ„)
    • JWT ์•ˆ์— ๋ฌด๋ถ„๋ณ„ํ•œ ์ •๋ณด๋ฅผ ๋„ฃ๋Š” ๊ฒฝ์šฐ JWT ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ง€๊ณ ,
    • ๊ทธ JWT๊ฐ€ HTTP ํ”„๋กœํ† ์ฝœ์— ๋‹ด๊ฒจ์„œ ๋“ค์–ด์˜ฌ ๋•Œ, HTTP ํ”„๋กœํ† ์ฝœ์ด ๋ฌด๊ฒ๊ณ  ์šฉ๋Ÿ‰์ด ํด์ˆ˜๋ก ๋„คํŠธ์›Œํฌ ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•จ
  • ๊ธฐ ์ƒ์„ฑ๋œ JWT๋ฅผ ์ผ๋ถ€๋งŒ ๋งŒ๋ฃŒ์‹œํ‚ฌ ๋ฐฉ๋ฒ•์ด ์—†์Œ
    • ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ์ €์žฅ๋œ JWT๋ฅผ ์„œ๋ฒ„์—์„œ ๋งŒ๋ฃŒ์‹œํ‚ฌ ๋ฐฉ๋ฒ•์ด ์—†์Œ
    • ๋Œ€์‹ , ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ์ €์žฅํ•  ๋•Œ ์ฟ ํ‚ค ์ž์ฒด ํ˜น์€ JWT ์ž์ฒด์— ๋งŒ๋ฃŒ ๊ธฐํ•œ์„ ์ค„ ์ˆ˜ ์žˆ์Œ
  • Secret Key ์œ ์ถœ ์‹œ JWT ์กฐ์ž‘ ๊ฐ€๋Šฅ
    • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— JWT์— ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋„ฃ์œผ๋ฉด ์•ˆ ๋จ!

JWT ์‚ฌ์šฉ ํ๋ฆ„

1. Client๊ฐ€ username, password๋กœ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ

 

1-1) ์„œ๋ฒ„์—์„œ "๋กœ๊ทธ์ธ ์ •๋ณด" → JWT๋กœ ์•”ํ˜ธํ™”(Secret Key ์‚ฌ์šฉ)

JWT ์˜ˆ์‹œ

1-2) ์„œ๋ฒ„์—์„œ ์ง์ ‘ ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑํ•ด JWT๋ฅผ ๋‹ด์•„ Client ์‘๋‹ต์— ์ „๋‹ฌ

  • JWT ์ „๋‹ฌ ๋ฐฉ๋ฒ•์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ •ํ•จ
// ex) ์‘๋‹ต Header์— ์•„๋ž˜ ํ˜•ํƒœ๋กœ JWT ์ „๋‹ฌ
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token);
cookie.setPath("/");

// Response ๊ฐ์ฒด์— Cookie ์ถ”๊ฐ€
res.addCookie(cookie);

Response ๋‚ด Cookie ์ •๋ณด

1-3) ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ์ž๋™์œผ๋กœ JWT ์ €์žฅ๋จ

 

2. Client์—์„œ JWT ํ†ตํ•ด ์ธ์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•

 

2-1) ์„œ๋ฒ„์—์„œ API ์š”์ฒญ ์‹œ๋งˆ๋‹ค ์ฟ ํ‚ค์— ํฌํ•จ๋œ JWT๋ฅผ ์ฐพ์•„์„œ ์‚ฌ์šฉ

// ์ฟ ํ‚ค ์ฐพ๋Š” ์ฝ”๋“œ
// HttpServletRequest์—์„œ Cookie Value: JWT ๊ฐ€์ ธ์˜ค๊ธฐ
public String getTokenFromRequest(HttpServletRequest req) {
    Cookie[] cookies = req.getCookies();
    if(cookies != null) {
        for(Cookie cookie : cookies) {
            if(cookie.getName().equals(AUTHORIZATION_HEADER)) {
                try {
                    return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode ๋˜์–ด ๋„˜์–ด๊ฐ„ Value ๋‹ค์‹œ Decode
                }catch (UnsupportedEncodingException e){
                    return null;
                }
            }
        }
    }
    return null;
}
  • ์ฟ ํ‚ค์— ๋‹ด๊ธด ์ •๋ณด๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ์ค‘ ์ด๋ฆ„์ด JWT๊ฐ€ ๋‹ด๊ธด ์ฟ ํ‚ค์˜ ์ด๋ฆ„๊ณผ ๋™์ผํ•œ์ง€ ํ™•์ธํ•ด JWT๋ฅผ ๊ฐ€์ ธ์˜ด

2-2) Server์—์„œ ๊ฒ€์ฆ ํ™•์ธ ์ ˆ์ฐจ

  • Client๊ฐ€ ์ „๋‹ฌํ•œ JWT ์œ„์กฐ ์—ฌ๋ถ€ ๊ฒ€์ฆ(Secret Key ์‚ฌ์šฉ)
  • JWT ์œ ํšจ๊ธฐ๊ฐ„์ด ์ง€๋‚˜์ง€ ์•Š์•˜๋Š”์ง€ ๊ฒ€์ฆ
  • ๊ฒ€์ฆ ์„ฑ๊ณต์‹œ, JWT์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ํ™•์ธ

JWT ๊ตฌ์กฐ

  • JWT๋Š” ๋ˆ„๊ตฌ๋‚˜ ํ‰๋ฌธ์œผ๋กœ ๋ณตํ˜ธํ™” ๊ฐ€๋Šฅํ•จ
  • ํ•˜์ง€๋งŒ Secret Key๊ฐ€ ์—†์œผ๋ฉด JWT ์ˆ˜์ • ๋ถˆ๊ฐ€๋Šฅํ•จ(์ฆ‰, JWT๋Š” Read Only ๋ฐ์ดํ„ฐ์ž„)
  • jwt.io

JWT ๊ตฌ์กฐ ์˜ˆ์‹œ

1. Header

  • ์–ด๋–ค ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉํ–ˆ๋Š”์ง€ ex) HS256
  • ํƒ€์ž… ex) JWT

2. Payload

  • ๋ฐ์ดํ„ฐ ์ •๋ณด
  • JSON ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ์Œ

3. JWT Signature Verification

  • Secret Key๋ฅผ ๋„ฃ์Œ

JWT ๋‹ค๋ฃจ๊ธฐ

  • JWT๋ฅผ Spring์—์„œ ์กฐ์ž‘ํ•˜๋ ค๋ฉด, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ → build.gradle ๋‚ด dependencies์— ์•„๋ž˜ ์ฝ”๋“œ ์ถ”๊ฐ€
// JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
  • application.properties์— Secret Key ์ถ”๊ฐ€
// Base64๋กœ ์ธ์ฝ”๋”ฉ๋˜์–ด ์žˆ๋Š” secret key
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==

JWT Util ํด๋ž˜์Šค ์ƒ์„ฑ

JWT ์œ ํ‹ธ์€ JWT์™€ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ๋“ค์„ ๊ฐ€์ง„ ํด๋ž˜์Šค

Util(์œ ํ‹ธ) ํด๋ž˜์Šค
ํŠน์ •ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜ ํ˜น์€ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•œ ์–ด๋– ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์ด ์กด์žฌํ•˜๋Š” ํด๋ž˜์Šค
์ฆ‰, ๋‹ค๋ฅธ ๊ฐ์ฒด์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ๋กœ์จ ๋™์ž‘ํ•˜๋Š” ํด๋ž˜์Šค

ex) 3,000์ด๋ผ๊ณ  ํ–ˆ์„ ๋•Œ ์› ํ˜น์€ ๋‹ฌ๋Ÿฌ๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ถ™์—ฌ์•ผํ•˜๋Š” ๋ฌธ์ž์—ด ์กฐ์ž‘ํ•˜๋Š” ์œ ํ‹ธ ํด๋ž˜์Šค
ex) ๋‚ ์งœ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ์œ ํ‹ธ ํด๋ž˜์Šค

 

ํ† ํฐ ์ƒ์„ฑ์— ํ•„์š”ํ•œ JWT ๋ฐ์ดํ„ฐ

// Header KEY ๊ฐ’
public static final String AUTHORIZATION_HEADER = "Authorization";
// ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ฐ’์˜ KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token ์‹๋ณ„์ž
// ํ† ํฐ ์•ž์— ๋ถ™์ผ ๊ทœ์น™
public static final String BEARER_PREFIX = "Bearer ";
// ํ† ํฐ ๋งŒ๋ฃŒ์‹œ๊ฐ„
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60๋ถ„

@Value("${jwt.secret.key}") // Base64 Encode ํ•œ SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

// ๋กœ๊น…: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋™์ž‘์„ ํ•˜๋Š” ๋™์•ˆ์— ํ”„๋กœ์ ํŠธ์˜ ์ƒํƒœ๋‚˜ ์–ด๋– ํ•œ ๋™์ž‘ ์ •๋ณด๋ฅผ ์‹œ๊ฐ„ ์ˆœ์œผ๋กœ ์ž…๋ ฅํ•˜๋Š” ๊ฒƒ
public static final Logger logger = LoggerFactory.getLogger("JWT ๊ด€๋ จ ๋กœ๊ทธ");

// @PostConstruct๋Š” ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ๋ฐ›์•„ ์˜ค๋ฉด ๋˜๋Š” ๊ฐ’์„ ์‚ฌ์šฉํ•  ๋•Œ๋งˆ๋‹ค ์š”์ฒญ์„ ์ƒˆ๋กœ ํ˜ธ์ถœํ•˜๋Š” ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ
@PostConstruct
public void init() {
    byte[] bytes = Base64.getDecoder().decode(secretKey);
    key = Keys.hmacShaKeyFor(bytes);
}

 

์‚ฌ์šฉ์ž ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•˜๋Š” Enum Class ์ƒ์„ฑ

package com.sparta.springauth.entity;

public enum UserRoleEnum {
    USER(Authority.USER),  // ์‚ฌ์šฉ์ž ๊ถŒํ•œ
    ADMIN(Authority.ADMIN);  // ๊ด€๋ฆฌ์ž ๊ถŒํ•œ

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

 

JWT ์ƒ์„ฑ

[JWT ์ƒ์„ฑ ํ›„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ• 2๊ฐ€์ง€]
1. Response Header์— JWT ํ† ํฐ ๋‹ฌ์•„์„œ ์‘๋‹ต

  • Cookie ์ƒ๊ฐ๋ณด๋‹ค ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•จ

2. Cookie ๊ฐ์ฒด ์ƒ์„ฑ ํ›„ Cookie ๊ฐ์ฒด์— Token์„ ๋‹ด์€ ํ›„, ๊ทธ Cookie๋ฅผ Response ๊ฐ์ฒด์— ๋‹ด์•„์„œ ์‘๋‹ต

  • JWT ํ† ํฐ ๋งŒ๋ฃŒ ๊ธฐํ•œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ฟ ํ‚ค์— ๋Œ€ํ•œ ๋งŒ๋ฃŒ ๊ธฐํ•œ์„ ์ค„ ์ˆ˜ ์žˆ์Œ
  • ๋‹ค๋ฅธ ์˜ต์…˜๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ
  • ๋˜ํ•œ, ์„œ๋ฒ„์—์„œ Cookie๋ฅผ ๋งŒ๋“ค๊ณ  Header์— Set-Cookie๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋„˜์–ด๊ฐ€๋ฉด ์ž๋™์œผ๋กœ Cookie๊ฐ€ ์ €์žฅ๋จ
// ํ† ํฐ ์ƒ์„ฑ
public String createToken(String username, UserRoleEnum role) {
    Date date = new Date();

    return BEARER_PREFIX +
            Jwts.builder()
                    .setSubject(username) // ์‚ฌ์šฉ์ž ์‹๋ณ„์ž๊ฐ’(ID)
                    .claim(AUTHORIZATION_KEY, role) // ์‚ฌ์šฉ์ž ๊ถŒํ•œ(key-value)
                    .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // ๋งŒ๋ฃŒ ์‹œ๊ฐ„
                    .setIssuedAt(date) // ๋ฐœ๊ธ‰์ผ
                    .signWith(key, signatureAlgorithm) // ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜(secret Key, ์„ ํƒํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜)
                    .compact();
}

 

์ƒ์„ฑ๋œ JWT๋ฅผ Cookie์— ์ €์žฅ

// -------- ์ƒ์„ฑ๋œ JWT๋ฅผ Cookie์— ์ €์žฅ --------
public void addJwtToCookie(String token, HttpServletResponse res) {
    try {
        token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value ์—๋Š” ๊ณต๋ฐฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์„œ encoding ์ง„ํ–‰

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
        cookie.setPath("/");

        // Response ๊ฐ์ฒด์— Cookie ์ถ”๊ฐ€
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        logger.error(e.getMessage());
    }
}

 

Cookie์— ๋“ค์–ด์žˆ๋Š” JWT ํ† ํฐ Substring

  • ์•ž์— ๋ถ™์ธ Bearer๋ฅผ ๋–ผ๊ธฐ ์œ„ํ•ด Substring ์ˆ˜ํ–‰ํ•จ
// JWT ํ† ํฐ substring
public String substringToken(String tokenValue) {
    if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
        return tokenValue.substring(7);
    }
    logger.error("Not Found Token");
    throw new NullPointerException("Not Found Token");
}

 

JWT ๊ฒ€์ฆ

// ํ† ํฐ ๊ฒ€์ฆ
public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException | SignatureException e) {
        logger.error("Invalid JWT signature, ์œ ํšจํ•˜์ง€ ์•Š๋Š” JWT ์„œ๋ช… ์ž…๋‹ˆ๋‹ค.");
    } catch (ExpiredJwtException e) {
        logger.error("Expired JWT token, ๋งŒ๋ฃŒ๋œ JWT token ์ž…๋‹ˆ๋‹ค.");
    } catch (UnsupportedJwtException e) {
        logger.error("Unsupported JWT token, ์ง€์›๋˜์ง€ ์•Š๋Š” JWT ํ† ํฐ ์ž…๋‹ˆ๋‹ค.");
    } catch (IllegalArgumentException e) {
        logger.error("JWT claims is empty, ์ž˜๋ชป๋œ JWT ํ† ํฐ ์ž…๋‹ˆ๋‹ค.");
    }
    return false;
}

 

JWT์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

// ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

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

package com.sparta.springauth.auth;

import com.sparta.springauth.entity.UserRoleEnum;
import com.sparta.springauth.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

@RestController
@RequestMapping("/api")
public class AuthController {
	...
    
    private final JwtUtil jwtUtil;

    public AuthController(JwtUtil jwtUtil){
        this.jwtUtil = jwtUtil;
    }

    ...

    @GetMapping("/create-jwt")
    public String createJwt(HttpServletResponse res) {
        // Jwt ์ƒ์„ฑ
        String token = jwtUtil.createToken("Robbie", UserRoleEnum.USER);

        // Jwt ์ฟ ํ‚ค ์ €์žฅ
        jwtUtil.addJwtToCookie(token, res);

        return "createJwt : " + token;
    }

    @GetMapping("/get-jwt")
    public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
        // JWT ํ† ํฐ substring
        String token = jwtUtil.substringToken(tokenValue);

        // ํ† ํฐ ๊ฒ€์ฆ
        if(!jwtUtil.validateToken(token)){
            throw new IllegalArgumentException("Token Error");
        }

        // ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
        Claims info = jwtUtil.getUserInfoFromToken(token);
        // ์‚ฌ์šฉ์ž username
        String username = info.getSubject();
        System.out.println("username = " + username);
        // ์‚ฌ์šฉ์ž ๊ถŒํ•œ
        String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
        System.out.println("authority = " + authority);

        return "getJwt : " + username + ", " + authority;
    }

    ...
}

JWT ์ƒ์„ฑ ์ฝ”๋“œ ๊ฒฐ๊ณผ
JWT ์กฐํšŒ ์ฝ”๋“œ ๊ฒฐ๊ณผ