JWT(JSON Web Token)
JWT๋ ๋ ๊ฐ์ฒด ๊ฐ์ ์ ๋ณด๋ฅผ JSON ๊ฐ์ฒด๋ก์ ์์ ํ๊ฒ ์ ์กํ๊ธฐ ์ํ ์ปดํฉํธํ๊ณ ๋ ๋ฆฝ์ ์ธ(self-contained) ๋ฐฉ์์ ๊ณต๊ฐ ํ์ค(RFC 7519)์ด๋ค.
ํํ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ์์ ์ฌ์ฉ๋๋ฉฐ, ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ฅํ์ง ์๋ Stateless ํ๊ฒฝ์ ๊ตฌ์ถํ๋ ๋ฐ ํต์ฌ์ ์ธ ์ญํ
OAuth 2.0์ Access Token์ด๋ OpenID Connect์ ID Token ๊ตฌํ์ฒด๋ก ๋๋ฆฌ ํ์ฉ
ํ ํฐ ์์ฒด๊ฐ ์๋ช ๋์ด ์์ผ๋ฏ๋ก, ์๋ฒ๋ ํ ํฐ์ ์ ๋ณด๊ฐ ์๋ณ์กฐ๋์ง ์์์์ ๊ฒ์ฆ ๊ฐ๋ฅ
JWT ๊ตฌ์กฐ - Header / Payload / Signature
JWT๋ ๋ง์นจํ(.)๋ก ๊ตฌ๋ถ๋๋ ์ธ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ, ๊ฐ ๋ถ๋ถ์ Base64Url๋ก ์ธ์ฝ๋ฉ๋์ด ์๋ค.
xxxxx.yyyyy.zzzzz
Header.Payload.Signature1. Header (ํค๋)
ํ ํฐ ์์ฒด์ ๋ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ์์ผ๋ฉฐ, ๋ ๊ฐ์ง ์ ๋ณด๋ฅผ ํ์๋ก ํฌํจํ๋ค.
typ(Type): ํ ํฐ์ ํ์ ์ ์ง์ (๋ณดํตJWT๋ก ๊ณ ์ )alg(Algorithm): ํ ํฐ์ ์๋ช (sign)ํ๋ ๋ฐ ์ฌ์ฉ๋ ์ํธํ ์๊ณ ๋ฆฌ์ฆ ์ง์ (์:HS256,RS256)
{
"alg": "HS256",
"typ": "JWT"
}2. Payload (ํ์ด๋ก๋)
ํ ํฐ์ด ์ค์ ๋ก ์ ๋ฌํ๊ณ ์ ํ๋ ์ ๋ณด(ํด๋ ์)๋ฅผ ๋ด๊ณ ์์ผ๋ฉฐ, ์ฌ์ฉ์์ ๋ํ ์ ๋ณด๋ ํ ํฐ ์์ฒด์ ๋ํ ์ค๋ช ๋ฑ์ ๋ํ๋ด๋ ํค-๊ฐ ์์ผ๋ก ๊ตฌ์ฑ๋๋ค.
๋ค์์ Registered, Public, Private ํด๋ ์์ ๋ชจ๋ ํฌํจํ๋ JWT ํ์ด๋ก๋์ ์์์ด๋ค.
Registered Claims(๋ฑ๋ก๋ ํด๋ ์): JWT ์ฌ์์ ์ด๋ฏธ ์ ์๋ ํด๋ ์๋ค๋ก, ํ์๋ ์๋์ง๋ง ์ํธ ์ด์ฉ์ฑ์ ์ํด ์ฌ์ฉ ๊ถ์ฅ
iss(Issuer): ํ ํฐ ๋ฐ๊ธ์sub(Subject): ํ ํฐ์ ์ฃผ์ฒด(์: ์ฌ์ฉ์์ ๊ณ ์ ID)aud(Audience): ํ ํฐ์ ์์ ์(์ด ํ ํฐ์ ์ฌ์ฉํด์ผ ํ๋ ์๋น์ค)exp(Expiration Time): ํ ํฐ์ ๋ง๋ฃ ์๊ฐiat(Issued At): ํ ํฐ์ด ๋ฐ๊ธ๋ ์๊ฐ
Public Claims(๊ณต๊ฐ ํด๋ ์): JWT๋ฅผ ์ฌ์ฉํ๋ ์ฌ๋ฌ ์ฃผ์ฒด๋ค์ด ๊ณตํต์ ์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํด ๊ณต๊ฐ์ ์ผ๋ก ์ ์๋ ํด๋ ์
๊ณต๊ฐ๋์ด๋ ์ข์ ์ ๋ณด๊ฐ ์๋, ํ์ค์ฒ๋ผ ๊ณต๊ฐ์ ์ผ๋ก ์ ์๋ ๊ท์ฝ์ด๋ผ๋ ์๋ฏธ
์ด๋ฆ ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํด IANA JWT ํด๋ ์ ๋ ์ง์คํธ๋ฆฌ์ ๋ฑ๋กํ๊ฑฐ๋, URI ํ์์ผ๋ก ๋ค์์คํ์ด์ค๋ฅผ ๊ด๋ฆฌ
Private Claims(๋น๊ณต๊ฐ ํด๋ ์): ํ ํฐ์ ๋ฐ๊ธํ๋ ์๋ฒ์ ํ ํฐ์ ์ฌ์ฉํ๋ ์๋ฒ ๊ฐ์ ํ์ ํ์ ์ฌ์ฉํ๋ ๋น๊ณต๊ฐ์ ์ธ ํด๋ ์(์:
role: "editor")
ํ์ด๋ก๋๋ ๋จ์ํ Base64Url๋ก ์ธ์ฝ๋ฉ๋์์ ๋ฟ, ์ํธํ๋ ๊ฒ์ด ์๋๋ฏ๋ก, ๋น๋ฐ๋ฒํธ์ ๊ฐ์ ๋ฏผ๊ฐํ ์ ๋ณด๋ ์ ๋ ํ์ด๋ก๋์ ๋ด์์๋ ์ ๋๋ค.
3. Signature(์๋ช
)
์๋ช ์ ํ ํฐ์ ๋ฌด๊ฒฐ์ฑ(Integrity)๊ณผ ๋ฐ๊ธ์์ ์ ์(Authenticity)์ ๋ณด์ฅํ๋ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ด๋ค.
์๋ช ์ ์ธ์ฝ๋ฉ๋
Header์Payload๋ฅผ ํฉ์น๊ณ , ์ง์ ๋ ๋น๋ฐ ํค(Secret) ๋๋ ๊ฐ์ธ ํค(Private Key)๋ฅผ ์ฌ์ฉํ์ฌalg์ ๋ช ์๋ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ํธํํ์ฌ ์์ฑ์์ ์ธก์ ์ด ์๋ช ์ ๊ฒ์ฆํจ์ผ๋ก์จ, ํ ํฐ์ด ์ค๊ฐ์ ๋ณ๊ฒฝ๋์ง ์์์ผ๋ฉฐ ์ ๋ขฐํ ์ ์๋ ๋ฐ๊ธ์์ ์ํด ๋ฐ๊ธ๋์์์ ํ์ธ
์๋ช
์๊ณ ๋ฆฌ์ฆ
JWT ์๋ช ๋ฐฉ์์ ๋์นญํค์ ๋น๋์นญํค ๋ฐฉ์์ผ๋ก ๋๋๋ค.
HS256(HMAC with SHA-256): ๋์นญํค ์๊ณ ๋ฆฌ์ฆ
ํ๋์ ๋น๋ฐ ํค(Secret)๋ฅผ ์๋ช ๊ณผ ๊ฒ์ฆ์ ๋ชจ๋ ์ฌ์ฉ
์๋ฒ ๋ด์์ ํ ํฐ์ ์์ฑํ๊ณ ๊ฒ์ฆํ๋ ๋ฑ, ์ ๋ขฐํ ์ ์๋ ๋จ์ผ ์์คํ ํน์ ์์์ ์์คํ ํ๊ฒฝ์ ์ ํฉ
๋จ์ : ์๋ช ์ ๊ฒ์ฆํด์ผ ํ๋ ๋ชจ๋ ์ฃผ์ฒด๊ฐ ๋์ผํ ๋น๋ฐ ํค๋ฅผ ๊ณต์ ํด์ผ ํ๋ฏ๋ก, ํค๊ฐ ์ ์ถ๋ ๊ฒฝ์ฐ ๋๊ตฌ๋ ์ ํจํ ํ ํฐ์ ์์ฑ ๊ฐ๋ฅ ์ํ ์กด์ฌ
RS256(RSA Signature with SHA-256): ๋น๋์นญํค ์๊ณ ๋ฆฌ์ฆ
๊ฐ์ธ ํค(Private Key)๋ก ์๋ช ํ๊ณ , ์์ ์ด๋ฃจ๋ ๊ณต๊ฐ ํค(Public Key)๋ก ๊ฒ์ฆ
ํ ํฐ ๋ฐ๊ธ์(์: ์ธ์ฆ ์๋ฒ)๋ง์ด ๊ฐ์ธ ํค๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ๊ณ , ํ ํฐ ๊ฒ์ฆ์(์: ๋ฆฌ์์ค ์๋ฒ๋ค)๋ ๊ณต๊ฐ ํค๋ง ์์งํ์ฌ ๊ฒ์ฆ
MSA(Microservice Architecture)์ ๊ฐ์ด ์ฌ๋ฌ ์๋น์ค์์ ํ ํฐ์ ๊ฒ์ฆํด์ผ ํ๋ ๋ถ์ฐ ํ๊ฒฝ์ ์ ํฉํ๋ฉฐ, ํ์ฌ ๊ฐ์ฅ ๋๋ฆฌ ๊ถ์ฅ๋๋ ๋ฐฉ์
JWT ๋ณด์ ๋ฐ ์ฃผ์์ฌํญ
JWT๋ ํธ๋ฆฌํ์ง๋ง, ์๋ชป ์ฌ์ฉํ๋ฉด ์ฌ๊ฐํ ๋ณด์ ์ทจ์ฝ์ ์ผ๋ก ์ด์ด์ง ์ ์๋ค.
alg: none์ทจ์ฝ์ ๊ณผ๊ฑฐ ์ผ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์
algํค๋๋ฅผnone์ผ๋ก ์ค์ ํ๋ฉด ์๋ช ๊ฒ์ฆ์ ๊ฑด๋๋ฐ๋ ์ทจ์ฝ์ ์กด์ฌ๊ณต๊ฒฉ์๋ ์๋ช ์ ์ ๊ฑฐํ๊ณ
alg๋ฅผnone์ผ๋ก ์กฐ์ํ์ฌ ์ ํจํ ํ ํฐ์ธ ๊ฒ์ฒ๋ผ ์์ฅ ๊ฐ๋ฅํด๊ฒฐ์ฑ : ์๋ฒ๋ ๋ฐ๋์
alg: none์ ๊ฑฐ๋ถํ๊ณ , ํ์ฉํ ์๊ณ ๋ฆฌ์ฆ ๋ชฉ๋ก(Whitelist)์ ๋ช ์์ ์ผ๋ก ๊ด๋ฆฌ
ํ ํฐ ์ ์ฅ ์์น(
localStoragevsHttpOnlyCookie)localStorage: JavaScript๋ก ์ ๊ทผ์ด ์ฌ์ ์ฌ์ฉ์ด ํธ๋ฆฌํ์ง๋ง, XSS(Cross-Site Scripting) ๊ณต๊ฒฉ์ ์ทจ์ฝ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฃผ์ ๋๋ฉด ํ ํฐ ํ์ทจ ๊ฐ๋ฅ์ฑ ์กด์ฌ
HttpOnlyCookie: JavaScript ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํ์ฌ XSS ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ํ ํฐ์ ๋ณดํธ ๊ฐ๋ฅํ์ง๋ง ๋ธ๋ผ์ฐ์ ๊ฐ ๋ชจ๋ ์์ฒญ์ ์๋์ผ๋ก ์ฟ ํค๋ฅผ ํฌํจ์ํค๋ฏ๋ก CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ์ ์ทจ์ฝ(SameSite ์์ฑ์ผ๋ก ์ํ ๊ฐ๋ฅ)
Stateless์ ํ ํฐ ํ๊ธฐ์ ์ด๋ ค์
JWT๋ ์๋ฒ๊ฐ ์ํ๋ฅผ ์ ์ฅํ์ง ์๋ Statelessํ๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ์ด๋ ๊ณง ํ ๋ฒ ๋ฐ๊ธ๋ ํ ํฐ์ ๋ง๋ฃ ์ ์ ํ๊ธฐ ์ด๋ ค์ ์กด์ฌ
ํด๊ฒฐ์ฑ
์งง์ ๋ง๋ฃ ์๊ฐ: Access Token์ ๋ง๋ฃ ์๊ฐ์ ๋งค์ฐ ์งง๊ฒ(์: 5~15๋ถ) ์ค์ ํ์ฌ ํ์ทจ๋๋๋ผ๋ ์ํ์ ์ต์ํ
ํ ํฐ ํ๊ธฐ ๋ชฉ๋ก(Blacklist) ์ ์ง: ํ๊ธฐ๋ ํ ํฐ ID๋ฅผ ๋ณ๋์ ์ ์ฅ์(์: Redis)์ ๋ชฉ๋ก์ผ๋ก ๊ด๋ฆฌ(JWT์ Stateless ์ฅ์ ์ ์ผ๋ถ ํฌ์ํ๋ ๋ฐฉ๋ฒ)
Refresh Token ์ ๋ต
Access Token ๋จ๋
์ฌ์ฉ์ ํ๊ณ
JWT๋ฅผ Access Token ํ๋๋ก๋ง ์ด์ฉํ ๊ฒฝ์ฐ, Stateless ํน์ฑ์ด ์คํ๋ ค ๋ณด์์ ํน์ ํธ์์ ์ฝ์ ์ผ๋ก ์์ฉํ ์ ์๋ค.
์ ์ด๊ถ ์์ค: ํ ํฐ์ด ํ์ทจ๋์ด๋ ์๋ฒ์์ ๊ฐ์ ๋ก ๋ก๊ทธ์์(๋ฌดํจํ)์ํฌ ๋ฐฉ๋ฒ ์์
์ ํจ๊ธฐ๊ฐ ๋๋ ๋ง: ๋ณด์์ ์ํด ๊ธฐ๊ฐ์ ์งง๊ฒ ์ก์ผ๋ฉด ์ฌ์ฉ์๊ฐ ๋๋ฌด ์์ฃผ ๋ก๊ทธ์ธํด์ผ ํ๊ณ , ๊ธธ๊ฒ ์ก์ผ๋ฉด ํ์ทจ ์ ํผํด ํ๋ ๊ฐ๋ฅ์ฑ ์กด์ฌ
๋ ํ ํฐ์ ์ญํ ๋ถ๋ด
๋ชฉ์
๋ฆฌ์์ค ์ ๊ทผ ์น์ธ
์๋ก์ด Access Token ๋ฐ๊ธ
์ ํจ ๊ธฐ๊ฐ
๋งค์ฐ ์งง์
์๋์ ์ผ๋ก ๊น
์ ์ฅ์
ํด๋ผ์ด์ธํธ ๋ฉ๋ชจ๋ฆฌ(๋ณ์) ๋ฑ
์๋ฒ DB / Redis ๋ฐ ํด๋ผ์ด์ธํธ(HttpOnly Cookie)
์ธ์ฆ ์๋๋ฆฌ์ค(Flow)
๋ก๊ทธ์ธ ์ฑ๊ณต: ์๋ฒ๋ Access Token(AT)๊ณผ Refresh Token(RT)์ ๋ชจ๋ ๋ฐ๊ธ
์ด๋ RT๋ ์๋ฒ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ Redis์ ์ ์ฅํ์ฌ ์ถ์ ๊ฐ๋ฅํ๊ฒ ๊ด๋ฆฌ
API ์์ฒญ: ํด๋ผ์ด์ธํธ๋ AT๋ฅผ ํค๋์ ๋ด์ ์์ฒญ ์ ์ก
์๋ฒ๋ AT์ ์๋ช ์ ๊ฒ์ฆํ๊ณ ์๋ต
AT ๋ง๋ฃ: AT๊ฐ ๋ง๋ฃ๋๋ฉด ์๋ฒ๋
401 Unauthorized์๋ฌ๋ฅผ ๋ฐํํ ํฐ ๊ฐฑ์ : ํด๋ผ์ด์ธํธ๋ ์ ์ฅํด๋ RT๋ฅผ ์๋ฒ์ ๋ณ๋ ๊ฐฑ์ ์๋ํฌ์ธํธ๋ก ์ ์กํ์ฌ ์๋ก์ด AT ์์ฒญ
๊ฒ์ฆ ๋ฐ ์ฌ๋ฐ๊ธ: ์๋ฒ๋ RT์ ์ ํจ์ฑ์ DB์ ๋์กฐํ์ฌ ํ์ธํ ํ, ์๋ก์ด AT๋ฅผ ๋ฐ๊ธ
๋ณด์ ๊ฐํ ๊ธฐ๋ฒ - Refresh Token Rotation
RT๋ ์๋ช ์ด ๊ธธ๊ธฐ ๋๋ฌธ์ ์ด ํ ํฐ ์์ฒด๊ฐ ํ์ทจ๋๋ฉด ๊ณต๊ฒฉ์๊ฐ ์ง์์ ์ผ๋ก AT๋ฅผ ์์ฑํ ์ ์๋ ์ํ์ด ์์ด, ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด Refresh Token Rotation ๊ธฐ๋ฒ์ ๊ถ์ฅํ๋ค.
๋์ ๋ฐฉ์: ํด๋ผ์ด์ธํธ๊ฐ RT๋ฅผ ์ฌ์ฉํ์ฌ AT๋ฅผ ๊ฐฑ์ ํ ๋, ๊ธฐ์กด์ RT๋ฅผ ๋ฌดํจํํ๊ณ ์๋ก์ด RT๋ ํจ๊ป ๋ฐ๊ธ
ํจ๊ณผ
๋ง์ฝ ๊ณต๊ฒฉ์๊ฐ RT๋ฅผ ํ์ทจํด ๋จผ์ ์ฌ์ฉํ๋ค๋ฉด, ์ดํ ์ ๋นํ ์ฌ์ฉ์๊ฐ ๋์ผํ RT๋ก ๊ฐฑ์ ์ ์๋ํ ๋ ์๋ฒ๋ ์ด๋ฏธ ์ฌ์ฉ๋ RT์์ ๊ฐ์ง
์ด ๊ฒฝ์ฐ ํด๋น ์ฌ์ฉ์์ ๊ด๋ จ๋ ๋ชจ๋ ํ ํฐ์ ์ฆ์ ๋ฌดํจํํ์ฌ ํผํด ์ต์ํ
๋๋ฌธ์ Refresh Token์ ์๋ฒ side(์: Redis)์์ ๊ด๋ฆฌํ๊ฒ ๋๋ฉด ์์ ํ Stateless๋ ๊นจ์ง๊ฒ ๋์ง๋ง, ์ค๋ฌด์์๋ ๋ณด์์์ ์ด์ ๋ก ์ด๋ฌํ Hybrid ๋ฐฉ์์ ํ์ค์ฒ๋ผ ์ฌ์ฉํ๋ค.
Last updated