From f5668e8e58f79dc316b355ab4ecf87b877241d5c 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: Sun, 13 Jul 2025 21:19:27 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat=20:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20jw?= =?UTF-8?q?tProvider=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terningserver/auth/jwt/JwtProvider.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java diff --git a/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java new file mode 100644 index 0000000..8bcb6ad --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java @@ -0,0 +1,85 @@ +package org.terning.terningserver.auth.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.terning.terningserver.auth.dto.Token; +import org.terning.terningserver.common.config.ValueConfig; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; + +import javax.crypto.SecretKey; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtProvider { + + private static final String USER_ID_CLAIM = "userId"; + private static final String TOKEN_PREFIX = "Bearer "; + + private final ValueConfig valueConfig; + private SecretKey secretKey; + + @PostConstruct + protected void init() { + secretKey = Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes()); + } + + public Token generateTokens(Long userId) { + String accessToken = generateToken(userId, valueConfig.getAccessTokenExpired()); + String refreshToken = generateToken(userId, valueConfig.getRefreshTokenExpired()); + return new Token(accessToken, refreshToken); + } + + public Token generateAccessToken(Long userId) { + String accessToken = generateToken(userId, valueConfig.getAccessTokenExpired()); + return new Token(accessToken, null); + } + + 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(); + } + throw new JwtException(JwtErrorCode.INVALID_USER_ID_TYPE.getMessage()); + } + + public String resolveToken(String rawToken) { + if (rawToken != null && rawToken.startsWith(TOKEN_PREFIX)) { + return rawToken.substring(TOKEN_PREFIX.length()); + } + throw new JwtException(JwtErrorCode.TOKEN_NOT_FOUND.getMessage()); + } + + private String generateToken(Long userId, long expiration) { + Claims claims = Jwts.claims(); + claims.put(USER_ID_CLAIM, userId); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(secretKey) + .compact(); + } + + private Claims parseClaims(String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (ExpiredJwtException e) { + throw new JwtException(JwtErrorCode.EXPIRED_JWT_TOKEN.getMessage()); + } catch (UnsupportedJwtException | MalformedJwtException | SecurityException | IllegalArgumentException e) { + throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN.getMessage()); + } + } +} From 1b33d0eff8c9264bae7fa4f4e9a82af9a0fccb6d 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: Sun, 13 Jul 2025 21:19:40 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat=20:=20=ED=83=80=EC=9E=85=20=EC=9D=BC?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../external/pushNotification/user/domain/UserSyncEvent.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/terning/terningserver/external/pushNotification/user/domain/UserSyncEvent.java b/src/main/java/org/terning/terningserver/external/pushNotification/user/domain/UserSyncEvent.java index 5583aee..ca6c090 100644 --- a/src/main/java/org/terning/terningserver/external/pushNotification/user/domain/UserSyncEvent.java +++ b/src/main/java/org/terning/terningserver/external/pushNotification/user/domain/UserSyncEvent.java @@ -1,6 +1,7 @@ package org.terning.terningserver.external.pushNotification.user.domain; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -24,7 +25,7 @@ public class UserSyncEvent { private Long userId; - @Enumerated + @Enumerated(EnumType.STRING) private UserSyncEventType eventType; private String newValue; From 5622913e57c1d8601e3e26ba8a515664c02cf086 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: Sun, 13 Jul 2025 21:20:12 +0900 Subject: [PATCH 3/5] =?UTF-8?q?chore=20:=20jwt=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{common/security => auth}/jwt/exception/JwtErrorCode.java | 4 +++- .../{common/security => auth}/jwt/exception/JwtException.java | 2 +- .../common/exception/GlobalExceptionHandler.java | 4 ++-- .../common/security/jwt/application/JwtClaimsGenerator.java | 2 +- .../common/security/jwt/application/JwtUserIdExtractor.java | 4 ++-- .../common/security/jwt/auth/JwtClaimsParser.java | 4 ++-- .../common/security/jwt/auth/UserIdConverter.java | 4 ++-- .../jwt/filter/CustomJwtAuthenticationEntryPoint.java | 4 ++-- 8 files changed, 15 insertions(+), 13 deletions(-) rename src/main/java/org/terning/terningserver/{common/security => auth}/jwt/exception/JwtErrorCode.java (76%) rename src/main/java/org/terning/terningserver/{common/security => auth}/jwt/exception/JwtException.java (80%) diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtErrorCode.java b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java similarity index 76% rename from src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtErrorCode.java rename to src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java index 344de71..f39b100 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtErrorCode.java +++ b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtErrorCode.java @@ -1,4 +1,4 @@ -package org.terning.terningserver.common.security.jwt.exception; +package org.terning.terningserver.auth.jwt.exception; import lombok.AllArgsConstructor; import lombok.Getter; @@ -11,6 +11,8 @@ public enum JwtErrorCode { 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]"; diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtException.java b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtException.java similarity index 80% rename from src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtException.java rename to src/main/java/org/terning/terningserver/auth/jwt/exception/JwtException.java index b8edb88..8211db1 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/exception/JwtException.java +++ b/src/main/java/org/terning/terningserver/auth/jwt/exception/JwtException.java @@ -1,4 +1,4 @@ -package org.terning.terningserver.common.security.jwt.exception; +package org.terning.terningserver.auth.jwt.exception; import lombok.Getter; diff --git a/src/main/java/org/terning/terningserver/common/exception/GlobalExceptionHandler.java b/src/main/java/org/terning/terningserver/common/exception/GlobalExceptionHandler.java index 6242786..eecd576 100644 --- a/src/main/java/org/terning/terningserver/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/terning/terningserver/common/exception/GlobalExceptionHandler.java @@ -13,8 +13,8 @@ import org.terning.terningserver.auth.common.exception.AuthException; import org.terning.terningserver.common.exception.dto.ErrorResponse; import org.terning.terningserver.common.exception.enums.ErrorMessage; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; -import org.terning.terningserver.common.security.jwt.exception.JwtException; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; @RestControllerAdvice @Slf4j diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java index 132f5d5..521337f 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java +++ b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; import java.util.Map; diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java index 78cbc31..3ac794b 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java +++ b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java @@ -3,8 +3,8 @@ import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; -import org.terning.terningserver.common.security.jwt.exception.JwtException; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; import org.terning.terningserver.common.security.jwt.auth.JwtClaimsParser; @Component diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java index 88cf585..5ffde25 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java +++ b/src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java @@ -5,8 +5,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.terning.terningserver.common.config.ValueConfig; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; -import org.terning.terningserver.common.security.jwt.exception.JwtException; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; import org.terning.terningserver.common.security.jwt.provider.JwtKeyProvider; @Service diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java index 2992a80..34058dd 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java +++ b/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java @@ -1,7 +1,7 @@ package org.terning.terningserver.common.security.jwt.auth; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; -import org.terning.terningserver.common.security.jwt.exception.JwtException; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; public class UserIdConverter { diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java b/src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java index 49e81ef..8b87ecc 100644 --- a/src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java +++ b/src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java @@ -6,8 +6,8 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import org.terning.terningserver.common.security.jwt.exception.JwtErrorCode; -import org.terning.terningserver.common.security.jwt.exception.JwtException; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; import java.io.IOException; From c8f6f4a92c490dc46ff876b42f1742f150c49477 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: Sun, 13 Jul 2025 21:20:23 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat=20:=20token=20dto=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/terning/terningserver/auth/dto/Token.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/org/terning/terningserver/auth/dto/Token.java diff --git a/src/main/java/org/terning/terningserver/auth/dto/Token.java b/src/main/java/org/terning/terningserver/auth/dto/Token.java new file mode 100644 index 0000000..fcfd5d4 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/dto/Token.java @@ -0,0 +1,4 @@ +package org.terning.terningserver.auth.dto; + +public record Token(String accessToken, String refreshToken) { +} From b8d80594293e405b8bba0e0de5ce51fac72a0ab7 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: Sun, 13 Jul 2025 23:01:21 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor(auth):=20JWT=20SecretKey=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B1=85=EC=9E=84=20=EC=9D=BC=EC=9B=90=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terningserver/auth/jwt/JwtProvider.java | 22 +++++++++++++------ .../common/config/ValueConfig.java | 11 +--------- 2 files changed, 16 insertions(+), 17 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 8bcb6ad..1db939a 100644 --- a/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java +++ b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java @@ -1,15 +1,23 @@ package org.terning.terningserver.auth.jwt; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SecurityException; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.terning.terningserver.auth.dto.Token; -import org.terning.terningserver.common.config.ValueConfig; import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +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 @@ -24,7 +32,7 @@ public class JwtProvider { @PostConstruct protected void init() { - secretKey = Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes()); + secretKey = Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes(StandardCharsets.UTF_8)); } public Token generateTokens(Long userId) { @@ -47,14 +55,14 @@ public Long getUserIdFrom(String authorizationHeader) { if (userIdClaim instanceof Number) { return ((Number) userIdClaim).longValue(); } - throw new JwtException(JwtErrorCode.INVALID_USER_ID_TYPE.getMessage()); + throw new JwtException(JwtErrorCode.INVALID_USER_ID_TYPE); } public String resolveToken(String rawToken) { if (rawToken != null && rawToken.startsWith(TOKEN_PREFIX)) { return rawToken.substring(TOKEN_PREFIX.length()); } - throw new JwtException(JwtErrorCode.TOKEN_NOT_FOUND.getMessage()); + throw new JwtException(JwtErrorCode.TOKEN_NOT_FOUND); } private String generateToken(Long userId, long expiration) { @@ -77,9 +85,9 @@ private Claims parseClaims(String token) { .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { - throw new JwtException(JwtErrorCode.EXPIRED_JWT_TOKEN.getMessage()); + throw new JwtException(JwtErrorCode.EXPIRED_JWT_TOKEN); } catch (UnsupportedJwtException | MalformedJwtException | SecurityException | IllegalArgumentException e) { - throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN.getMessage()); + throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); } } } 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 a6aa80e..89eb215 100644 --- a/src/main/java/org/terning/terningserver/common/config/ValueConfig.java +++ b/src/main/java/org/terning/terningserver/common/config/ValueConfig.java @@ -1,13 +1,9 @@ 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 { @@ -26,9 +22,4 @@ public class ValueConfig { @Value("${jwt.refresh-token-expired}") private Long refreshTokenExpired; - - @PostConstruct - protected void init() { - secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8)); - } -} \ No newline at end of file +}