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) { +} 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..1db939a --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/jwt/JwtProvider.java @@ -0,0 +1,93 @@ +package org.terning.terningserver.auth.jwt; + +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.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 +@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(StandardCharsets.UTF_8)); + } + + 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); + } + + 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); + } + + 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); + } catch (UnsupportedJwtException | MalformedJwtException | SecurityException | IllegalArgumentException e) { + throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); + } + } +} 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/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 +} 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; 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;