์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด, ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ ์งํด์ผ ํ ๋๊ฐ ๋ง๋ค.
๊ณผ๊ฑฐ์๋ ์ธ์ (session)์ ์ฌ์ฉํ์ง๋ง, ์ต๊ทผ์๋ JWT (JSON Web Token)์ด ๋๋ฆฌ ์ฌ์ฉ๋๊ณ ์๋ค.
๊ทธ๋ ๋ค๋ฉด JWT๊ฐ ๋ฌด์์ด๊ณ , ์ด๋ป๊ฒ ๋์ํ๋์ง ์ ๋ฆฌํด๋ณด์.
1. JWT๋ ๋ฌด์์ธ๊ฐ?
JWT(JSON Web Token)๋ ์ฌ์ฉ์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ ์กํ๊ธฐ ์ํ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ด๋ค.
์ฝ๊ฒ ๋งํด, ๋ก๊ทธ์ธํ๋ฉด ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ํธํ๋ ํ ํฐ ํํ๋ก ๋ฐ๊ธํ์ฌ ํด๋ผ์ด์ธํธ๊ฐ ์ ์ฅํ๊ณ ,
์ดํ ์์ฒญํ ๋๋ง๋ค ํ ํฐ์ ๋ณด๋ด ์ธ์ฆ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด๋ค.
- โ JWT๋ ์์ฒด์ ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํฌํจํ๊ธฐ ๋๋ฌธ์, ๋ณ๋์ ์ธ์ ์ ์ฅ์๊ฐ ํ์ ์์
- โ RESTful API์ ์ ๋ง๋ ์ธ์ฆ ๋ฐฉ์ → ์๋ฒ๊ฐ ์ฌ์ฉ์์ ์ํ๋ฅผ ์ ์งํ์ง ์์๋ ๋จ (Stateless)
- โ ํ ํฐ์ ์ด์ฉํ ์ธ์ฆ ๋ฐฉ์์ผ๋ก, ๋ค์ํ ์๋น์ค(๋ชจ๋ฐ์ผ, ์น, API ์๋ฒ)์์ ์ฌ์ฉ ๊ฐ๋ฅ
2. JWT์ ๊ตฌ์กฐ
JWT๋ ์ธ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋๋ค.
ํค๋(Header).ํ์ด๋ก๋(Payload).์๋ช (Sigature)
์๋ฅผ ๋ค์ด, ์๋์ ๊ฐ์ JWT๊ฐ ์๋ค๊ณ ํด๋ณด์.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkppbiIsImlhdCI6MTUxNjIzOTAyMn0. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
์ด์ ๊ฐ๊ฐ์ ๋ถ๋ถ์ ์์ธํ ์ดํด๋ณด์.
โ Header (ํค๋)
JWT์ ํ์ ๊ณผ ์๋ช ์๊ณ ๋ฆฌ์ฆ์ ์ ์ํ๋ ๋ถ๋ถ์ด๋ค.
{
"alg": "HS256", // HMAC SHA256 ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ
"typ": "JWT"
}
์ ์ ๋ณด๋ฅผ Base64 URL ์ธ์ฝ๋ฉํ๋ฉด JWT์ ์ฒซ ๋ฒ์งธ ๋ถ๋ถ์ด ๋๋ค.
โก Payload (ํ์ด๋ก๋, ์ฌ์ฉ์ ์ ๋ณด)
ํ์ด๋ก๋์๋ ์ฌ์ฉ์์ ์ ๋ณด(ํด๋ ์, Claims)๊ฐ ํฌํจ๋๋ค.
{
"sub": "1234567890", // ์ฌ์ฉ์ ID
"name": "Jin", // ์ฌ์ฉ์ ์ด๋ฆ
"iat": 1516239022 // ํ ํฐ ๋ฐ๊ธ ์๊ฐ (Unix Timestamp)
}
์ด ์ ๋ณด ์ญ์ Base64 URL ์ธ์ฝ๋ฉ๋์ด JWT์ ๋ ๋ฒ์งธ ๋ถ๋ถ์ด ๋๋ค.
โข Signature (์๋ช , ๋ณด์ ์์)
์๋ช ์ JWT์ ์๋ณ์กฐ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํ ๊ฐ์ผ๋ก, ๋น๋ฐ ํค๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ๋๋ค.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
์ฆ, ์๋ช ์ด ์ผ์นํ๋์ง ๊ฒ์ฆํ๋ฉด ํ ํฐ์ด ๋ณ์กฐ๋์ง ์์๋ค๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
3. JWT๋ ์ด๋ป๊ฒ ์ฌ์ฉ๋ ๊น?
(1) ๋ก๊ทธ์ธ ์ JWT ๋ฐ๊ธ
- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ์๋ฒ๋ ID, ๋น๋ฐ๋ฒํธ๋ฅผ ๊ฒ์ฆํ๋ค.
- ๊ฒ์ฆ์ด ์๋ฃ๋๋ฉด JWT๋ฅผ ์์ฑํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ๋ค.
- ํด๋ผ์ด์ธํธ๋ JWT๋ฅผ localStorage, sessionStorage, ๋๋ HttpOnly Cookie์ ์ ์ฅํ๋ค.
๐ ์์ (Spring Boot์์ JWT ๋ฐ๊ธ)
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
);
String token = jwtTokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtResponse(token));
}
(2) API ์์ฒญ ์ JWT ์ธ์ฆ
์ดํ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ API ์์ฒญ์ ๋ณด๋ผ ๋,
Authorization: Bearer <JWT> ํค๋๋ฅผ ํฌํจํ์ฌ ๋ณด๋ธ๋ค.
๐ ์์ (JWT ํฌํจ ์์ฒญ)
GET /user/profile Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR...
์๋ฒ์์๋ ํ ํฐ์ ๊ฒ์ฆํ ํ, ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํ์ธํ์ฌ ์์ฒญ์ ์ฒ๋ฆฌํ๋ค.
๐ ์์ (Spring Boot์์ JWT ๊ฒ์ฆ)
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
return claims.getSubject(); // username ๋ฐํ
}
4. JWT์ ์ฅ์ ๊ณผ ๋จ์
โ JWT์ ์ฅ์
โ Stateless (๋ฌด์ํ์ฑ) → ์๋ฒ๊ฐ ์ฌ์ฉ์์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์ฅํ ํ์ ์์
โ ํ์ฅ์ฑ → ์ฌ๋ฌ ์๋ฒ์์ ์ธ์ฆ์ ๊ณต์ ํ ์ ์์
โ ๋น ๋ฅธ ์ธ์ฆ → ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ์์ด ํ ํฐ๋ง์ผ๋ก ์ธ์ฆ ๊ฐ๋ฅ
โ JWT์ ๋จ์
โ ํ ํฐ ํ์ทจ ์ ๋ณด์ ์ํ → ํ์ทจ๋ JWT๋ ๋ง๋ฃ๋ ๋๊น์ง ์
์ฉ ๊ฐ๋ฅ
โ ํ ํฐ ํฌ๊ธฐ๊ฐ ํผ → ๋งค ์์ฒญ๋ง๋ค ํฌํจํด์ผ ํ๋ฏ๋ก ํธ๋ํฝ ์ฆ๊ฐ ๊ฐ๋ฅ
โ ์ธ์
๋ฌดํจํ ์ด๋ ค์ → ๋ก๊ทธ์์ ์ ๊ธฐ์กด JWT๋ฅผ ๊ฐ์ ๋ก ํ๊ธฐํ๋ ๊ฒ์ด ์ด๋ ค์
5. JWT๋ฅผ ์์ ํ๊ฒ ์ฌ์ฉํ๋ ค๋ฉด?
JWT๋ ๊ฐ๋ ฅํ ์ธ์ฆ ๋ฐฉ์์ด์ง๋ง, ๋ณด์์ ์ํด ๋ช ๊ฐ์ง ์ฃผ์ํด์ผ ํ๋ค.
โ
HTTPS ์ฌ์ฉ → ๋คํธ์ํฌ์์ JWT๊ฐ ๋
ธ์ถ๋์ง ์๋๋ก ํ๋ค.
โ
HttpOnly Cookie ์ฌ์ฉ → localStorage๋ณด๋ค๋ ๋ณด์์ฑ์ด ๋์ ์ฟ ํค ์ฌ์ฉ
โ
์งง์ ๋ง๋ฃ ์๊ฐ ์ค์ → ํ ํฐ ์ ํจ ์๊ฐ์ ์งง๊ฒ ์ค์ ํ๊ณ , ๋ฆฌํ๋ ์ ํ ํฐ ํ์ฉ
โ
์๋ช
๊ฒ์ฆ ํ์ → ๋ชจ๋ ์์ฒญ์์ JWT์ ์๋ณ์กฐ ์ฌ๋ถ๋ฅผ ๊ฒ์ฆํด์ผ ํ๋ค.
6. ์ ๋ฆฌ
โ
JWT๋ ์ธ์ฆ ์ ๋ณด๋ฅผ ํฌํจํ๋ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์์ด๋ค.
โ
Header.Payload.Signature 3๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋๋ค.
โ
๋ก๊ทธ์ธ ์ JWT๋ฅผ ๋ฐ๊ธํ๊ณ , ์ดํ API ์์ฒญ ์ ์ธ์ฆ์ ์ฌ์ฉ๋๋ค.
โ
์ธ์
์ ์ ์ฅํ ํ์๊ฐ ์์ด RESTful API์ ๊ถํฉ์ด ์ข๋ค.
โ
ํ์ง๋ง, ๋ณด์ ์ทจ์ฝ์ ์ด ์์ ์ ์์ด HTTPS, ์ฟ ํค ์ ์ฅ, ๋ง๋ฃ ์๊ฐ ์ค์ ์ด ์ค์ํ๋ค.
JWT๋ฅผ ์ ํ์ฉํ๋ฉด RESTful API์์ ์ธ์ฆ์ ํจ๊ณผ์ ์ผ๋ก ๊ตฌํํ ์ ์๋ค! ๐
'๐ Web' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Git Flow ์๋ฒฝ ์ ๋ฆฌ: ํ์ ๊ณผ ๋ฐฐํฌ๋ฅผ ์ํ Git ๋ธ๋์น ์ ๋ต (0) | 2025.02.28 |
---|---|
RESTful API๋? ๊ฐ๋ ๋ถํฐ ์ค๊ณ ์์น, ๊ตฌํ ์ ๋ฆฌ (1) | 2025.02.27 |