๐ JWT๋? (JSON Web Token)
- JWT(JSON Web Token)๋ ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ์ ๋ณด ์ ๋ฌ์ ์ํ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ ๋ฐฉ์!
- ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ ์ํ๋ฅผ ์ ์ฅํ์ง ์๊ณ (stateless) ์ธ์ฆํ ์ ์์ด์,
REST API ๋ฑ์์ ๋ง์ด ์ฌ์ฉ๋๋ค.
โ 1. JWT ๊ตฌ์กฐ
JWT๋ .(์ )์ผ๋ก ๊ตฌ๋ถ๋ 3๊ฐ์ ๋ถ๋ถ์ผ๋ก ๊ตฌ์ฑ๋๋ค.
๐ ์์
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIiLCJyb2xlIjoiQURNSU4iLCJpYXQiOjE2OTQ2MjQ4MDB9.f9XY4qkAeUpmEYAf_CtwJfpFq93j98gVwVFXD2DdeuA
๐น 1. Header (ํค๋)
- ํ ํฐ์ ํ์ (JWT)๊ณผ ์๋ช ์ ์ฌ์ฉํ ์๊ณ ๋ฆฌ์ฆ(์: HS256, RS256)์ ํฌํจ
{
"alg": "HS256",
"typ": "JWT"
}
๐น 2. Payload (ํ์ด๋ก๋)
- ์ฌ์ฉ์ ์ ๋ณด(ํด๋ ์, Claim)**๊ฐ ๋ด๊ธด ๋ถ๋ถ
- ํ ํฐ์ ํฌ๊ธฐ๊ฐ ์ปค์ง๋ฉด ๋คํธ์ํฌ ํธ๋ํฝ์ ๋ง์ด ์ก์๋จน๊ธฐ ๋๋ฌธ์ ํ์ํ ์ ๋ณด๋ง ๋ด๋๋ก ํด์ผํ๋ค!
{
"sub": "user12", // ์ฌ์ฉ์ ID
"role": "ADMIN", // ์ญํ (Role)
"iat": 1694624800 // ๋ฐ๊ธ ์๊ฐ (Unix Timestamp)
}
๐น 3. Signature (์๋ช )
- JWT๊ฐ ๋ณ์กฐ๋์ง ์์์์ ๋ณด์ฅํ๋ ์ญํ
- HMAC, RSA, ECDSA ๋ฑ์ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํด ์์ฑ
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
โ 2. JWT ํน์ง
โ ๋ฌด์ํ(stateless)
- ์๋ฒ์์ ์ธ์
์ ์ ์งํ ํ์ ์์ โ ์๋ฒ ๋ถํ ๊ฐ์
โ ์์ฒด ์ธ์ฆ(Self-contained) - ์ฌ์ฉ์ ์ ๋ณด๊ฐ ํ ํฐ์ ํฌํจ๋จ โ DB ์กฐํ ์์ด ์ธ์ฆ ๊ฐ๋ฅ
โ ์์ ํ ์๋ช (Signature) ๊ฒ์ฆ - ๋น๋ฐ ํค(HS256) ๋๋ ๊ณต๊ฐ ํค(RS256)๋ก ์๋ช โ ๋ณ์กฐ ๋ฐฉ์ง
โญ MSA์์ ์ฌ์ฉ ์ด์
- ๊ฐ ์๋น์ค ์์ฒด ์ธ์ฆ: JWT๋ ํ ํฐ ์์ฒด์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๊ธฐ ๋๋ฌธ์, ์ธ์ฆ ์๋ฒ๊ฐ ์์ด๋ ๊ฐ ์๋น์ค๊ฐ JWT๋ฅผ ํตํด ์ธ์ฆ์ ์ฝ๊ฒ ๊ฒ์ฆํ ์ ์๋ค.
- ํ์ฅ์ฑ ๋ฐ ์ ์ฐ์ฑ: ์๋ฒ ํ์ฅ(์ค์ผ์ผ๋ง)์ด๋ ๋ค๋ฅธ ์๋น์ค ์ฐ๋์์๋ ์ธ์ฆ ๋ฐ ๊ถํ ์ ๋ณด๋ฅผ ๊ณต์ ํ ์ ์์ด์ ํ์ฅ์ฑ๊ณผ ์ ์ฐ์ฑ์ด ๋ฐ์ด๋จ.
โ 3. JWT ์ธ์ฆ ํ๋ฆ
1๏ธโฃ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ์์ฒญ (ID/PW)
2๏ธโฃ ์๋ฒ๊ฐ JWT๋ฅผ ์์ฑ ํ ๋ฐํ, JWT๋ ํด๋ผ์ด์ธํธ ์ธก์ ์ ์ฅ.
3๏ธโฃ ํด๋ผ์ด์ธํธ๊ฐ JWT๋ฅผ ํฌํจํด ์์ฒญ (Authorization: Bearer <token>)
4๏ธโฃ ์๋ฒ์์ JWT ๊ฒ์ฆ ํ ์๋ต ๋ฐํ
โญ ์ธ์ ๊ธฐ๋ฐ ์ธ์ฆ๊ณผ ๋ฌ๋ฆฌ ์๋ฒ๊ฐ ๋ณ๋๋ก ์ํ(Session)๋ฅผ ์ ์ฅํ์ง ์์๋ ๋จ!
โ 4. JWT ์์ฑ
ํค ์์ฑ ๋ฐฉ์์ ๋ฐ๋ผ ์ ๊ณต, ๊ฒ์ฆ ๋ฐฉ์์ด ๋ค๋ฅด๋ค!!
- ๊ฐ๋ ฅํ ํด์ ์๊ณ ๋ฆฌ์ฆ(HMAC-SHA256, RS256 ๋ฑ) ์ฌ์ฉ
- ์ทจ์ฝํ ์๊ณ ๋ฆฌ์ฆ(์: none ์๊ณ ๋ฆฌ์ฆ) ์ฌ์ฉ โ
๐ ํค ์ ์ฅ ์์น
- ํ์ผ ์์คํ (private_key.pem, public_key.pem)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค (MySQL, PostgreSQL, Redis ๋ฑ)
- ํ๊ฒฝ ๋ณ์ ๋๋ ๋ณด์ ์๋น์ค (AWS Secrets Manager, HashiCorp Vault)
- HSM (Hardware Security Module) ๊ฐ์ ํ๋์จ์ด ๊ธฐ๋ฐ ํค ๊ด๋ฆฌ ์์คํ
โ 4-0. ํค ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ
์ผ๋ฐ์ ์ผ๋ก jsonwebtoken ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ JwtTokenProvider์ ์์ฑํ์ฌ ์ฌ์ฉ.
Private key๊ฐ ํ์ํ๋ค.
๐ gradle.build
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
โ 4-1. RSA ๋ฐฉ์ (๋น๋์นญํค ๊ธฐ๋ฐ)
โ ์ธ์ฆ ์๋ฒ(ex.auth-service)๋ ๊ฐ์ธํค(Private Key)๋ก JWT ์๋ช
โ ๊ฒ์ฆ ์๋ฒ(ex.gateway)๋ ๊ณต๊ฐํค(Public Key)๋ก JWT ๊ฒ์ฆ
๐ JWT ๋ฐ๊ธ (RSA ํค ์ฌ์ฉ)
@Component
public class JwtTokenProvider {
private final KeyPair keyPair = {keyPair}; // RSA ํค ์ (๊ณต๊ฐ ํค + ๊ฐ์ธ ํค)
public String createToken(String username, List<String> roles) {
return Jwts.builder()
.setSubject(username)
.claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1์๊ฐ ์ ํจ
.signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256) // ๊ฐ์ธ ํค๋ก ์๋ช
.compact();
}
public PublicKey getPublicKey() {
return keyPair.getPublic(); // ๊ณต๊ฐ ํค ๊ฒ์ฆ ์๋ฒ์๊ฒ ์ ๊ณต
}
}
๐ JWK Set ๊ณต๊ฐ (Auth ์๋ฒ์์ ์ ๊ณต)
@RestController
@RequestMapping("/.well-known")
public class JwkSetController {
private final JwtTokenProvider jwtTokenProvider;
public JwkSetController(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@GetMapping("/jwks.json")
public Map<String, Object> getJwks() throws Exception {
PublicKey publicKey = jwtTokenProvider.getPublicKey();
RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) publicKey)
.keyID("my-key-id") // ํค ID (์์ ๊ฐ)
.algorithm(JWSAlgorithm.RS256)
.build();
return new JWKSet(rsaKey).toJSONObject();
}
}
โ 4-2. HS256 ๋ฐฉ์ (๋์นญํค ๊ธฐ๋ฐ)
โ ์ธ์ฆ ์๋ฒ์ ๊ฒ์ฆ ์๋ฒ์ ๋์ผํ Secret key ์ฌ์ฉ.
๐ JWT ๋ฐ๊ธ (HS256 ํค ์ฌ์ฉ)
@Component
public class JwtTokenProvider {
private final String SECRET_KEY = {secret-key}; // ์์ ํ ์ ์ฅ ํ์ (ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ ๊ถ์ฅ)
private final long EXPIRATION_TIME = 1000 * 60 * 60; // 1์๊ฐ
// JWT ์์ฑ (HS256 ์ฌ์ฉ)
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY.getBytes())
.compact();
}
@SuppressWarnings("deprecation")
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY.getBytes())
.build()
.parseClaimsJws(token); // JWT์ ํ์(Header.Payload.Signature ํํ์ธ์ง) & ์๋ช
& ๋ง๋ฃ ๊ฒ์ฆ
return true;
// } catch (ExpiredJwtException e) {
// System.out.println("โ ๋ง๋ฃ๋ ํ ํฐ์
๋๋ค!");
// } catch (MalformedJwtException e) {
// System.out.println("โ ์๋ชป๋ ํ์์ ํ ํฐ์
๋๋ค!");
// } catch (SignatureException e) {
// System.out.println("โ ์๋ช
์ด ์ฌ๋ฐ๋ฅด์ง ์์ ํ ํฐ์
๋๋ค!");
} catch (Exception e) {
return false;
}
}
}
โ 5. JWT ๊ฒ์ฆ
Filter์ ๋ณ๋๋ก ์์ฑํ์ฌ @Configuration์์ ์ ์ฉํ๊ณ ,
์ JwtTokenProvider.validateTokenํจ์๋ฅผ ํธ์ถํ์ฌ jwt ํ ํฐ์ ๊ฒ์ฆํ์ฌ ์์ฒญ์ ํต๊ณผ์ํค๊ฑฐ๋ ์ฐจ๋จํ๋ค.
๐ filter๋?
JWT ์ธ์ฆ์์ ํํฐ(Filter)๋ HTTP ์์ฒญ๊ณผ ์๋ต์ ๊ฐ๋ก์ฑ์ ํน์ ์ฒ๋ฆฌ๋ฅผ ์ํํ๋ ์ปดํฌ๋ํธ.
ํค ์์ฑ ๋ฐฉ์์ ๋ฐ๋ผ ์ ๊ณต, ๊ฒ์ฆ ๋ฐฉ์์ด ๋ค๋ฅด๋ค!! ๊ฒ์ฆ ๋ฐฉ์์ ์๋ ์ฝ๋ ์ฐธ๊ณ
๐ Spring Security ( ์ธ์ฆ ๊ตฌํ ๋ฐฉ์ ์ฐธ๊ณ !)
โญ ํ ํฐ ๊ฒ์ฆ ์์น
๋ณดํต Spring Security์์ ๊ฒ์ฆ ํ๋๋ฐ Gateway๋ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ๋ ์ค ํ๋์์๋ง Filter๋ก JWT ๊ฒ์ฆ์ ํ๋ฉด ๋๋ค.
- Gateway: JwtGatewayFilterFactory ์์ฑ.Gateway์์ ์์ฒญ์ ์ฒ๋ฆฌํ ๋(์์ฒญ์ด ๋ฐฑ์๋ ์๋น์ค๋ก ๋์ด๊ฐ๊ธฐ ์ ) JWT ํ ํฐ์ ๊ฒ์ฆ, ์์ฒญ์ด ๋ฐฑ์๋ ์๋น์ค๋ก ๋์ด๊ฐ ์ ์๋๋ก ํฉ๋๋ค.
- Spring Security: JwtAuthenticationFilter ์์ฑ. ์์ฒญ์ด ๋ฐฑ์๋ ์๋น์ค์ ๋๋ฌํ ํ, ํด๋น ์์ฒญ์ด ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ํด ์ด๋ฃจ์ด์ก๋์ง ํ์ธํ๋ ์ญํ ์ ํฉ๋๋ค.
โ 6. Key์ JWT ๊ด๋ฆฌ
ํ ํฐ ์์ฑ์ ์ฌ์ฉํ๋ โKeyโ์ ๊ฒฐ๊ณผ๋ฌผ์ธ JWT(AccessToken, RefreshToken)์ ์์ ํ ๊ด๋ฆฌ๊ฐ ํ์ํ๋ค!!
๐ ์๋์ ๊ด๋ฆฌ ๋ฐฉ๋ฒ์ ๋ชจ๋ ์ ์ฉํด์ผ ํ๋ ๊ฒ์ ์๋๋ค!! ๋ณธ์ธ์ ์ํฉ์ ๋ง๊ฒ ์ ์ฉํ ๊ฒ.
ํ์๋ผ๊ณ ์๊ฐ ๋๋ ๊ฒ์๋ ๋ฒํธ๋ค์ โญ ๋ถ์!!
โ 6-1. Key ๊ด๋ฆฌ
1๏ธโฃ โญSecret Key๋ฅผ ์์ ํ๊ฒ ๋ณด๊ดํ๊ธฐ
- ํ๊ฒฝ ๋ณ์(.env) ๋๋ Key Management System(KMS) ์ฌ์ฉ
- ์์ค ์ฝ๋์ ์ง์ ๋ ธ์ถ โ
2๏ธโฃ Secret Key ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณ๊ฒฝ (Key Rotation)
- ์ ๊ธฐ์ ์ผ๋ก ์๋ก์ด ํค๋ฅผ ์์ฑํ๊ณ , ๊ธฐ์กด ํค๋ฅผ ํ๊ธฐํ๋ Key Rotation ์ ์ฉ
- ์์ ํค๋ก ์๋ช ๋ JWT๋ ์ผ์ ๊ธฐ๊ฐ ๋์๋ง ํ์ฉ(๊ทธ๋ ์ด์ค ๊ธฐ๊ฐ)
3๏ธโฃ ๋ค์ค Secret Key ๊ด๋ฆฌ (ํค ๋ฒ์ ์ ์ฉ)
- ์: "kid"(Key ID) ํ๋๋ฅผ JWT์ ์ถ๊ฐํ์ฌ,
์ฌ๋ฌ ๊ฐ์ ํค๋ฅผ ์ด์ํ๋ฉด์ ์ ์ง์ ์ผ๋ก ๊ต์ฒด ๊ฐ๋ฅ
4๏ธโฃ MSA ํ๊ฒฝ์์๋ ์๋น์ค๋ณ Secret Key ๋ถ๋ฆฌ
- ํ๋์ ํค๋ฅผ ๋ชจ๋ ์๋น์ค์์ ์ฌ์ฉ โ
- ๊ฐ ์๋น์ค๋ณ๋ก ๋ค๋ฅธ Secret Key ์ฌ์ฉ โ ํน์ ์๋น์ค๋ง ์ํํด์ง
5๏ธโฃ JWT ๋์ฒด ๋ฐฉ๋ฒ ๊ณ ๋ ค (Opaque Token)
- ์๋ฒ์์ ์๋ช ๊ฒ์ฆ ์์ด DB์์ ์ง์ ํ ํฐ์ ํ์ธํ๋ ๋ฐฉ์ ๊ณ ๋ ค
- JWT๋ ํด๋ผ์ด์ธํธ์์ ๊ฒ์ฆ ๊ฐ๋ฅํ์ง๋ง, Secret Key ์์์ ์ํ์ด ํฌ๊ธฐ ๋๋ฌธ
6๏ธโฃ Key ์ ์ถ ๊ฐ์ง๋ฅผ ์ํ ๋ชจ๋ํฐ๋ง(๋ก๊ทธ ๋ถ์)
- ๋น์ ์์ ์ผ๋ก ๋ง์ JWT ๋ฐ๊ธ ์์ฒญ, ์ ํจํ์ง ์์ JWT ์๋ ๊ฐ์ง ๋ฑ
โ 6-2. JWT ๊ด๋ฆฌ
1๏ธโฃโญAccess Token์ ์์ ํ๊ฒ ๋ณด๊ดํ๊ธฐ
- Access Token: localStorage
- Refresh Token: HttpOnly, secure ์ฟ ํค
- ๐ ์ ์ฅ์ ๋น๊ต
2๏ธโฃ โญRefresh Token์ ์ด์ฉ
- Access Token์ ์งง๊ฒ ์ค์ ํ๊ณ Refresh Token์ ํ์ฉ
- Refresh Token์ DB์์ ๊ด๋ฆฌํ๊ณ , ํ์ทจ ์ ์ ์ฒด ๋ฌดํจํ ๊ฐ๋ฅํ๋๋ก ์ค๊ณ
- Refresh Token์ ์ฌ์ฉํ ๋ DB/redis์ ์ ์ฅ๋ ๊ฒ๊ณผ ๊ฐ์์ง ํ๋ฒ ๋ ๊ฒ์ฆ
3๏ธโฃ ํ ํฐ ๋ธ๋๋ฆฌ์คํธ ์ด์ฉ
- Redis ๊ฐ์ DB์ ์ ์ฅ, ์ด๋ฅผ ๋ง๋ฃ๋ ๋๊น์ง ๊ฐ์ง๊ณ ์์์ผ๋ก์จ ํด๋น ํ ํฐ์ ์ฌ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค.
- ๋ก๊ทธ์์ ์๋ํฌ์ธํธ ๋๋ ํ์ทจ ์์ฌ ์ ์ ์ฉ.
โ ๋จ์ : ๋งค๋ฒ ์์ฒญ๋ง๋ค DB๋ฅผ ์กฐํํด์ผ ํจ (์ฑ๋ฅ ๋ถ๋ด ๐ข)
โ ๋์: 2๏ธโฃ Refresh Token์ ์ด์ฉ
4๏ธโฃ Token์ ์ฌ์ฉ์์ IP ๋ด์์ ๊ฒ์ฆ
- ๊ธฐ์กด์ ๋ฐ๊ธ๋ IP์ฃผ์์ ๋ค๋ฅธ IP์ฃผ์๊ฐ ํ์ง๋์๋ค๋ฉด ํด๋น ํ ํฐ์ ๋ธ๋๋ฆฌ์คํธ์ ๋ฑ๋กํ๊ณ ์ฌ์ฉ์์๊ฒ ๋ค์ ๋ก๊ทธ์ธ์ ์ ๋ ํ๋ค.
- ํ์ทจ๋ ํ ํฐ์ ๋ํ ๋์.
โ๋จ์ : IP๋ ๋ณํ ์ ์์ (ํ๋ก์, VPN ์ฌ์ฉ, ์ ๋ IP, ๋ชจ๋ฐ์ผ ๋คํธ์ํฌ ๋ฌธ์ ๋ฑ) -> IP ๋ณํ๋ฉด ์ ์ ์ฌ์ฉ์๋ ๋ก๊ทธ์์ ๋จ.
โ ๋์:
- IP์ User-Agent ์ ๋ณด๋ฅผ ์๋ฒ์์ ์ธ์ ํํ๋ก ๋ฐ๋ก ๊ด๋ฆฌ
- ์์ฒญ ์ IP๊ฐ ๋ค๋ฅด๋ฉด ์ถ๊ฐ ์ธ์ฆ (ex. OTP) ์๊ตฌ
- Refresh Token ์ฌ๋ฐ๊ธ ์ IP ๊ฒ์ฆ
5๏ธโฃ ์ฌ์ฉ์ ์ ๋ณด ์ ๋ฐ์ดํธ (๊ถํ ๋ณ๊ฒฝ ๋ฑ)
- JWT๋ ๋ฐ๊ธ ํ ์์ ํ ์ ์๋ค.
โ ๋์:
- Access Token ๋์ Opaque Token(์๋ฒ ์ ์ฅํ ํ ํฐ) ํ์ฉ
- Refresh Token ์ฌ๋ฐ๊ธ ์์ ์ ์ ๋ณด ๋ฐ์
- Token ๋ฒ์ ํ๋ ์ถ๊ฐ ํ, Refresh ์ ๊ฒ์ฆ
'study > ๋ณด์' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Keystore์ Truststore (0) | 2025.04.10 |
---|---|
SSL์ด๋? (Secure Sockets Layer) (0) | 2025.03.24 |
์ ์ฅ์ ๋น๊ต (0) | 2024.12.01 |
๋ฐฉํ๋ฒฝ/IDS/IPS (0) | 2024.03.12 |
CNAPP (CWPP/SCPM/CIEM) (0) | 2024.03.12 |