
์ด ๋ฌธ์์์๋ ์น ์ธ์ฆ ๊ณผ์ ์์ ์์ฃผ ๋ฑ์ฅํด ํท๊ฐ๋ฆฌ๊ธฐ ์ฌ์ด(์ ๊ฐ ํท๊ฐ๋ ค์) JWT, Bearer, access_token, refresh_token,
์ฟ ํคยทlocalStorageยท๋ฉ๋ชจ๋ฆฌ ์ ์ฅ ๋ฐฉ์, Session ๋ฐฉ์, ๊ทธ๋ฆฌ๊ณ ์ธ์
ํ ํฐ ๊ฐ๋
์ ๋ช
ํํ ์ ๋ฆฌํด๋ดค์ต๋๋ค.
์น์์ ๋ก๊ทธ์ธ ๊ด๋ จ ์์ ์ ํฌ๊ฒ ๋ ๋จ๊ณ๋ก ๋๋ฉ๋๋ค.
์ด ๋ ๋จ๊ณ ๊ณผ์ ์์ ์๋ ๊ฐ๋ ๋ค์ด ์์ฐ์ค๋ฝ๊ฒ ๋ฑ์ฅํฉ๋๋ค:
JWT๋ ํ ํฐ์ ๋ชจ์(ํฌ๋งท) ์
๋๋ค. ์ฆ, โํ ํฐ์ด ์ด๋ค ๊ตฌ์กฐ๋ก ์๊ฒผ๋?โ๋ฅผ ์ ์ํ ๊ท๊ฒฉ์
๋๋ค. => "ํ ํฐ์ด ์ด๋ค ๋ชจ์์ธ๊ฐ"
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
Stateless = ์๋ฒ๊ฐ ์๋ฌด ์ํ๋ ๋ณด๊ดํ์ง ์๊ณ , ์์ฒญ๋ง๋ค ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ค์ ๋ฐ์ ํ์ธํ๋ ๋ฐฉ์
Bearer๋ ์ธ์ฆ ํ ํฐ์ ์ ์กํ๋ ๋ฐฉ์(์คํด) ์
๋๋ค. => "์ ์ก ๋ฐฉ์"
Authorization: Bearer <token>
ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ API์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ ๋ฌํ ๋ ๊ฑฐ์ ํ์ค์ฒ๋ผ ์ฌ์ฉ๋ฉ๋๋ค.
Bearer๋ โ์ ์ก ๋ฐฉ์โ์ด๋ผ JWT๊ฐ ์๋ ํ ํฐ๋ ์ผ๋ง๋ ์ง ์ค์ด ๋ณด๋ผ ์ ์๋ค.
JWT๋ Bearer์์ ์ฐ์ด๋ ์ฌ๋ฌ ํ ํฐ ์ค ํ๋์ผ ๋ฟ์ด๋ค.
Set-Cookie ๋ก ๋ด๋ ค์ฃผ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์ ์ฅ httpOnly, secure, sameSite ์ต์
์ผ๋ก ๋ณด์ ๊ฐํ ๊ฐ๋ฅ ๐ httpOnly๋?
์๋ฐ์คํฌ๋ฆฝํธ์์ ์ฟ ํค๋ฅผ ์ฝ์ง ๋ชปํ๋๋ก ์ฐจ๋จํ๋ ์ต์
โ refresh_token, session_id์ฒ๋ผ ์ ์ถ๋๋ฉด ์ํํ ๊ฐ ๋ณดํธ์ ํ์
์ refresh_token / session_id ๋ ๋ฐ๋์ httpOnly ์ฟ ํค์ ์ ์ฅํ ๊น?
์ด ๋ ๊ฐ์ ํ์ทจ๋๋ฉด ๊ณ์ ์ด ๊ทธ๋๋ก ๋๋๋๋ โ๊ฐ์ฅ ์ํํ ํ ํฐโ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
ํญ๋ชฉ ํธ๋ ธ์ ๋ ์ํ๋ access_token ์งง๊ฒ ์ด์์ ํผํด ์ ํ์ refresh_token ์ access_token ๋ฌดํ ์ฌ๋ฐ๊ธ โ ๊ณ์ ์ฅ์ session_id ์๋ฒ ์ธ์ ๊ทธ๋๋ก ํ์ทจ โ ์์ ๋๋ ์ฆ, ์ ๋ JavaScript์์ ์ฝํ๋ฉด ์ ๋๋ ํ ํฐ๋ค์ ๋๋ค.
localStorage โ
โ JS๋ก ์ฝํ โ XSS ๊ณต๊ฒฉ์ ๊ทธ๋๋ก ๋ ธ์ถhttpOnly ์ฟ ํค โญ
โ JS ์ ๊ทผ ์์ ์ฐจ๋จ
โ ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ ์ ์กํ๋ฏ๋ก ์ธ์ฆ ํ๋ฆ๋ ์์ฐ์ค๋ฌ์
์ผ๋ฐ ์ฟ ํค๋ JS์์ ์ฝํ๊ธฐ ๋๋ฌธ์ XSS ๊ณต๊ฒฉ์ ์ทจ์ฝํฉ๋๋ค.
XSS(Cross-Site Scripting)?
์ฌ์ฉ์๊ฐ ๋ฐฉ๋ฌธํ ์ ์ ์ฌ์ดํธ ์์์
๊ณต๊ฒฉ์๊ฐ ๋ง๋ ์ ์ฑ JS ์ฝ๋๊ฐ ์คํ๋๋๋ก ์์ด๋ ๊ณต๊ฒฉ
Set-Cookie: refresh_token=abcd; HttpOnly;
์ฃผ๋ก ์ด๋์ ์ฐ๋๊ฐ?
์ฃผ ์ฌ์ฉ์ฒ
์ธ๋ฉ๋ชจ๋ฆฌ๋?
React state, Context, Redux store, Zustand, ์ ์ญ JS ๋ณ์ ๋ฑ
๋ฐํ์ ๋ฉ๋ชจ๋ฆฌ์๋ง ๋ค๊ณ ์๋ ์ํ๋ฅผ ์๋ฏธ
์ฃผ ์ฌ์ฉ์ฒ
์ผ๋ถ๋ฌ ์ฌ๋ผ์ง๋๋ก ์ค๊ณํ ๊ฒ์ ๋๋ค.
localStorage์ ์ ์ฅํ๋ฉด ์คํฌ๋ฆฝํธ๋ก ์ฝ๊ฒ ํ์ทจ๋จ:
localStorage.getItem("access_token")
๊ทธ๋ฌ๋ ๋ฉ๋ชจ๋ฆฌ๋ ํ์ด์ง Reload ์ ์ฌ๋ผ์ ธ ์ง์์ ์ธ ๊ณต๊ฒฉ์ด ๋ถ๊ฐ๋ฅ.
์ธ๋ฉ๋ชจ๋ฆฌ์ ์๋ access_token์ ์๋ก๊ณ ์นจ ์ ์ฌ๋ผ์ง๊ธฐ ๋๋ฌธ์,
์๋ฌด ๋ก์ง๋ ์์ผ๋ฉด ์ฌ์ค์ โ๋ก๊ทธ์์" ๋ฉ๋๋ค.
๊ทธ๋์ ๋ณดํต ์ฑ์์ ์ด๋ฐ ํ๋ฆ์ผ๋ก ๊ตฌํํฉ๋๋ค:
์ฑ์ด ์ฒ์ ๋ก๋๋ ๋(์: App ๋๋ Layout ๋ง์ดํธ ์์ ์)
/auth/refresh ๊ฐ์ ํ ํฐ ์ฌ๋ฐ๊ธ API๋ฅผ ์๋์ผ๋ก ํ ๋ฒ ํธ์ถํฉ๋๋ค.
์ด๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์์์ refresh_token(httpOnly ์ฟ ํค)์ ์๋ฒ๋ก ์ ์กํฉ๋๋ค.
์๋ฒ๋ refresh_token์ ๊ฒ์ฆํ๊ณ ์ access_token์ ์๋ต์ผ๋ก ๋ด๋ ค์ค๋๋ค.
ํ๋ก ํธ๋ ๋ฐ์ access_token์ ๋ค์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํฉ๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด, ์ ์ ์ ์ฅ์์๋ ์๋ก๊ณ ์นจ์ ํด๋ ๋ก๊ทธ์ธ์ด ์ ์ง๋ ๊ฒ์ฒ๋ผ ๋ณด์ ๋๋ค.
๋น์
์ธ์ (Session) ๋ฐฉ์์ ์๋ฒ๊ฐ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ง์ ๊ธฐ์ตํ๋ ๋ฐฉ์์ ๋๋ค.
๋ก๊ทธ์ธ ์ฑ๊ณต โ ์๋ฒ๊ฐ ์ธ์ ์์ฑ
session_id โ { userId: 1, role: 'admin' }
session_id๋ฅผ ์ฟ ํค๋ก ๋ด๋ ค์ค
Set-Cookie: session_id=xyz123; HttpOnly;
๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ๋ง๋ค ์๋ ์ ์ก
Cookie: session_id=xyz123
์๋ฒ๋ ์ด session_id๋ก ์ ์ ์ ๋ณด ์กฐํ
์๋ฒ๊ฐ ์ ์ ์ํ๋ฅผ ์ ์ฅํ๊ณ ,
๋ธ๋ผ์ฐ์ ๋ session_id๋ผ๋ ์ด์ ๋ง ๋ค๊ณ ๋ค๋๋ ๊ตฌ์กฐ
์ค์ผ์ผ๋ง ๋น์ฉ์ด๋?
์ฌ์ฉ์ ์ฆ๊ฐ์ ๋ง์ถฐ ์๋ฒ๋ฅผ ํ์ฅํ ๋ ํ์ํ ์ถ๊ฐ ๋ถ๋ด
| ๊ฐ๋ | ์ ์ฅ ์์น | ๋ชฉ์ | ์ธ์ฆ ์ฌ๋ถ |
|---|---|---|---|
| Session(์๋ฒ ์ธ์ ) | ์๋ฒ | ๋ก๊ทธ์ธ ์ํ ์ ์ง | โ |
| sessionStorage | ๋ธ๋ผ์ฐ์ | ์ผ๋ฐ ๋ฐ์ดํฐ ์์ ์ ์ฅ | โ |
fetch('/api/login', {
method: 'POST',
credentials: 'include'
});
Opaque Token์ ์๋ฏธ๋ฅผ ์ ์ ์๋ ๋๋ค ๋ฌธ์์ด ํ ํฐ์ ๋๋ค.
์:
4f8d1b28e92c4a6ca8948d91a3f8a0d7
๋ฐฑ์๋ ์ธ์ฆ ์๋ฒ๊ฐ ์ง์ ์์ฑํด ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํฉ๋๋ค.
์)
98de12ccf39a49dbb041a01bdac20345
์๋ฒ DB์๋ ์ด๋ ๊ฒ ์ ์ฅ๋จ:
refresh_token -> userId 123
| ํ ํฐ ์ข ๋ฅ | ์๋ฒ์์ DB ํ์? | ์ด์ |
|---|---|---|
| JWT | โ ํ์ ์์ | ํ ํฐ ์์ userId/exp/role ๋ฑ์ด ๋ค ๋ค์ด์๊ณ ์๋ช ์ผ๋ก ๊ฒ์ฆ ๊ฐ๋ฅ |
| Opaque Token | โ ํ์ํจ | ๋ด๋ถ ์ ๋ณด ์์ โ ์๋ฒ๊ฐ ์ ์ฅํ ์ ๋ณด์ ๋งค์นญํด์ผ ํจ |
โ ๊ทธ๋์ refresh_token์ ์ฐ๋ฉด
๋ ธ์ถ๋์ ๋ ๋ด๋ถ ์ ๋ณด๊ฐ ์์ผ๋ฏ๋ก ์์
์๋ฒ DB์์ ํด๋น ํ ํฐ๋ง ์ง์ฐ๋ฉด ์ฆ์ ๋ฌดํจํ ๊ฐ๋ฅ (๋ก๊ทธ์์ ๊ฐ๋ฅ)
JWT refresh_token๋ณด๋ค ํจ์ฌ ์ปจํธ๋กค์ด ์ฌ์
| ๊ฐ๋ | ์ญํ | ์ฃผ๋ก ์ฐ๋ ๊ณณ |
|---|---|---|
| JWT | ํ ํฐ ํฌ๋งท(Stateless ์ธ์ฆ์ ์ ํฉ) | API ์ธ์ฆ, OAuth id_token |
| access_token | ์งง๊ฒ ์ฌ๋ ์ธ์ฆ ํ ํฐ | Bearer ํค๋, ์ธ๋ฉ๋ชจ๋ฆฌ ์ ์ฅ |
| refresh_token | access_token ์ฌ๋ฐ๊ธ | httpOnly ์ฟ ํค(์๋ฒ DB ๊ฒ์ฆ) |
| Bearer | ์ธ์ฆ ์ ๋ฌ ๋ฐฉ์(ํค๋ ์คํด) | ๋ชจ๋ API ์ธ์ฆ ์์ฒญ |
| ์ฟ ํค(httpOnly) | ๋ฏผ๊ฐ ํ ํฐ ๋ณด๊ด/์๋ ์ ์ก | refresh_token, session_id |
| localStorage / sessionStorage | JS ์ ๊ทผ ๊ฐ๋ฅ Key-Value ์ ์ฅ์ | ์ผ๋ฐ UI ๊ฐ, ๋ฎ์ ๋ณด์ ๋ฐ์ดํฐ |
| ๋ฉ๋ชจ๋ฆฌ(In-Memory) | JS ๋ฐํ์ ์ํ ์ ์ฅ | access_token ์์ ๋ณด๊ด |
| ์ธ์ (SessionID) | ์๋ฒ๊ฐ ์ํ ์ ์ฅํ๋ ์ธ์ฆ ๋ฐฉ์ | SSR/์ ํต ์น, ์ฌ๋ด ์์คํ |
| Opaque Token | ์๋ฒ๊ฐ DB์ ์ ์ฅํ๋ ๋๋ค ํ ํฐ | refresh_token, OAuth2 access_token |
| API Key | ์๋น์ค/์๋ฒ ๊ฐ ์ธ์ฆ | ์ธ๋ถ API, ๋ฐฑ์๋ ํต์ |
| id_token | ์ฌ์ฉ์ ์ ๋ณด(JWT) | ์์ ๋ก๊ทธ์ธ(OAuth/OpenID Connect) |
/auth/refresh ์์ฒญ์ ๋ณด๋ด๊ณ โ ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฌ์ฉ์๋ ์๋ก๊ณ ์นจ์ ํ๊ฑฐ๋ access_token์ด ๋ง๋ฃ๋๋๋ผ๋
refresh_token(์ฟ ํค) ๋๋ session_id ๋๋ถ์ ๋ก๊ทธ์ธ ์ํ๊ฐ ์ ์ง๋จ