diff --git a/src/main/java/org/terning/terningserver/auth/application/AuthService.java b/src/main/java/org/terning/terningserver/auth/application/AuthService.java index 8f621be..1b360cc 100644 --- a/src/main/java/org/terning/terningserver/auth/application/AuthService.java +++ b/src/main/java/org/terning/terningserver/auth/application/AuthService.java @@ -111,7 +111,7 @@ public TokenReissueResponse reissueAccessToken(String authorizationHeader) { Long userId = jwtProvider.getUserIdFrom(authorizationHeader); User user = userRepository.findById(userId) - .orElseThrow(() -> new JwtException(JwtErrorCode.INVALID_JWT_TOKEN)); + .orElseThrow(() -> new JwtException(JwtErrorCode.INVALID_TOKEN)); String providedToken = jwtProvider.resolveToken(authorizationHeader); user.validateRefreshToken(providedToken); diff --git a/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java index 1db939a..e209346 100644 --- a/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java +++ b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java @@ -8,6 +8,9 @@ import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SecurityException; import jakarta.annotation.PostConstruct; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.terning.terningserver.auth.dto.Token; @@ -15,11 +18,6 @@ import org.terning.terningserver.auth.jwt.exception.JwtException; import org.terning.terningserver.common.config.ValueConfig; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.util.Date; - @Component @RequiredArgsConstructor public class JwtProvider { @@ -28,11 +26,12 @@ public class JwtProvider { private static final String TOKEN_PREFIX = "Bearer "; private final ValueConfig valueConfig; + private SecretKey secretKey; @PostConstruct protected void init() { - secretKey = Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes(StandardCharsets.UTF_8)); + this.secretKey = Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes(StandardCharsets.UTF_8)); } public Token generateTokens(Long userId) { @@ -48,10 +47,9 @@ public Token generateAccessToken(Long userId) { public Long getUserIdFrom(String authorizationHeader) { String token = resolveToken(authorizationHeader); - Claims claims = parseClaims(token); - Object userIdClaim = claims.get(USER_ID_CLAIM); + if (userIdClaim instanceof Number) { return ((Number) userIdClaim).longValue(); } @@ -73,21 +71,39 @@ private String generateToken(Long userId, long expiration) { .setClaims(claims) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(secretKey) + .signWith(this.secretKey) .compact(); } private Claims parseClaims(String token) { try { return Jwts.parserBuilder() - .setSigningKey(secretKey) + .setSigningKey(this.secretKey) .build() .parseClaimsJws(token) .getBody(); - } catch (ExpiredJwtException e) { - throw new JwtException(JwtErrorCode.EXPIRED_JWT_TOKEN); - } catch (UnsupportedJwtException | MalformedJwtException | SecurityException | IllegalArgumentException e) { - throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); + } catch (Exception e) { + handleJwtException(e); + throw new JwtException(JwtErrorCode.UNEXPECTED_ERROR); + } + } + + private void handleJwtException(Exception e) { + if (e instanceof ExpiredJwtException) { + throw new JwtException(JwtErrorCode.EXPIRED_TOKEN); + } + if (e instanceof SecurityException) { + throw new JwtException(JwtErrorCode.SIGNATURE_ERROR); + } + if (e instanceof MalformedJwtException) { + throw new JwtException(JwtErrorCode.MALFORMED_TOKEN); + } + if (e instanceof UnsupportedJwtException) { + throw new JwtException(JwtErrorCode.UNSUPPORTED_TOKEN); + } + if (e instanceof IllegalArgumentException) { + throw new JwtException(JwtErrorCode.EMPTY_TOKEN); } + throw new JwtException(JwtErrorCode.INVALID_TOKEN); } } diff --git a/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java index f39b100..dcd29d7 100644 --- a/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java +++ b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java @@ -7,20 +7,19 @@ @Getter @AllArgsConstructor public enum JwtErrorCode { - INVALID_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 JWT 토큰입니다."), - INVALID_USER_ID(HttpStatus.BAD_REQUEST, "유효하지 않은 userId 값입니다."), - INVALID_USER_ID_TYPE(HttpStatus.BAD_REQUEST, "유효하지 않은 userId 타입입니다."), - INVALID_USER_DETAILS_TYPE(HttpStatus.INTERNAL_SERVER_ERROR, "유효하지 않은 UserDetail 타입입니다."), - TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "Authorization 헤더에 토큰이 없습니다."), - EXPIRED_JWT_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 JWT 토큰입니다."), - ; - public static final String PREFIX = "[JWT ERROR]"; + INVALID_USER_ID_TYPE(HttpStatus.BAD_REQUEST, "사용자 ID의 타입이 유효하지 않습니다."), + EMPTY_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 비어있거나 유효하지 않은 형식입니다."), - private final HttpStatus status; - private final String rawMessage; + TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "HTTP Authorization 헤더를 찾을 수 없습니다."), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), + MALFORMED_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 형식의 토큰입니다."), + SIGNATURE_ERROR(HttpStatus.UNAUTHORIZED, "토큰 서명 검증에 실패했습니다."), + UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "지원되지 않는 방식의 토큰입니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."), + + UNEXPECTED_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "토큰 처리 중 예상치 못한 서버 오류가 발생했습니다."); - public String getMessage() { - return PREFIX + " " + rawMessage; - } + private final HttpStatus status; + private final String message; } diff --git a/src/main/java/org/terning/terningserver/common/config/ValueConfig.java b/src/main/java/org/terning/terningserver/common/config/ValueConfig.java index 89eb215..c1e2604 100644 --- a/src/main/java/org/terning/terningserver/common/config/ValueConfig.java +++ b/src/main/java/org/terning/terningserver/common/config/ValueConfig.java @@ -1,9 +1,13 @@ package org.terning.terningserver.common.config; +import jakarta.annotation.PostConstruct; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + @Configuration @Getter public class ValueConfig { @@ -22,4 +26,9 @@ public class ValueConfig { @Value("${jwt.refresh-token-expired}") private Long refreshTokenExpired; + + @PostConstruct + protected void init() { + secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8)); + } } diff --git a/src/main/java/org/terning/terningserver/user/domain/User.java b/src/main/java/org/terning/terningserver/user/domain/User.java index 134cf4e..8e9f9cd 100644 --- a/src/main/java/org/terning/terningserver/user/domain/User.java +++ b/src/main/java/org/terning/terningserver/user/domain/User.java @@ -107,7 +107,7 @@ public void updateProfile(String name, ProfileImage profileImage){ public void validateRefreshToken(String providedToken) { if (this.refreshToken == null || !this.refreshToken.equals(providedToken)) { - throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); + throw new JwtException(JwtErrorCode.INVALID_TOKEN); } } }