반응형
지금까지 프로젝트를 했던 것들을 짚어봤을 때
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] Docker-Compose를 활용한 CI / CD 구축 (1) | 2025.03.18 |
---|---|
[Spring] JPA 벌크 연산이란? (0) | 2025.02.01 |
[Spring] Spring Batch를 활용한 배치 프로세싱 (1) | 2025.01.20 |
[Spring] Private 메서드에 @Transactional 선언 시 트랜잭션이 동작하는가? (0) | 2025.01.16 |
[Spring] Spring Data JPA에서 새로운 Entity인지 판단하는 방법? (1) | 2024.12.08 |