์น ์ดํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ค๋ณด๋ฉด, ์ฌ์ฉ์ ์ธ์ฆ๊ณผ ๊ถํ ์ธ๊ฐ์ ๋ํ ๊ฐ๋ ์ ๋ฐ๋์ ๋ง์ฃผํ๊ฒ ๋๊ณ , ์ด๋ฅผ ์ค์ ๋ก ๊ตฌํํ๊ธฐ ์ํด์ ์ฟ ํค, ์ธ์ , JWT์ ๊ฐ์ ๊ธฐ์ ์ ํ์ฉํ๊ฒ ๋๋ค.
์ด๋ฒ ๊ฐ์์์๋ ์ธ์ฆ ์ธ๊ฐ์ ๊ฐ๋ ๊ณผ ์ฟ ํค, ์ธ์ , JWT ๊ฐ๊ฐ์ ๋ฐฉ์์ด ์ด๋ป๊ฒ ์๋ํ๋์ง, ์ด๋ค ์ฅ๋จ์ ์ ๊ฐ์ง๋์ง ์ ์ฒด์ ์ผ๋ก ์์๋ณด์๋ค.
๐์ธ์ฆ/์ธ๊ฐ
์ธ์ฆ
Authentication, ์ฌ์ฉ์๊ฐ "๋๊ตฌ์ธ์ง" ํ์ธํ๋ ๊ณผ์
์ธ์ฆ์ ์์คํ ์ ์ด๋ฏธ ๊ฐ์ ๋ ์ฌ์ฉ์์ธ์ง๋ฅผ ํ๋ณํ๋ ์ ์ฐจ์ด๋ค. ํํ ๋ก๊ทธ์ธ ๊ณผ์ ์ ํตํด ์ํ๋๋ฉฐ, ์ฌ์ฉ์๊ฐ ์ ๊ณตํ๋ ์๊ฒฉ์ฆ๋ช (์์ด๋, ๋น๋ฐ๋ฒํธ, ์ธ์ฆ ํ ํฐ, OTP ๋ฑ)์ ๊ธฐ๋ฐ์ผ๋ก ํด๋น ์ฌ์ฉ์๊ฐ ๋ฑ๋ก๋ ์ฌ์ฉ์์์ ํ์ธํ๋ค.
์์:
- ์ฌ์ฉ์๊ฐ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํด ๋ก๊ทธ์ธ
- OAuth๋ฅผ ํตํด ๊ตฌ๊ธ ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ
- API ์์ฒญ์ ํฌํจ๋ JWT ํ ํฐ์ ํ์ฑํ์ฌ ์ ํจํ ์ฌ์ฉ์์์ ๊ฒ์ฆ
์ธ์ฆ์ ๋ณธ์ธ ํ์ธ ์ ์ฐจ์ด๋ฉฐ, ์ฃผ๋ก ๋ก๊ทธ์ธ ๋๋ API ํธ์ถ ์ Access Token ๋ฑ์ผ๋ก ์ํ๋๋ค. ์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด, ์ฌ์ฉ์์ ๋ํ ์๋ณ ์ ๋ณด(์ฌ์ฉ์ ID, ์ญํ ๋ฑ)์ ์์คํ ์ด ์ธ์ํ๊ฒ ๋๋ค.
์ธ๊ฐ
Authorization, ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ํน์ ์์์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ด ์๋์ง๋ฅผ ํ๋ณํ๋ ๊ณผ์
์ธ๊ฐ๋ ์ธ์ฆ์ด ์๋ฃ๋ ์ฌ์ฉ์๊ฐ ์์ฒญํ ์์์ ์ ๊ทผํ ๊ถํ์ด ์๋์ง๋ฅผ ํ๋จํ๋ค. ์๋ฅผ ๋ค์ด, ์ผ๋ฐ ์ฌ์ฉ์์ ๊ด๋ฆฌ์๊ฐ ์ ๊ทผํ ์ ์๋ ์์์ด ๋ค๋ฅธ ๊ฒฝ์ฐ, ์ธ๊ฐ๋ ์ด ๊ถํ ๊ตฌ๋ถ์ ์ ์ดํ๋ ์ญํ ์ ํ๋ค.
์์:
- ๊ด๋ฆฌ์์ธ ๊ฒฝ์ฐ์๋ง ์ ์ ๋ชฉ๋ก API ํธ์ถ ๊ฐ๋ฅ
- ์ฌ์ฉ์๊ฐ ๋ณธ์ธ์ ๊ฒ์๊ธ๋ง ์์ ํ ์ ์์
- ํน์ ๊ธฐ๋ฅ(๊ฒฐ์ , ์ญ์ ๋ฑ)์ ํน์ ์ญํ ์ ๊ฐ์ง ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
์ธ๊ฐ๋ ๊ถํ ์ ์ด ์ ์ฐจ์ด๋ฉฐ, ์ธ์ฆ ์ดํ์ ์ํ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก Role
, Permission
, Access Control List(ACL)
๋ฑ์ ๊ธฐ์ค์ผ๋ก ํ๋จํ๋ค.
์ธ์ฆ ์คํจ -> ์์ฒญ ์์ฒด ๊ฑฐ์ (401 Unauthorized)
์ธ๊ฐ ์คํจ -> ์ ๊ทผ ๊ฑฐ์ (403 Forbidden)
๐ช์ฟ ํค
์น ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )์๊ฒ ์ ์ฅํ๋ผ๊ณ ์ ๋ฌํ๋ ์์ ๋ฐ์ดํฐ ์กฐ๊ฐ
๋ธ๋ผ์ฐ์ ๋ ์ฟ ํค๋ฅผ ์ ์ฅํด๋๊ณ , ๋์ผ ๋๋ฉ์ธ์ ๋ค์ ์์ฒญ์ ๋ณด๋ผ ๋ ์๋์ผ๋ก ํด๋น ์ฟ ํค๋ฅผ ํจ๊ป ์ ์กํ๋ค.
์ฟ ํค์ ๋์ ํ๋ฆ
1. ์๋ฒ->ํด๋ผ์ด์ธํธ (Set-Cookie ํค๋)
๋ก๊ทธ์ธ ์ฑ๊ณต ์, ์๋ฒ๊ฐ HTTP ์๋ต ํค๋์ Set-Cookie
๋ฅผ ํฌํจํ์ฌ ์ฟ ํค๋ฅผ ์ค์ ํ๋ค.
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; HttpOnly; Secure; Path=/; Max-Age=3600
2. ํด๋ผ์ด์ธํธ ์ ์ฅ ๋ฐ ์๋ ์ ์ก
๋ธ๋ผ์ฐ์ ๊ฐ ์ฟ ํค๋ฅผ ์ ์ฅํ๊ณ , ์ดํ ๊ฐ์ ๋๋ฉ์ธ ์์ฒญ ์ Cookie
ํค๋์ ํด๋น ์ฟ ํค๋ฅผ ์๋์ผ๋ก ํฌํจํ๋ค.
GET /profile HTTP/1.1
Host: example.com
Cookie: sessionId=abc123
3. ์๋ฒ๋ ์ฟ ํค ์ ๋ณด๋ฅผ ์ฝ์ด ์์ฒญ ์ฒ๋ฆฌ
์๋ฒ๋ Cookie
ํค๋๋ฅผ ํตํด ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณํ๊ฑฐ๋ ์ธ์ฆ ์ํ๋ฅผ ํ๋จํ๋ค.
์ฟ ํค์ ์ฅ๋จ์
โ ์ฅ์
- ํด๋ผ์ด์ธํธ์ ์ ์ฅ๋์ด ์๋ฒ ๋ถํ๊ฐ ์ ์ (Stateless)
- HTTP ์์ฒญ๋ง๋ค ์๋ ์ ์ก๋์ด ์ธ์ฆ ์ ๋ณด ์ ์ก ํธ๋ฆฌ
- ๋๋ฉ์ธ๋ณ๋ก ์ ์ฅ๋์ด ๋ณด์ ์ ์ฑ ์ ์ฉ ๊ฐ๋ฅ
โ๋จ์
- ์ฟ ํค ์์ฒด๋ ํด๋ผ์ด์ธํธ ์ ์ฅ -> ํ์ทจ ์ํ์ด ์๋ค(XSS, MITM ๊ณต๊ฒฉ)
- ์ ์ฅ ์ฉ๋์ด ์ ๋ค(4KB ๋ด์ธ)
- ๋ฏผ๊ฐ ์ ๋ณด ์ ์ฅ ๋ถ๊ฐ(์ํธํ ํ์)
- CSRF ๊ณต๊ฒฉ ์ทจ์ฝ(์ถ๊ฐ ๋์ฑ ํ์)
โญ์ฟ ํค๋ ํด๋ผ์ด์ธํธ ์ ์ฅ ํน์ฑ์, ๋ณด์ ์ทจ์ฝ์ ๋ณด์์ ์ํด ๋ฐ๋์ HttpOnly, Secure, SameSite ์ต์ ์ ์ ์ ํ ์ค์ ํด์ผ ํ๋ค.
๐ฆ์ธ์
์๋ฒ ์ธก์์ ์ฌ์ฉ์ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์
ํด๋ผ์ด์ธํธ๋ ์ค์ง ๊ณ ์ ์ธ์ ID๋ง ์ฟ ํค๋ก ์ ์ฅํ๊ณ , ์๋ฒ๋ ์ธ์ ID๋ฅผ ํค๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค.
์ธ์ ๋์ ํ๋ฆ
1. ํด๋ผ์ด์ธํธ๊ฐ ๋ก๊ทธ์ธ ์์ฒญ ์ ์ก
2. ์๋ฒ๊ฐ ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ๊ณ ์ ํ ์ธ์
ID ์์ฑ(UUID ๋ฑ)
3. ์๋ฒ๋ ์ธ์
์คํ ๋ฆฌ์ง(๋ฉ๋ชจ๋ฆฌ, Redis, DB ๋ฑ)์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅ
{ sessionId: { userId: 123, roles: ['admin'] } }
4. ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ Set-Cookie: sessinId=xyz
ํค๋ ์ ์ก
5. ํด๋ผ์ด์ธํธ๋ ์ฟ ํค์ sessionId
์ ์ฅ
6. ์ดํ ๋ชจ๋ ์์ฒญ์ Cookie: sessionId=xyz
ํค๋ ํฌํจ
7. ์๋ฒ๋ ์ธ์
ID๋ก ์ ์ฅ๋ ์ฌ์ฉ์ ์ํ๋ฅผ ์กฐํ ํ ์ธ์ฆ ๋ฐ ์ธ๊ฐ ์ฒ๋ฆฌ
์ธ์ ์ ์ฅ๋จ์
โ ์ฅ์
- ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋ฒ์ ์ ์ฅ -> ์ค์ ๋ฐ์ดํฐ ๋ ธ์ถ ์ํ ์ ์
- ์ธ์ ๊ด๋ฆฌ ์ ๊ถํ ๋ณ๊ฒฝ, ๋ก๊ทธ์์ ์ฒ๋ฆฌ ์ฉ์ด
HttpOnly
,Secure
์ฟ ํค ์ต์ ์ผ๋ก ๋ณด์ ๊ฐํ ๊ฐ๋ฅ
โ๋จ์
- ์๋ฒ ๋ฉ๋ชจ๋ฆฌ, ์ ์ฅ์ ๋ถ๋ด(๋๊ท๋ชจ ์ฌ์ฉ์์ ๋ถ์ ํฉ)
- ์๋ฒ๊ฐ ์ํ๋ฅผ ์ ์งํด์ผ ํ๋ฏ๋ก, ์์ Stateless๊ฐ ์๋
- ๋ถํ ๋ถ์ฐ ํ๊ฒฝ์์ ์ธ์ ๋๊ธฐํ(์ธ์ ํด๋ฌ์คํฐ๋ง ํ์)
๐ชJWT
Json Web Token, JSON ๊ฐ์ฒด๋ฅผ Base64URL๋ก ์ธ์ฝ๋ฉํ๊ณ , ๋น๋ฐํค๋ก ์๋ช ํ์ฌ ํด๋ผ์ด์ธํธ์ ์ ๋ฌํ๋ ํ ํฐ
์๋ฒ๋ ๋ณ๋์ ์ธ์ ์ ์ฅ ์์ด ํ ํฐ ๊ฒ์ฆ๋ง์ผ๋ก, ์ธ์ฆ์ ์ํํ ์ ์๋ค. JWT๋ Stateless ์ธ์ฆ ๊ตฌํ์ ์ต์ ํ๋ ๋ฐฉ์์ผ๋ก, SPA, ๋ชจ๋ฐ์ผ API, ๋ง์ดํฌ๋ก์๋น์ค์์ ํญ๋๊ฒ ์ฌ์ฉ๋๋ค.
JWT ๊ตฌ์กฐ ๋ฐ ์ํธํ ์๊ณ ๋ฆฌ์ฆ ์ข ๋ฅ
JWT๋ Header, Payload, Verify Signature 3๋ถ๋ถ์ผ๋ก ๋๋๋ค.
๊ตฌ๋ถ | ์ค๋ช | ์์ (Base64URL ์ธ์ฝ๋ฉ) |
---|---|---|
Header | ํ ํฐ ํ์ ๊ณผ ์๋ช ์๊ณ ๋ฆฌ์ฆ ๋ช ์ | { "alg": "HS256", "typ": "JWT" } |
Payload | ์ค์ ์ธ์ฆ ์ ๋ณด(ํด๋ ์) ํฌํจ | { "userId": 123, "admin": true, "iat": 1234567890 } |
Signature | Header + Payload๋ฅผ ๋น๋ฐํค๋ก ์๋ช ํ ๊ฐ (๋ณ์กฐ ๋ฐฉ์ง) | HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) |
JWT์ ์๋ช ๊ฒ์ฆ์ JWT๊ฐ ์ค๊ฐ์ ํ์ทจ๋๊ฑฐ๋ ์กฐ์๋์์๋์ ๋ฐฉ์ด ๋ฉ์ปค๋์ฆ์ ์ดํดํ๋ ๋ฐ ํ์์ ์ด๋ค. ํ์ด๋ก๋๊ฐ ํ์ทจ๋๊ฑฐ๋ ๋ณ์กฐ๋์๋๋ผ๋, JWT์ ์๋ช ๊ฒ์ฆ ๊ณผ์ ์์ ๋ณด์์ด ์๋ํ๋ค. JWT๋ ๋ฐ๊ธ ์, ์๋ฒ๊ฐ ํค๋์ ํ์ด๋ก๋๋ฅผ ์ด์ด ๋ถ์ธ ๋ฌธ์์ด์ ๋น๋ฐ ํค๋ฅผ ์ฌ์ฉํด ์๋ช ์ ์์ฑํ๋ค. ์ด ์๋ช ์ ํด๋ผ์ด์ธํธ๊ฐ ์กฐ์ํ ์ ์์ผ๋ฉฐ, ํ ํฐ ์์ฒด์ ํฌํจ๋์ด ์ ๋ฌ๋๋ค.
์๋ฒ๋ ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ JWT๋ฅผ ๊ฒ์ฆํ ๋, ํค๋์ ํ์ด๋ก๋๋ฅผ ๋๊ฐ์ด ์ด์ด ๋ถ์ฌ ์๋ฒ์ ์ ์ฅ๋ ๋น๋ฐ ํค๋ก ๋ค์ ์๋ช ์ ์์ฑํ๋ค. ๊ทธ ํ ์๋ก ์์ฑํ ์๋ช ๊ณผ ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ JWT์ ์๋ช ์ ๋น๊ตํ๋ค. ๋ง์ฝ ํ์ด๋ก๋๊ฐ ์กฐ์๋์๋ค๋ฉด, ์๋ก์ด ์๋ช ๊ณผ ๊ธฐ์กด ์๋ช ์ด ์ผ์นํ์ง ์๊ธฐ ๋๋ฌธ์ ์๋ฒ๋ ์ด ํ ํฐ์ ์ ํจํ์ง ์์ ๊ฒ์ผ๋ก ํ๋จํ๊ณ ์์ฒญ์ ๊ฑฐ๋ถํ๋ค.
์ฆ, JWT๋ ํ์ด๋ก๋๊ฐ ์ธ๋ถ์ ๋ ธ์ถ๋๋๋ผ๋ ์๋ช ๊ฒ์ฆ์ ํตํด ์ค๊ฐ์์ ์.๋ณ์กฐ๋ ํ ํฐ์ ์๋ณํ๊ณ ์ฐจ๋จํ ์ ์๋ค.
์ฃผ์ ์๋ช ์๊ณ ๋ฆฌ์ฆ ์ข ๋ฅ
์๊ณ ๋ฆฌ์ฆ | ์ค๋ช | ํน์ง |
---|---|---|
HS256 | HMAC with SHA-256 | ๋์นญํค ์๋ช , ํค ๊ด๋ฆฌ๊ฐ ๊ฐ๋จํ์ง๋ง ํค ๋ ธ์ถ์ ์ํ |
RS256 | RSA with SHA-256 | ๋น๋์นญํค ์๋ช , ๊ณต๊ฐํค/๊ฐ์ธํค ์ ์ฌ์ฉ, ๋ณด์ ์ฐ์ |
ES256 | ECDSA with P-256 curve and SHA-256 | ๋น๋์นญํค ์๋ช , ํค ๊ธธ์ด ์งง๊ณ ํจ์จ์ |
HS256์ ๋๋ค์ ๊ฐ๋จํ API์์ ์ฌ์ฉํ์ง๋ง, ๋ณด์ ์๊ตฌ๊ฐ ๋์ ๊ฒฝ์ฐ RS256๊ฐ์ ๋น๋์นญํค ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ์ ๊ถ์ฅํ๋ค.
JWT ์ธ์ฆ ํ๋ฆ ์์ธ
JWT๋ฅผ ์ฌ์ฉํ ๋๋ ๋ณดํต Access Token
๊ณผ Refresh Token
๋ ๊ฐ์ง ํ ํฐ์ ํจ๊ป ์ด์ฉํ๋ค.
ํ ํฐ ์ข ๋ฅ | ์ญํ | ์ ํจ ๊ธฐ๊ฐ ์์ | ์ ์ฅ ์์น | ๋ณด์ ๊ณ ๋ ค์ฌํญ |
---|---|---|---|---|
Access Token | API ์์ฒญ ์ ์ธ์ฆ ์ ๋ณด๋ก ์ฌ์ฉ, ์งง์ ์ ํจ ๊ธฐ๊ฐ (ex: 15๋ถ ~ 1์๊ฐ) | 15๋ถ ~ 1์๊ฐ | ํด๋ผ์ด์ธํธ ๋ฉ๋ชจ๋ฆฌ ๋๋ ์ฟ ํค | ํ์ทจ ์ ์ํ, ์งง์ ์๋ช ์ผ๋ก ํผํด ์ ํ |
Refresh Token | Access Token ์ฌ๋ฐ๊ธ ์์ฒญ์ ์ฌ์ฉ, ๊ธด ์ ํจ ๊ธฐ๊ฐ (ex: 7์ผ ~ 30์ผ) | ๋ฉฐ์น ~ ๋ช ์ฃผ | HttpOnly, Secure ์ฟ ํค ๊ถ์ฅ | ์๋ฒ์์ ์ ์ฅ(๊ถ์ฅ), ํ์ทจ ์ ์ํ ํผ |
1. ๋ก๊ทธ์ธ ์ฑ๊ณต->ํ ํฐ ๋ฐ๊ธ
- ์๋ฒ๋ ์ฌ์ฉ์ ์ธ์ฆ ํ,
Access Token
๊ณผRefresh Token
์ ๋ชจ๋ ์์ฑํ๋ค. Access Token
์ API ์ ๊ทผ์ ์ํด ํด๋ผ์ด์ธํธ์ ๋ฐ๋ก ์ ๋ฌ๋๋ค.Refresh Token
์ ๋ณด์ ์HttpOnly
์ฟ ํค ๋ฑ ํด๋ผ์ด์ธํธ ์คํฌ๋ฆฝํธ ์ ๊ทผ์ด ์ ํ๋ ์ ์ฅ์์ ์ ์ฅํ๋ ๊ฒ์ด ์ข๋ค.
2. API ์์ฒญ ์ Access Token ์ฌ์ฉ
- ํด๋ผ์ด์ธํธ๋ ๋งค API ์์ฒญ์
Authorization: Bearer <Access Token>
ํค๋๋ฅผ ํฌํจํด ๋ณด๋ธ๋ค. - ์๋ฒ๋
Access Token
์๋ช ๋ฐ ๋ง๋ฃ ์๊ฐ(exp
)์ ๊ฒ์ฌํด ์ ํจํ๋ฉด ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
3. Access Token ๋ง๋ฃ->Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ ์์ฒญ
Access Token
์ด ๋ง๋ฃ๋๋ฉด ํด๋ผ์ด์ธํธ๋Refresh Token
์ ์ฌ์ฉํด ์๋ฒ์ ์๋ก์ดAccess Token
๋ฐ๊ธ ์์ฒญ์ ํ๋ค.- ์๋ฒ๋
Refresh Token
์ ๊ฒ์ฆํ๊ณ , ์ ํจํ๋ค๋ฉด ์Access Token
์ ๋ฐ๊ธํด ํด๋ผ์ด์ธํธ์ ์ ๋ฌํ๋ค. - ๋ง์ฝ
Refresh Token
๋ ๋ง๋ฃ๋์๊ฑฐ๋ ์ ํจํ์ง ์์ผ๋ฉด ํด๋ผ์ด์ธํธ๋ ๋ค์ ๋ก๊ทธ์ธ์ ์๊ตฌ๋ฐ๋๋ค.
4. ๋ก๊ทธ์์ ์ฒ๋ฆฌ
- ํด๋ผ์ด์ธํธ๋
Access Token
๊ณผRefresh Token
์ ๋ชจ๋ ์ญ์ ํ๋ค. - ์๋ฒ์์๋ (Refresh Token์ ์ ์ฅํ๋ ๊ฒฝ์ฐ) ํด๋น ํ ํฐ์ ๋ฌดํจํ ์ฒ๋ฆฌํ๋ค.
Access Token
์ ์งง์ ์ ํจ ๊ธฐ๊ฐ์ผ๋ก ์์ฐ ๋ง๋ฃ๋๋ฏ๋ก ๋ณ๋ ์๋ฒ ์ ์ฅ/์ญ์ ๊ฐ ์์ ์ ์๋ค.
JWT ํ ํฐ ์ ํจ ๊ธฐ๊ฐ ๊ด๋ฆฌ ํ
- Access Token์ ๋งค์ฐ ์งง๊ฒ(10~60๋ถ) ์ค์ ํด ํ์ทจ ์ ํผํด๋ฅผ ์ต์ํํ๋ค.
- Refresh Token์ ์๋์ ์ผ๋ก ๊ธธ๊ฒ(์ผ์ฃผ์ผ~ํ๋ฌ) ์ค์ ํ๋, ์ฌ๋ฐ๊ธ ๋ก์ง๊ณผ ํ์ทจ ๊ฐ์ง๊ฐ ํ์ํ๋ค.
- Refresh Token์ ์๋ฒ์ ์ ์ฅํ๊ฑฐ๋ ๋ธ๋๋ฆฌ์คํธ๋ฅผ ์ด์ํด ์ ํจ์ฑ ๊ด๋ฆฌ๋ฅผ ๊ถ์ฅํ๋ค.
- ์ฌ๋ฐ๊ธ ์ Refresh Token ์์ฒด๋ ๊ฐฑ์ ํ๋ ์ ๋ต(rolling tokens)์ผ๋ก ๋ณด์ ๊ฐํ๊ฐ ๊ฐ๋ฅํ๋ค.
JWT ํ ํฐ ์์ Payload
{
"userId": 123,
"roles": ["user"],
"iat": 1650123456, // ๋ฐ๊ธ ์๊ฐ (Issued At)
"exp": 1650127056 // ๋ง๋ฃ ์๊ฐ (Expiration Time) - 1์๊ฐ ํ
}
ํ ํฐ ์ ์ฅ ์์น๋ณ ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก
ํ ํฐ,,,์ด๋์ ์ ์ฅํ๋๊ฒ ์ ์ผ ์ข์๊น ๊ถ๊ธํ์ฌ ์ฐพ์๋ณด์๋ค.
์ ์ฅ ์์น | ์ค๋ช | ์ฅ์ | ๋จ์ / ์ํ์์ | ๋ณด์ ๊ถ์ฅ ์ฌํญ |
---|---|---|---|---|
LocalStorage | ๋ธ๋ผ์ฐ์ ์ localStorage ์ ์ ์ฅ |
- ๊ฐํธํ๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์ - SPA์์ ์์ฃผ ์ฌ์ฉ๋จ |
- XSS ๊ณต๊ฒฉ์ ์ทจ์ฝ (์คํฌ๋ฆฝํธ๋ก ํ์ทจ ๊ฐ๋ฅ) | - ๋ฏผ๊ฐํ ํ ํฐ ์ ์ฅ ์ง์ - XSS ๋ฐฉ์ด ๊ฐํ (CSP, ์ ๋ ฅ ๊ฒ์ฆ ๋ฑ) |
SessionStorage | ๋ธ๋ผ์ฐ์ ์ sessionStorage ์ ์ ์ฅ |
- ํญ ๋ณ ๋ถ๋ฆฌ ๊ฐ๋ฅ - ๋ธ๋ผ์ฐ์ ์ข ๋ฃ ์ ์ญ์ ๋จ |
- XSS ๊ณต๊ฒฉ์ ์ทจ์ฝ | - XSS ๋ฐฉ์ด ๊ฐํ ํ์ - ๋ฏผ๊ฐ ์ ๋ณด ์ ์ฅ ์ต์ํ |
HttpOnly ์ฟ ํค | ์๋ฒ๊ฐ Set-Cookie ํค๋๋ก ๋ฐํํ๋ HttpOnly ์ฟ ํค |
- JavaScript ์ ๊ทผ ๋ถ๊ฐ → XSS ๊ณต๊ฒฉ ๋ฐฉ์ด ๊ฐ๋ฅ - ์๋์ผ๋ก ์์ฒญ์ ํฌํจ |
- CSRF ๊ณต๊ฒฉ์ ์ทจ์ฝ (์๋ ์ ์ก ํน์ฑ) | - CSRF ํ ํฐ ์ฌ์ฉ ๋๋ SameSite ์์ฑ ์ค์ - Secure, SameSite=strict ๊ถ์ฅ |
๋ฉ๋ชจ๋ฆฌ (JS ๋ณ์) | JavaScript ์คํ ์ค ๋ฉ๋ชจ๋ฆฌ์๋ง ์์ ์ ์ฅ | - ํ์ทจ ์ํ ์ ์ - ๋ธ๋ผ์ฐ์ ๋ซ์ผ๋ฉด ์๋ฉธ |
- ์๋ก๊ณ ์นจ ์ ์์ค - SPA์์ ์ํ ์ ์ง ์ด๋ ค์ |
- ์งง์ ์ ํจ๊ธฐ๊ฐ Access Token์ ์ ํฉ - ๋ฆฌํ๋ ์ ์ ๋ณ๋ ์ ์ฅ์ ํ์ |
Access Token์ localStorage
๋ sessionStorage
์ ์ ์ฅํ๋ฉด, ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค. ์ด ๊ฒฝ์ฐ ์ฌ์ดํธ์ XSS ์ทจ์ฝ์ ์ด ์กด์ฌํ๋ฉด ์
์ฑ ์คํฌ๋ฆฝํธ๊ฐ ํ ํฐ์ ํ์ทจํ ์ ์๋ค.
- ํ ํฐ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ ๊ทผ ๊ฐ๋ฅํ ์ ์ฅ์์ ์ ์ฅํ์ง ์๋ ๊ฒ์ด ์ข๋ค.
- Access Token์ ๋ฉ๋ชจ๋ฆฌ์๋ง ์์๋ก ์ ์ฅํ๊ณ , ํ์ด์ง ๋ฆฌ๋ก๋ ์ ์ฌ๋ผ์ง๋๋ก ๊ด๋ฆฌํ๋ค.
// ๋ก๊ทธ์ธ ์ฑ๊ณต ํ Access Token ์ ์ฅ
let accessToken: string | null = null;
function login() {
api.post('/login', { email, password }).then(res => {
accessToken = res.data.accessToken; // ๋ฉ๋ชจ๋ฆฌ์๋ง ์ ์ฅ
});
}
Refesh Token์ ๋ฏผ๊ฐํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ผ๋ฉฐ, ์ ํจ ์๊ฐ์ด ๊ธธ๊ธฐ ๋๋ฌธ์ ํ์ทจ ์ ํผํด๊ฐ ํฌ๋ค. ํนํ ๋ธ๋ผ์ฐ์ ์ localStorage
๋ sessionStorage
์ ์ ์ฅํ๋ฉด XSS๋ก ํ์ทจ๋ ์ ์๋ค. ๋ฐ๋๋ก, ์ฟ ํค์ ์ ์ฅํ๋ฉด ์๋์ผ๋ก ์์ฒญ๊ณผ ํจ๊ป ์ ์ก๋๋ฏ๋ก CSRF์ ๋
ธ์ถ๋ ์ ์๋ค. ๋ฐ๋ผ์ Refresh Token์ ๋ค์ ์ต์
์ ์ฌ์ฉํ์ฌ ์ฟ ํค์ ์ ์ฅํ๋ ๊ฒ์ด ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก์ด๋ค.
HttpOnly
: ์๋ฐ์คํฌ๋ฆฝํธ์์ ์ ๊ทผ ๋ถ๊ฐ๋ฅ-XSS ๋ฐฉ์ดSecure
: HTTPS์์๋ง ์ ์ก-์ค๊ฐ์ ๊ณต๊ฒฉ ๋ฐฉ์ดSameSite=Strict
๋๋Lax
: ๋ค๋ฅธ ์ถ์ฒ์ ์์ฒญ์ ์๋ ์ ์ก๋์ง ์๋๋ก ์ ํ-CSRF ๋ฐฉ์ด
Set-Cookie: refreshToken=abc.def.ghi;
HttpOnly;
Secure;
SameSite=Strict;
Path=/refresh;
Max-Age=604800;
๋ณด์ ์ทจ์ฝ์ ์ ๋ฆฌ
์ข ๋ฅ | ์ค๋ช | Access Token ์ํฅ | Refresh Token ์ํฅ |
XSS | ์ ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ๋ธ๋ผ์ฐ์ ์์ ํ์ทจ | localStorage ์ ์ฅ ์ ์ํ | ์ฟ ํค์ HttpOnly ์์ผ๋ฉด ์ํ |
CSRF | ์ฌ์ฉ์์ ์ธ์ฆ ์ํ๋ฅผ ์ ์ฉํ ์์ฒญ ์์กฐ | ๊ฑฐ์ ์์ (ํค๋ ์๋ ์ค์ ํ์) | ์๋ ์ฟ ํค ์ ์ก ์ ์ํ (SameSite๋ก ๋ฐฉ์ง ๊ฐ๋ฅ) |
์ ์ฅ ์์น ์ ํ ๊ธฐ์ค ์์ฝ
์ข ๋ฅ | ์ ์ฅ ์์น | ์ด์ /๋ชฉ์ |
Access Token | ๋ฉ๋ชจ๋ฆฌ(RAM) | XSS ๊ณต๊ฒฉ ๋ฐฉ์ง. ์๋ก๊ณ ์นจ ์ ์๋ ์ด๊ธฐํ. |
Refresh Token | HttpOnly ์ฟ ํค | XSS ๋ฐฉ์ง + Secure, SameSite๋ก CSRF ๋ฐฉ์ง ๊ฐ๋ฅ |
๐กAccess Token์ ์ธ์ ๋ ์ฌ๋ฐ๊ธ ๊ฐ๋ฅํ ์๋ชจํ์ด๋ฏ๋ก, ๋ณด์๋ณด๋ค๋ ๋ฏผ์ฒฉ์ฑ๊ณผ ์์์ฑ ์ค์ฌ์ผ๋ก ๋ค๋ฃจ๊ณ , Refresh Token์ ์ฌ๋ฐ๊ธ์ ํต์ฌ ์ด์ ์ด๋ฏ๋ก ์ต์ฐ์ ๋ณด์ ๋์์ผ๋ก ๋ค๋ฃฌ๋ค!
JWT ์ค์ต ์์
JWT ์ค์ต์ ์ํด์ ๋จผ์ jsonwebtoken ํจํค์ง๋ฅผ ์ค์นํด์ค๋ค.
var jwt = require("jsonwebtoken"); //jwt ๋ชจ๋ ์ํ
//์๋ช
=ํ ํฐ ๋ฐํ
var token = jwt.sign({ foo: "bar" }, "shhhhhh");
//token ์์ฑ ์, jwt ์๋ช
์ ํ๋ค! (ํ์ด๋ก๋, ๋๋ง์ ์ํธํค) + SHA256
console.log(token);
//๊ฒ์ฆ
//๋ง์ฝ์ ๊ฒ์ฆ์ ์ฑ๊ณตํ๋ฉด, ํ์ด๋ก๋ ๊ฐ ํ์ธ
var decoded = jwt.verify(token, "shhhhhh");
console.log(decoded);
๐๏ธํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ: .env
ํ์ผ ๊ฐ๋
๋ฐ ํ์ฉ
ํ๊ฒฝ ๋ณ์๋ ์ค์ํ ๋น๋ฐ ์ ๋ณด๋ฅผ ์ฝ๋์ ๋ถ๋ฆฌํด ์์ ํ๊ฒ ๊ด๋ฆฌํ๊ธฐ ์ํ ๋ฐฉ๋ฒ
.env
ํ์ผ์ ๋ฏผ๊ฐ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค(DB ์ ์, JWT ๋น๋ฐ ํค ๋ฑ)- ์ผ๋ฐ์ ์ผ๋ก, ํ๋ก์ ํธ ๋ฃจํธ์ ์์นํ๋ค.
PORT=3000
DB_HOST=localhost
DB_USER=admin
DB_PASS=secret
JWT_SECRET=superSecretKey123!
.gitignore
์ ์ถ๊ฐํ์ฌ Git ๋ ธ์ถ์ ๋ฐฉ์งํด์ผ ํ๋ค.dotenv
ํจํค์ง๋ก ๋ก๋ํ๋ค.
require('dotenv').config();
const jwtSecret = process.env.JWT_SECRET;
Access Token์ด๋ Refresh Token์ ํ๋ก์ ํธ ํ๋ฉด์ ๋ช ๋ฒ ์จ๋ดค์ง๋ง, ์ฌ์ค “๋ณด์์ ์ผ๋ก ์ด๊ฒ ๋ง๋?” ๊ณ ๋ฏผํด๋ณธ ์ ์ ๊ฑฐ์ ์์๋ค. JWT๋ ๊ทธ๋ฅ ๋ค๋ค ์ฐ๋๊น ๋๋ ๋ฐ๋ผ ์ฐ๋ ๋๋์ด์๊ณ ..
๊ทผ๋ฐ ์ด๋ฒ์ ๊ณต๋ถํ๋ฉด์ ์ฟ ํค vs ์ธ์ , JWT ๊ตฌ์กฐ, ์๋ช ์ ์ญํ ๊ฐ์ ๊ฑธ ํ์คํ ์๊ฒ ๋์๊ณ , ์ด์ ๋ ๋จ์ํ “๋์๋ง ๊ฐ๋ฉด ๋๋ค”๊ฐ ์๋๋ผ “์ด๊ฒ ์ง์ง ์์ ํ ๋ฐฉ์์ธ๊ฐ?”๋ฅผ ๊ณ ๋ฏผํ๊ฒ ๋๋ ๊ฒ ๊ฐ๋ค. ํ ํฐ์ ์ด๋์ ์ ์ฅํ๋๋์ ๋ฐ๋ผ ์ด๋ค ๊ณต๊ฒฉ์ ๋ง์ ์ ์๊ณ , ์ด๋ค ์ํ์ด ์๊ธธ ์ ์๋์ง๋ฅผ ์๊ณ ๋๋๊น ์ด์ ์ ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ตฌํ์ ํ์๋, ์ ์ ๋์๋ณด๊ณ ๋ฐ์ฑํ๊ฒ ๋ ๊ฒ ๊ฐ๋ค.
์์ผ๋ก๋ ์ธ์ฆ/์ธ๊ฐ ๊ตฌํํ ๋๋ ๊ทธ๋ฅ ์ต์ํ ๋ฐฉ์ ๋ง๊ณ , ์ํฉ์ ๋ง๋ ๋ฐฉ์์ด ๋ญ์ง, ๋ณด์์ ์ผ๋ก๋ ๋ฉ๋์ด ๊ฐ๋ ๊ตฌ์กฐ์ธ์ง ๋จผ์ ์๊ฐํด๋ด์ผ๊ฒ ๋ค.
'๐๏ธํ๋ก๊ทธ๋๋จธ์ค ๋ฐ๋ธ์ฝ์ค > TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
nodejs ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ (1) | 2025.05.22 |
---|---|
[๋ฐ๋ธ์ฝ์ค] ํ์ ํด๊ณผ ํ๋ก์ ํธ ๊ด๋ฆฌ ๋ฐฉ๋ฒ๋ก (0) | 2025.05.12 |
[๋ฐ๋ธ์ฝ์ค] ๋ฐฑ์๋ ํํธ 5 (0) | 2025.05.01 |
[๋ฐ๋ธ์ฝ์ค] GUI ํ์ฉ๊ณผ API ์ฐ๋์ผ๋ก ๋ฐฐ์ฐ๋ DB ํ์ฉ ์ฌํ (0) | 2025.05.01 |
[๋ฐ๋ธ์ฝ์ค] ์ฌ์ฉ์-๊ฒ์๊ธ ์์ ๋ก ๋ฐฐ์ฐ๋ ํ ์ด๋ธ ์ค๊ณ์ ์ปฌ๋ผ ์์ฑ, ์ ์ฝ์กฐ๊ฑด (0) | 2025.04.28 |