From 97a1c00d27f8e9240141babb6f8725d4f71669a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Tue, 15 Jul 2025 10:03:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20JWT=20=EC=8B=9C=ED=81=AC=EB=A6=BF?= =?UTF-8?q?=20=ED=82=A4=20Base64=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terning/terningserver/common/config/ValueConfig.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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)); + } } From 85a5c5aa246b2a1417f2f095717bca72acb0ae6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Tue, 15 Jul 2025 10:03:40 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20JwtProvider=EB=A5=BC=20?= =?UTF-8?q?=EB=8B=A8=EC=9D=BC=20=ED=82=A4=20=EC=B2=98=EB=A6=AC=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=8B=A8=EC=88=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terningserver/auth/jwt/JwtProvider.java | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) 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); } } From e0889a326f0916c2f02bbf46687c67b36e1615d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=AF=E1=86=AB=E1=84=8C=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=89=E1=85=AE=E1=86=AB?= Date: Tue, 15 Jul 2025 10:04:02 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/AuthService.java | 2 +- .../auth/jwt/exception/JwtErrorCode.java | 25 +++++++++---------- .../terningserver/user/domain/User.java | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) 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/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/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); } } }