[Spring] com.auth0 vs jsonwebtoken.jjwt

2025. 2. 16. 23:34·Back-end/Spring
반응형

 

지금까지 프로젝트를 했던 것들을 짚어봤을 때

 

JWT 토큰을 사용하기 위해 com.auth0.java-jwt 라이브러리를 사용하거나 io.jsonwebtoken (jjwt) 라이브러리를 사용하는 경우가 있었다.

 

com.auth0가 더 좋다, jjwt가 더 좋다 이런 말들이 많아서 직접 조사해보고 정리해보았다.

 

 

1. com.auth0:java-jwt

💡 장점

  • 가볍고 간결한 API
    • 코드가 직관적이며, JWT의 생성 및 검증 과정이 상대적으로 간단함.
  • 빌더 패턴을 활용한 사용성 향상
    • JWT 생성 시 JWT.create() 메서드를 사용하여 체인 방식으로 쉽게 설정 가능.
  • JDK 내장 라이브러리만 사용
    • 별도의 외부 의존성이 거의 없음. (java.security 및 java.util 기반)
  • 토큰 검증 시 Claim을 쉽게 추출 가능
    • Claim을 getClaim() 메서드로 직관적으로 가져올 수 있음.

❌ 단점

  • Secret Key를 HMAC 방식으로만 지원
    • RSA/ECDSA 공개/개인 키 기반의 서명 검증 기능이 부족.
  • JWS (서명된 JWT)만 지원, JWE(암호화된 JWT) 미지원
    • JWE를 사용해야 하는 경우 적합하지 않음.

 

사용 예시

1. build.gradle

implementation 'com.auth0:java-jwt:4.4.0' // 최신 버전 확인

 

 

2. JwtProvider.java

package bootcamp.wssrs.global.jwt;

import bootcamp.wssrs.global.redis.RedisService;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

@Slf4j
@Component
public class JwtProvider {

    private final Algorithm algorithm;
    private final RedisService redisService;

    @Value("${jwt.access-token.expiration}")
    private Long accessTokenValidTime; // 24시간

    @Value("${jwt.refresh-token.expiration}")
    private Long refreshTokenValidTime; // 2주

    public JwtProvider(@Value("${jwt.secret-key}") String secretKey, RedisService redisService) {
        this.algorithm = Algorithm.HMAC256(secretKey);
        this.redisService = redisService;
    }

    public String getEmailFromAccessToken(String token) {
        return JWT.decode(token).getClaim("email").asString();
    }

    public boolean validate(String token) {
        try {
            JWT.require(algorithm).build().verify(token);
            if (redisService.hasKeyBlackList(token)) { // JWT BlackList
                return false;
            }
            return true;
        } catch (JWTVerificationException e) {
            return false;
        }
    }

    // 액세스 토큰 생성
    public String createAccessToken(String email, String role) {
        return JWT.create()
                .withIssuedAt(Instant.now())
                .withExpiresAt(Instant.now().plus(accessTokenValidTime, ChronoUnit.HOURS))
                .withClaim("email", email)
                .withSubject(role)
                .sign(algorithm);
    }

    // 리프레시 토큰 생성
    public String createRefreshToken() {
        return JWT.create()
                .withIssuedAt(Instant.now())
                .withExpiresAt(Instant.now().plus(refreshTokenValidTime, ChronoUnit.HOURS))
                .sign(algorithm);
    }

    // JWT 만료 시간 확인
    public Long getTokenExpiration(String token) {
        return JWT.require(algorithm).build().verify(token).getExpiresAt().getTime();
    }

}

 

 

2. io.jsonwebtoken (jjwt)

💡 장점

  • 다양한 알고리즘 지원
    • HMAC뿐만 아니라 RSA/ECDSA 기반의 서명 검증이 가능함.
  • JWE(암호화된 JWT) 지원
    • JWT를 암호화할 수 있는 기능을 제공.
  • 더 많은 보안 기능 제공
    • 서명 검증 외에도 만료 시간 설정, 비밀 키 저장 등의 기능을 포함.
  • 플렉서블한 Claim 처리
    • Claim을 쉽게 추가하거나 추출 가능.

❌ 단점

  • 상대적으로 무겁고 복잡한 API
    • java-jwt보다 코드가 조금 더 길어질 수 있음.
  • 추가적인 의존성 필요
    • Base64 인코딩/디코딩을 위해 jjwt-impl 및 jjwt-jackson 등의 추가 라이브러리가 필요.

 

 

사용 예시

1. build.gradle

implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")

 

io.jsonwebtoken:jjwt-api

  • 인터페이스(API)만 제공하는 모듈
  • 코드에서 사용하는 주요 클래스들이 포함되어 있음 (e.g., Jwts.builder(), Claims 등)
  • 컴파일 시 필요하지만, 구현체(실제 동작)는 포함되어 있지 않음

io.jsonwebtoken:jjwt-impl

  • jjwt-api의 실제 구현체(implementation)
  • 내부적으로 JWT를 생성하고 검증하는 로직을 포함
  • 컴파일 타임에는 필요 없고, 런타임에만 필요

io.jsonwebtoken:jjwt-jackson

  • JSON 파싱을 위해 Jackson을 사용하는 구현체
  • jjwt-impl이 JSON을 처리하는 방식 중 하나 (Gson을 사용하는 jjwt-gson도 있음)
  • 컴파일 타임에는 필요 없고, 런타임에만 필요

 

 

 

2. JwtProvider.java

package conference.clerker.global.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class JwtProvider {

    @Value("${jwt.secret-key}")
    private String secretKey;

    @Value("${jwt.access-token.expiration}")
    private long tokenValidityInSeconds;

    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secretKey.getBytes());
    }

    // JWT 토큰 생성 (추가 정보 포함)
    public String generateToken(Map<String, Object> additionalClaims) {
        Date now = new Date();
        long jwtExpirationMs = TimeUnit.HOURS.toMillis(tokenValidityInSeconds); // 시간을 밀리초로 변환
        Date expiryDate = new Date(now.getTime() + jwtExpirationMs);

        return Jwts.builder()
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .addClaims(additionalClaims) // 추가 정보 (custom claims) 설정
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
    }

    // 이메일 추출
    public String getEmailFromToken(String token) {
        return parseClaims(token).get("email", String.class); // email 키로 이메일 추출
    }

    // 사용자 이름 추출
    public String getUsernameFromToken(String token) {
        return parseClaims(token).get("username", String.class); // username 키로 사용자 이름 추출
    }

    // 토큰에서 Claims 추출
    private Claims parseClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // 토큰 유효성 검증
    public boolean validateToken(String token) {
        try {
            Claims claims = parseClaims(token);
            return !claims.getExpiration().before(new Date());
        } catch (Exception e) {
            log.error("Invalid JWT token: ", e);
            return false;
        }
    }
}

 

 

 

결론

  • HMAC256과 같은 단순한 JWT 서명/검증이 필요하면 com.auth0:java-jwt 사용이 더 간단하고 직관적.
  • RSA/ECDSA 기반의 서명을 사용하거나 JWE(암호화된 JWT)까지 고려해야 한다면 io.jsonwebtoken (jjwt)이 더 적합.
  • 보안이 중요한 엔터프라이즈 환경에서는 jjwt가 더 강력한 기능을 제공.

✔️ 간단한 JWT 검증이 필요하다면 java-jwt
✔️ 더 강력한 보안 기능이 필요하다면 jjwt

반응형

'Back-end > Spring' 카테고리의 다른 글

[Spring] filter vs AOP vs Interceptor  (0) 2025.04.09
[Spring] Docker-Compose를 활용한 CI / CD 구축  (1) 2025.03.18
[Spring] JPA 벌크 연산이란?  (0) 2025.02.01
[Spring] Spring Batch를 활용한 배치 프로세싱  (2) 2025.01.20
[Spring] Private 메서드에 @Transactional 선언 시 트랜잭션이 동작하는가?  (0) 2025.01.16
'Back-end/Spring' 카테고리의 다른 글
  • [Spring] filter vs AOP vs Interceptor
  • [Spring] Docker-Compose를 활용한 CI / CD 구축
  • [Spring] JPA 벌크 연산이란?
  • [Spring] Spring Batch를 활용한 배치 프로세싱
류건
류건
개발 일지
  • 류건
    건's Dev
    류건
  • 전체
    오늘
    어제
    • 분류 전체보기 (94)
      • Back-end (55)
        • Spring (30)
        • Nest.js (3)
        • Next.js (2)
        • Node.js (3)
      • Infra & Cloud (20)
        • Cloud Computing (6)
        • Docker (3)
        • AWS (7)
      • Java (2)
      • Computer Science (12)
        • Computer Network (0)
        • Operating System (0)
        • 정보 보호와 시스템 보안 (12)
      • 회고록 (1)
        • 우아한테크코스 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    db
    Kafka
    티스토리챌린지
    Spring Boot
    public key
    ssl
    express.js
    JPA
    aws
    nginx
    Webflux
    Docker
    Spring
    오블완
    Github Actions
    JWT
    보안
    ddl-auto
    node.js
    https
    Nest.js
    EC2
    CORS
    고가용성
    CD
    CI/CD
    WebClient
    Lock
    어노테이션
    정보보호
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.0
류건
[Spring] com.auth0 vs jsonwebtoken.jjwt
상단으로

티스토리툴바