JWT #2

Dearยท2025๋…„ 8์›” 1์ผ

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
65/74

๐Ÿ’™ hmacShaKeyFor(byte[] keyBytes)

JWT ์„œ๋ช…์„ ์œ„ํ•œ HMAC ํ‚ค๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์†Œ๋“œ

์ด ๋ฉ”์†Œ๋“œ๋Š” JJWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณต๋˜๋ฉฐ, JWT๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์„œ๋ช…(Signature) ๋ถ€๋ถ„์— ํ•„์š”ํ•œ ํ‚ค๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

๐Ÿ’š ๋™์ž‘ ๊ฐœ๋…

String secret = ~~;
SecretKey key = Keys.hmacShakeyFor(secret.getBytes));
  • secret.getBytes() : ๋ฌธ์ž์—ด์„ ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
  • hmacShaKeyFor(...) : ๋ฐ”์ดํŠธ ๋ฐฐ์—ด์˜ ๊ธธ์ด์— ๋”ฐ๋ผ HMAC ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ‚ค ์ƒ์„ฑ

์ด ๋ฉ”์†Œ๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ HMAC-SHA ์•Œ๊ณ ๋ฆฌ์ฆ˜ (HS256, HS384, HS512)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ‚ค๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

HMAC-SHA ์•Œ๊ณ ๋ฆฌ์ฆ˜

HMAC (Hash-based Message Authentication Code)

HMAC์€ ํ•ด์‹œ ํ•จ์ˆ˜๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฉ”์‹œ์ง€์˜ ๋ฌด๊ฒฐ์„ฑ๊ณผ ์ธ์ฆ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ์‹์ด๋‹ค.

์ž…๋ ฅ๊ฐ’ : ๋ฉ”์‹œ์ง€ + ๋น„๋ฐ€ ํ‚ค
๊ฒฐ๊ณผ๊ฐ’ : ์„œ๋ช…(Signature)

HMAC(secret, message) : ๋ฉ”์‹œ์ง€๊ฐ€ ์œ„์กฐ๋˜์ง€ ์•Š์•˜๋Š”์ง€, ์ธ์ฆ๋œ ํ‚ค๋กœ ์ƒ์„ฑ๋œ ๊ฒƒ์ธ์ง€ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฐฉ๋ฒ•

SHA (Secure Hash Algorithm)

ํ•ด์‹œ ํ•จ์ˆ˜๋กœ ์ฃผ์–ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ ์ •๋œ ๊ธธ์ด์˜ ๋ฌด์ž‘์œ„ ๊ฐ™์€ ๊ฐ’์œผ๋กœ ๋ฐ”๊พธ๋Š” ํ•จ์ˆ˜๋‹ค.
๋‹ค์–‘ํ•œ ๋น„ํŠธ ์ˆ˜๋ฅผ ์ง€์›ํ•œ๋‹ค.
SHA-256 - 256๋น„ํŠธ, SHA-384-384๋น„ํŠธ, SHA-512-512๋น„ํŠธ

HMAC + SHA = HMAC-SHA
๋‘˜์„ ์กฐํ•ฉํ•ด์„œ ๋งŒ๋“  ์•Œ๊ณ ๋ฆฌ์ฆ˜

์•Œ๊ณ ๋ฆฌ์ฆ˜์˜๋ฏธํŠน์ง•
HS256HMAC with SHA-256๊ฐ€์žฅ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ
HS384HMAC with SHA-384๋” ๊ฐ•ํ•œ ๋ณด์•ˆ
HS512HMAC with SHA-512๋งค์šฐ ๊ฐ•ํ•œ ๋ณด์•ˆ, ํ‚ค๋„ ๋” ๊น€

๐Ÿ’š JWT์—์„œ ์“ฐ์ด๋Š” ๋ฒ•

JWT 3๋ถ€๋ถ„์—์„œ Signature ๋ถ€๋ถ„์—์„œ ์‚ฌ์šฉ

Header:
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload:
{
  "sub": "user123",
  "role": "USER"
}

// HMAC-SHA๋กœ ๋งŒ๋“  ์„œ๋ช…. ์ด๊ฑธ๋กœ ์œ„์กฐ ์—ฌ๋ถ€ ํŒ๋‹จ
Signature = HMAC_SHA256(secret, base64url(Header) + "." + base64url(Payload))

๐Ÿ’™ SecretKeySpec

javax.crypto.spec.SecretKeySpec
๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ๋ถ€ํ„ฐ ๋Œ€์นญํ‚ค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํด๋ž˜์Šค

SecretKey ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋กœ ์•”ํ˜ธํšŒ ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์‚ฌ์šฉํ•  ์‹ค์ œ ํ‚ค ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.
๋ฐ”์ดํŠธ ๋ฐฐ์—ด(byte[]) + ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ด๋ฆ„(String)์„ ๋„˜๊ฒจ์ฃผ๋ฉด, SecretKey ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

public class SecretKeySpec implements SecretKey

์‚ฌ์šฉ ๋ชฉ์ 

ํŠน์ • ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์‚ฌ์šฉํ•  ํ‚ค๋ฅผ ์ง์ ‘ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•œ๋‹ค.
์˜ˆ : AES, HMAC ๋“ฑ์—์„œ ์„œ๋ช… ๋˜๋Š” ์•”ํ˜ธํ™”์— ์‚ฌ์šฉํ•  ํ‚ค๋ฅผ ์ง์ ‘ ์„ค์ •ํ•ด์•ผ ํ•  ๋•Œ

HMAC-SHA256์šฉ ํ‚ค๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ƒ์„ฑ ์˜ˆ์‹œ

import javax.crypto.spec.SecretKeySpec;
import javax.crypto.SecretKey;

String secret = "this-is-a-very-secret-key-32bytes!";
// ํ•ด๋‹น ํ‚ค๋ฅผ JJWT, Mac, Cipher ๋“ฑ์— ์ „๋‹ฌ
byte[] keyBytes = secret.getBytes();
// HmacSHA256๋Š” ์‚ฌ์šฉํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ด๋ฆ„
SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA256");

๐Ÿ’™ Keys.hmacShaKeyFor() vs SecretKeySpec

ํ•ญ๋ชฉSecretKeySpecKeys.hmacShaKeyFor()
์ œ๊ณต ์œ„์น˜Java ๊ธฐ๋ณธ APIJJWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
์šฉ๋„๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ๋Œ€์นญํ‚ค ๋งŒ๋“ค๊ธฐJWT ์„œ๋ช…์„ ์œ„ํ•œ ์•ˆ์ „ํ•œ HMAC ํ‚ค ์ƒ์„ฑ
ํ‚ค ๊ธธ์ด ๊ฒ€์‚ฌโŒ (์ง์ ‘ ๊ฒ€์ฆํ•ด์•ผ ํ•จ)โœ… (256๋น„ํŠธ ์ด์ƒ ์•„๋‹ˆ๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒ)
ํŽธ์˜์„ฑ๋‚ฎ์Œ๋†’์Œ
๊ถŒ์žฅ ์—ฌ๋ถ€๊ธฐ๋ณธ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉ ์‹œ OKJWT์šฉ ํ‚ค ์ƒ์„ฑ ์‹œ ๋” ๊ถŒ์žฅ๋จ
  • SecretKeySpec์€ ๋กœ์šฐ๋ ˆ๋ฒจ์˜ ํ‚ค ์ƒ์„ฑ ๋„๊ตฌ
  • hmacShaKeyFor()๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ SecretKeySpec์„ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์ž๋™์œผ๋กœ ์•ˆ์ •์„ฑ๊ณผ ๊ธธ์ด ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋” ์•ˆ์ „ํ•˜๊ณ  ํŽธ๋ฆฌํ•˜๋‹ค.

๐Ÿ’™ claim(String name, Object value)

JWT๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋กœ, JWT์˜ payload ์˜์—ญ์— ์›ํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

io.jsonwebtoken.JwtBuilder ์ธํ„ฐํŽ˜์ด์Šค์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ, Jwts.builder()๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ JwtBuilder ๊ฐ์ฒด์—์„œ ์‚ฌ์šฉ


Jwts.builder()
    .subject(username)                // ํ‘œ์ค€ ํด๋ ˆ์ž„: subject
    .claim("email", email)           // ์ปค์Šคํ…€ ํด๋ ˆ์ž„: ์ด๋ฉ”์ผ
    .claim("role", role)             // ์ปค์Šคํ…€ ํด๋ ˆ์ž„: ์—ญํ• 
    .issuedAt(now)
    .expiration(expiryDate)
    .signWith(secretKey)
    .compact();
    

๐Ÿ’š Claim

JWT์˜ payload ์˜์—ญ์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํด๋ ˆ์ž„์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค. ํด๋ ˆ์ž„์€ ํ† ํฐ์— ๋‹ด๊ธด ์ •๋ณด ์กฐ๊ฐ์ด๋‹ค.

๐Ÿ’š Claims

Header . Payload . Signature

์ด ์ค‘ Payload ๋ถ€๋ถ„์— ๋‹ด๊ธด ์ •๋ณด๊ฐ€ ๋ฐ”๋กœ Claims (์˜ˆ: ์‚ฌ์šฉ์ž ์ด๋ฆ„, ์ด๋ฉ”์ผ, ๊ถŒํ•œ(Role) ๋“ฑ)
key-value ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ์ด๋‹ค.

public Claims extractClaims(String token) {
        return Jwts.parser()
                .verifyWith(secretKey) // ๋‚ด๋ถ€์ ์œผ๋กœ ์„œ๋ช…๋„ ๊ฒ€์ฆ
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }
    

๋‚ด๋ถ€์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๋‚ด์šฉ

  1. JWT ํŒŒ์‹ฑ ๋ฐ ์„œ๋ช… ๊ฒ€์ฆ
    parseClaimsJws(token) ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค์Œ์„ ์ˆ˜ํ–‰
  • ํ† ํฐ์ด ๋ณ€์กฐ๋˜์ง€ ์•Š์•˜๋Š”์ง€ Signature๋ฅผ ํ†ตํ•ด ๊ฒ€์ฆ
  • ์„œ๋ช…ํ‚ค(secretKey)๋กœ verifyWith(secretKey) ๊ณผ์ •์„ ์ˆ˜ํ–‰
  1. ํด๋ ˆ์ž„ ์ •๋ณด ์ถ”์ถœ
    ์„œ๋ช… ๊ฒ€์ฆ์ด ํ†ต๊ณผํ•˜๋ฉด .getBody() ๋ฅผ ํ†ตํ•ด payload(ํด๋ ˆ์ž„) ๋‚ด์šฉ์„ ์ถ”์ถœ

Claim๊ณผ Claims

{
  "sub": "user1",
  "email": "user1@example.com",
  "role": "USER",
  "exp": 1722315123
}
ํ•ญ๋ชฉ์„ค๋ช…
์ „์ฒดClaims ๊ฐ์ฒด
"sub": "user1"ํ•˜๋‚˜์˜ Claim
"email": "user1@example.com"ํ•˜๋‚˜์˜ Claim
"role": "USER"ํ•˜๋‚˜์˜ Claim

๊ตฌ๋ถ„์„ค๋ช…
Claims์—ฌ๋Ÿฌ ๊ฐœ์˜ Claim์„ ๋‹ด์€ Map ํ˜•ํƒœ ๊ฐ์ฒด (Map<String, Object>)
Claim๊ทธ ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๋‹จ์ผ ์ •๋ณด (์˜ˆ: username, role, exp ๋“ฑ)

๐Ÿ’š Claims์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ์ถ”์ถœ

    public String getUsername(String token) {
        return extractClaims(token).getSubject();
    }

    // ์ด๋ฉ”์ผ ์ถ”์ถœ
    public String getEmail(String token) {
        return extractClaims(token).get("email", String.class);
    }

    // ์—ญํ• (role) ์ถ”์ถœ
    public String getRole(String token) {
        return extractClaims(token).get("role", String.class);
    }
    

๐Ÿ’™ JwtUtil ์ „์ฒด์ ์ธ ํ๋ฆ„

  1. ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ createToken() ํ˜ธ์ถœํ•˜์—ฌ JWT ์ƒ์„ฑ
  2. ํด๋ผ์ด์–ธํŠธ๋Š” ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค JWT๋ฅผ ํ—ค๋”์— ๋‹ด์•„ ๋ณด๋ƒ„
  3. ํ•„ํ„ฐ์—์„œ validateToken()์„ ํ†ตํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  4. ํ•„์š”ํ•  ๋•Œ ์‚ฌ์šฉ์ž ์ •๋ณด(username, email, role)๋Š” ์ถ”์ถœ ๋ฉ”์†Œ๋“œ๋กœ ๊บผ๋ƒ„

๐Ÿค ํšŒ๊ณ 

ํ”„๋กœ์ ํŠธ์—์„œ JwtUtil์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ถ๊ธˆํ–ˆ๋˜ ๋ถ€๋ถ„๋“ค๊ณผ ๊ฐœ๋…์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ๊ด€๋ จ ๋‚ด์šฉ์„ ํ•™์Šตํ–ˆ๋‹ค. ๊ณต๋ถ€๋ฅผ ํ•˜๋‹ค ๋ณด๋‹ˆ ๊ฐœ๋…์ด ํ™•์žฅ๋˜์–ด SecretKeySpec๊นŒ์ง€ ๊นŠ์ด ์žˆ๊ฒŒ ์‚ดํŽด๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค

profile
์นœ์• ํ•˜๋Š” ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€