[Spring boot] JWT 관리 코드 분석

반응형

build.gradle

  • jjwt-api: JWT 기능을 사용하는 데 필요한 API.
  • jjwt-impl: JWT 서명 및 파싱 등의 구현체.
  • jjwt-jackson: JSON 파싱을 위한 Jackson 연동.
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")

 

JWT

  • 밑은 JWT 관리 클래스이다. 한 번 다시 공부해보자
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {

    private Key key;
    private final long accessTokenValidity = 1000 * 60 * 1; // 15분
    private final long refreshTokenValidity = 1000L * 60 * 60 * 24 * 7; // 7일

    public JwtUtil(@Value("${jwt.secret}") String secretKeyRaw) {
        this.key = Keys.hmacShaKeyFor(secretKeyRaw.getBytes());
    }

    public String generateAccessToken(Long userId) {
        return generateToken(userId, accessTokenValidity);
    }

    public String generateRefreshToken(Long userId) {
        return generateToken(userId, refreshTokenValidity);
    }

    private String generateToken(Long userId, long validityInMs) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + validityInMs);

        return Jwts.builder()
                .setSubject(String.valueOf(userId))
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    public void validateToken(String token) {
        try {
            log.info("token {}", token);
            Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token);
        } catch (JwtException | IllegalArgumentException e) {
            throw UnauthorizedException.of("유효하지 않은 refresh token입니다.");
        }
    }

    public Long getUserIdFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

        return Long.parseLong(claims.getSubject());
    }

    public Date getExpiration(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

        return claims.getExpiration();
    }
}

 

SecretKey 및 유효기간

  • key: JWT 서명 및 검증에 사용될 비밀 키 (HS256 대칭 키)
    • @Value("${jwt.secret}"): application.properties 또는 application.yml에 정의된 jwt.secret 값을 주입받음
    • Keys.hmacShaKeyFor(...): HAMC 알고리즘(예 : HS256)에 맞는 javax,crypto.SecretKey를 생성 → 즉, HS256에서 요구하는 대칭키(서명용 비밀 키)를 만들기 위해 secretKeyRaw를 바이트 배열로 변환해서 키로 쓰는 것
  • accessTokenValidity: 액세스 토큰 만료 시간 → 1분(임시로 1분)
  • refreshTokenValidity: 리프레시 토큰 만료 시간 → 7일
private Key key;
private final long accessTokenValidity = 1000 * 60 * 1; // 1분
private final long refreshTokenValidity = 1000L * 60 * 60 * 24 * 7; // 7일

public JwtUtil(@Value("${jwt.secret}") String secretKeyRaw) {
    this.key = Keys.hmacShaKeyFor(secretKeyRaw.getBytes());
}

 

토큰 생성

public String generateAccessToken(Long userId) {
    return generateToken(userId, accessTokenValidity);
}

public String generateRefreshToken(Long userId) {
    return generateToken(userId, refreshTokenValidity);
}
private String generateToken(Long userId, long validityInMs) {
    Date now = new Date();
    Date expiry = new Date(now.getTime() + validityInMs);

    return Jwts.builder()
            .setSubject(String.valueOf(userId))  // 토큰에 userId를 subject로 설정
            .setIssuedAt(now)                    // 발급 시각
            .setExpiration(expiry)               // 만료 시각
            .signWith(key, SignatureAlgorithm.HS256)  // HS256으로 서명
            .compact();
}
  • 각각 Access/Refresh Token을 생성하며 내부적으로 generateToken(...)을 호출

결과는 다음과 같다.

  • sub(Subject): 유저 ID
  • iat(Issued At): 발급 시각
  • exp(Expiration): 만료 시각
  • 서명: HMAC-SHA256 사용

 

토큰 유효성 검사

public void validateToken(String token) {
    try {
        log.info("token {}", token);
        Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
    } catch (JwtException | IllegalArgumentException e) {
        throw UnauthorizedException.of("유효하지 않은 refresh token입니다.");
    }
}
  • 성공 시: 아무 예외 없이 통과.
  • 실패 시: JwtException 또는 IllegalArgumentException 발생 → UnauthorizedException 발생시킴.

 

토큰에서 유저 ID 추출

public Long getUserIdFromToken(String token) {
    Claims claims = Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .getBody();

    return Long.parseLong(claims.getSubject());
}
  • subject에 저장된 userId를 가져와 Long으로 변환하여 반환함

 

만료일 추출

public Date getExpiration(String token) {
    Claims claims = Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .getBody();

    return claims.getExpiration();
}
  • JWT의 exp 필드를 반환함 → 토큰 만료 시각 확인 용도로 사용됨

 

예시 토큰 구조 → 생성결과

{
  "sub": "4", // userId
  "iat": 1693000000,
  "exp": 1693000900
}
반응형