[TIL | 내일배움캠프] JWT (JSON Web Token)

변채주·2025년 10월 24일

Spring

목록 보기
5/17

어제 Session의 단점으로 글을 마무리지었다. 그 글에 이어서 JWT를 정리하고자 한다.

Session에 이어 또 대안 등장

JWT(JSON Web Token)

Token

사용자나 시스템의 신원(정보), 권한을 증명하고 요청이 유효한지 검증하는데 사용되는 디지털 문자열을 Token토큰이라고 한다. Web Application이나 API에서 인증-인가 과정에 사용된다.

그 중에서도 JSON 형태의 토큰을 JWT라고 하며, 인증에 필요한 정보들이 암호화되어있다. 암호화되어있다는 소리는? 인코딩(암호화)-디코딩(복호화) 과정도 필요하다는 소리


JWT 구조

(Header).(Payload).(signature)

  • Header : 토큰 타입과 해싱 알고리즘을 정의한다. 이 글에서는 토큰 중에서도 JWT만 서술하므로 토큰 타입은 "JWT"이다.

    • 해싱 알고리즘 종류(출처)
      1) HMAC (Hash-based Message Authentication Code)
      : 고정된 길이의 서명값을 생성함.
      ( HS256(32byte) / HS384(48byte) / HS512(54byte) )

    2) RSA (Rivest-SHamir-Adleman)
    : private key로 JWT를 생성하고, public key로 권한을 인증한다. JWT에서는 비대칭키 알고리즘으로 사용된다. 서명값의 길이는 RSA 키 길이와 동일하다.
    ( RS256(256byte) / RS384(384byte) / RS512(512byte) )
    ( 1 byte = 8 bit )

    3) ECDSA (Elliptic Curve Digital Signature Algorithm)
    : 작동 원리는 RSA와 같으나(private-public key) 더 짧은 키 길이로 높은 보안을 제공한다. 더 깊이 들어가면 머리가 터질 거 같으니 나중에 익숙해지면 알아보도록 하자...

  • Payload : 실제 인증과 관련된 데이터(사용자 정보Claims)를 담고 있다.

  • Signature : Header와 Payload를 서버의 Secret key로 서명-암호화 한다.


JWT 구현 방법

  1. JWT 생성
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
    Date date = new Date(); // 토큰 생성 시간 기록
    //토큰 식별자 "Bearer "를 추가해서 생성한 JWT 반환
    return BEARER_PREFIX +
            Jwts.builder()
                    .setSubject(username) // 사용자 식별자값(ID)
                    .claim(AUTHORIZATION_KEY, role) // 사용자 권한
                    .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
                    .setIssuedAt(date) // 발급일
                    .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                    .compact();
}
  1. JWT를 Cookie에 저장한다.
// JWT Cookie 에 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
    try {
        token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서(들어갈 수 없음) encoding 진행
        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
        cookie.setPath("/"); // 경로 설정

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        logger.error(e.getMessage());
    }
}

➡️ (인증-인가가 필요한 요청이 들어올 경우)

  1. 전달된 JWT에 토큰 식별자"Bearer "가 붙어있으므로 이를 분리하기 위해 substring 해준다.
// JWT 토큰 substring
public String substringToken(String tokenValue) {
    if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
        return tokenValue.substring(7); //"Bearer: "의 길이가 7
    }
    logger.error("Not Found Token");
    throw new NullPointerException("Not Found Token");
}
  1. 3의 결과 토큰 JWT를 검증한다.
// 토큰 검증
public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException | SignatureException e) {
        logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
    } catch (ExpiredJwtException e) {
        logger.error("Expired JWT token, 만료된 JWT token 입니다.");
    } catch (UnsupportedJwtException e) {
        logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
    } catch (IllegalArgumentException e) {
        logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
    }
    return false;
}
  1. JWT에서 사용자 정보를 가져온다.
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); 
    // body 부분에 들어있는 Claims(사용자정보) 가져오기
}
profile
우당탕탕얼레벌레 개발 일지

0개의 댓글