[๋ฐ๋ธŒ์ฝ”์Šค] ์ธ์ฆ/์ธ๊ฐ€ ์‹œ์Šคํ…œ ์ดํ•ด: ์ฟ ํ‚ค, ์„ธ์…˜, JWT์™€ ๋ณด์•ˆ ํฌ์ธํŠธ

2025. 5. 4. 21:16ยท๐Ÿ•Š๏ธํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐ๋ธŒ์ฝ”์Šค/TIL

์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๋‹ค๋ณด๋ฉด, ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ๊ถŒํ•œ ์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ฐœ๋…์„ ๋ฐ˜๋“œ์‹œ ๋งˆ์ฃผํ•˜๊ฒŒ ๋˜๊ณ , ์ด๋ฅผ ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ฟ ํ‚ค, ์„ธ์…˜, 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
'๐Ÿ•Š๏ธํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐ๋ธŒ์ฝ”์Šค/TIL' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • nodejs ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ
  • [๋ฐ๋ธŒ์ฝ”์Šค] ํ˜‘์—… ํˆด๊ณผ ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•๋ก 
  • [๋ฐ๋ธŒ์ฝ”์Šค] ๋ฐฑ์—”๋“œ ํŒŒํŠธ 5
  • [๋ฐ๋ธŒ์ฝ”์Šค] GUI ํ™œ์šฉ๊ณผ API ์—ฐ๋™์œผ๋กœ ๋ฐฐ์šฐ๋Š” DB ํ™œ์šฉ ์‹ฌํ™”
ํ‚ํ‚์ž‰
ํ‚ํ‚์ž‰
๋ฟŒ๋ก ํŠธ ๊ฐœ๋ฐœ์ž(์ง€๋ง์ƒ)์˜ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž ๋„์ „๊ธฐ
  • ํ‚ํ‚์ž‰
    monicx.dev
    ํ‚ํ‚์ž‰
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (147)
      • ๐Ÿ–ฅ๏ธdevelop (2)
        • Github (2)
        • Frontend (4)
        • Backend (5)
        • Mobile (0)
        • CS (0)
        • Three.js (0)
        • Docker (2)
      • ๐Ÿ“šbook (9)
        • npm Deep Dive (4)
      • ๐Ÿ“•review (19)
        • ์ฑ… (13)
        • ํ–‰์‚ฌ (0)
        • ํšŒ๊ณ  (2)
      • โญproject (5)
        • petiary (2)
        • ๆšŽ่ฉ  (0)
        • ์ธํ„ด (2)
      • ๐Ÿ˜ถ‍๐ŸŒซ๏ธalgorithm (0)
      • ๐Ÿ’กtips (1)
      • ๐Ÿ˜Ždaily (10)
      • ๐Ÿ•น๏ธgame (0)
      • ๐Ÿ•Š๏ธํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐ๋ธŒ์ฝ”์Šค (76)
        • TIL (51)
        • ํ”„๋กœ์ ํŠธ (17)
        • ํšŒ๊ณ  (8)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
  • ๋งํฌ

    • ๋ฒจ๋กœ๊ทธ
  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
ํ‚ํ‚์ž‰
[๋ฐ๋ธŒ์ฝ”์Šค] ์ธ์ฆ/์ธ๊ฐ€ ์‹œ์Šคํ…œ ์ดํ•ด: ์ฟ ํ‚ค, ์„ธ์…˜, JWT์™€ ๋ณด์•ˆ ํฌ์ธํŠธ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”