diff --git a/devenv/sql b/devenv/sql index ffd67b6c..dd0c6e85 160000 --- a/devenv/sql +++ b/devenv/sql @@ -1 +1 @@ -Subproject commit ffd67b6cc14c1b6ff70df58308406364149448f6 +Subproject commit dd0c6e85fdbd4d6b823e8fcbb45e5e1726c3fe5a diff --git a/server/api/src/main/java/vook/server/api/domain/user/model/user/User.java b/server/api/src/main/java/vook/server/api/domain/user/model/user/User.java index 2bdaad8f..1934ca90 100644 --- a/server/api/src/main/java/vook/server/api/domain/user/model/user/User.java +++ b/server/api/src/main/java/vook/server/api/domain/user/model/user/User.java @@ -24,6 +24,7 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(unique = true) private String uid; @Column(unique = true) diff --git a/server/api/src/main/java/vook/server/api/domain/vocabulary/model/term/Term.java b/server/api/src/main/java/vook/server/api/domain/vocabulary/model/term/Term.java index 1e1b4037..58e18572 100644 --- a/server/api/src/main/java/vook/server/api/domain/vocabulary/model/term/Term.java +++ b/server/api/src/main/java/vook/server/api/domain/vocabulary/model/term/Term.java @@ -20,6 +20,7 @@ public class Term extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(unique = true) private String uid; /** diff --git a/server/api/src/main/java/vook/server/api/domain/vocabulary/model/vocabulary/Vocabulary.java b/server/api/src/main/java/vook/server/api/domain/vocabulary/model/vocabulary/Vocabulary.java index 9b0ac53e..7377521a 100644 --- a/server/api/src/main/java/vook/server/api/domain/vocabulary/model/vocabulary/Vocabulary.java +++ b/server/api/src/main/java/vook/server/api/domain/vocabulary/model/vocabulary/Vocabulary.java @@ -21,6 +21,7 @@ public class Vocabulary extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(unique = true) private String uid; /** @@ -30,7 +31,10 @@ public class Vocabulary extends BaseEntity { private String name; @Embedded - @AttributeOverride(name = "value", column = @Column(name = "user_uid", nullable = false)) + @AttributeOverride( + name = "value", + column = @Column(name = "user_uid", nullable = false, unique = true) + ) private UserUid userUid; @Builder.Default diff --git a/server/api/src/main/java/vook/server/api/globalcommon/helper/format/FormatHelper.java b/server/api/src/main/java/vook/server/api/globalcommon/helper/format/FormatHelper.java new file mode 100644 index 00000000..01bda67d --- /dev/null +++ b/server/api/src/main/java/vook/server/api/globalcommon/helper/format/FormatHelper.java @@ -0,0 +1,12 @@ +package vook.server.api.globalcommon.helper.format; + +import org.slf4j.helpers.MessageFormatter; + +public class FormatHelper { + /** + * Slf4J 스타일로 문자열 포멧팅을 해주는 함수 + */ + public static String slf4j(String pattern, Object... params) { + return MessageFormatter.arrayFormat(pattern, params).getMessage(); + } +} diff --git a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTException.java b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTException.java new file mode 100644 index 00000000..63fb9a90 --- /dev/null +++ b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTException.java @@ -0,0 +1,28 @@ +package vook.server.api.globalcommon.helper.jwt; + +import lombok.Getter; + +@Getter +public class JWTException extends RuntimeException { + + private final String useData; + + public JWTException(Throwable cause) { + super(cause); + this.useData = ""; + } + + public JWTException(Throwable cause, String useData) { + super(cause); + this.useData = useData; + } + + @Override + public String getMessage() { + if (useData.isEmpty()) { + return super.getMessage(); + } else { + return super.getMessage() + "; useData(" + useData + ")"; + } + } +} diff --git a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTHelper.java b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTHelper.java index 8c1c324f..a69e52ef 100644 --- a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTHelper.java +++ b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTHelper.java @@ -5,7 +5,15 @@ protected static T run(CheckedSupplier supplier) { try { return supplier.get(); } catch (Exception e) { - throw new RuntimeException(e); + throw new JWTException(e); + } + } + + protected static T run(CheckedSupplier supplier, String useData) { + try { + return supplier.get(); + } catch (Exception e) { + throw new JWTException(e, useData); } } diff --git a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTReader.java b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTReader.java index 6a92cc98..48324ce8 100644 --- a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTReader.java +++ b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTReader.java @@ -3,6 +3,7 @@ import com.nimbusds.jose.JWSVerifier; import com.nimbusds.jose.crypto.MACVerifier; import com.nimbusds.jwt.SignedJWT; +import vook.server.api.globalcommon.helper.format.FormatHelper; import java.nio.charset.StandardCharsets; import java.util.Date; @@ -19,26 +20,29 @@ static JWTReader of(String secret, String token) { reader.verifier = new MACVerifier(secretBytes); reader.signedJWT = SignedJWT.parse(token); return reader; - }); + }, FormatHelper.slf4j("token: {}", token)); } public void validate() { run(() -> { if (!signedJWT.verify(verifier)) { - throw new IllegalArgumentException("JWT의 서명이 올바르지 않습니다."); + throw new JWTException(new IllegalArgumentException("JWT의 서명이 올바르지 않습니다.")); } Date expirationTime = signedJWT.getJWTClaimsSet().getExpirationTime(); if (expirationTime.before(new Date())) { - throw new IllegalArgumentException("JWT가 만료되었습니다."); + throw new JWTException(new IllegalArgumentException("JWT가 만료되었습니다.")); } return null; - }); + }, FormatHelper.slf4j("signedJWT: {}, verifier: {}", signedJWT.serialize(), verifier)); } public String getClaim(String claimName) { - return run(() -> signedJWT.getJWTClaimsSet().getStringClaim(claimName)); + return run( + () -> signedJWT.getJWTClaimsSet().getStringClaim(claimName), + FormatHelper.slf4j("claimName: {}, signedJWT: {}", claimName, signedJWT.serialize()) + ); } public static class Builder { diff --git a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTWriter.java b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTWriter.java index dc6e8190..0a17d3b4 100644 --- a/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTWriter.java +++ b/server/api/src/main/java/vook/server/api/globalcommon/helper/jwt/JWTWriter.java @@ -8,6 +8,7 @@ import com.nimbusds.jwt.SignedJWT; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import vook.server.api.globalcommon.helper.format.FormatHelper; import java.nio.charset.StandardCharsets; import java.util.Date; @@ -58,7 +59,7 @@ public String jwtString() { signedJWT.sign(signer); return signedJWT.serialize(); - }); + }, FormatHelper.slf4j("claims: {}", claims)); } public static class Builder { diff --git a/server/api/src/main/java/vook/server/api/web/common/auth/jwt/JWTFilter.java b/server/api/src/main/java/vook/server/api/web/common/auth/jwt/JWTFilter.java index 655f237b..fb81c4be 100644 --- a/server/api/src/main/java/vook/server/api/web/common/auth/jwt/JWTFilter.java +++ b/server/api/src/main/java/vook/server/api/web/common/auth/jwt/JWTFilter.java @@ -11,6 +11,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +import vook.server.api.globalcommon.helper.jwt.JWTException; import vook.server.api.web.common.auth.app.TokenService; import vook.server.api.web.common.auth.data.AuthValues; import vook.server.api.web.common.auth.data.VookLoginUser; @@ -34,8 +35,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse OAuth2User oAuth2User; try { oAuth2User = VookLoginUser.of(tokenService.validateAndGetUid(token)); - } catch (Exception e) { - log.debug("JWT validation failed", e); + } catch (JWTException e) { + log.warn(e.getMessage()); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } diff --git a/server/api/src/main/java/vook/server/api/web/common/response/GlobalControllerAdvice.java b/server/api/src/main/java/vook/server/api/web/common/response/GlobalControllerAdvice.java deleted file mode 100644 index b506cf20..00000000 --- a/server/api/src/main/java/vook/server/api/web/common/response/GlobalControllerAdvice.java +++ /dev/null @@ -1,20 +0,0 @@ -package vook.server.api.web.common.response; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -@Slf4j -@ControllerAdvice -public class GlobalControllerAdvice { - - @Value("${service.oauth2.loginFailUrl}") - private String loginFailUrl; - - @ExceptionHandler(Exception.class) - public String handleException(Exception e) { - log.error(e.getMessage(), e); - return "redirect:" + loginFailUrl; - } -} diff --git a/server/api/src/main/java/vook/server/api/web/common/response/GlobalRestControllerAdvice.java b/server/api/src/main/java/vook/server/api/web/common/response/GlobalRestControllerAdvice.java index 2658b9a3..5b73355e 100644 --- a/server/api/src/main/java/vook/server/api/web/common/response/GlobalRestControllerAdvice.java +++ b/server/api/src/main/java/vook/server/api/web/common/response/GlobalRestControllerAdvice.java @@ -1,12 +1,15 @@ package vook.server.api.web.common.response; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; import vook.server.api.globalcommon.exception.AppException; +import vook.server.api.globalcommon.helper.jwt.JWTException; @Slf4j @RestControllerAdvice @@ -20,6 +23,13 @@ public ResponseEntity handleAppException(AppException e) { return ResponseEntity.status(badRequest.statusCode()).body(badRequest.response()); } + @ExceptionHandler(JWTException.class) + public ResponseEntity handleJWTException(JWTException e) { + log.debug(e.getMessage(), e); + CommonApiException serverError = CommonApiException.serverError(ApiResponseCode.ServerError.UNHANDLED_ERROR, e); + return ResponseEntity.status(serverError.statusCode()).body(serverError.response()); + } + @ExceptionHandler(CommonApiException.class) public ResponseEntity handleCommonApiException(CommonApiException e) { log.debug(e.getMessage(), e); @@ -40,6 +50,12 @@ public ResponseEntity handleHttpMessageConversionException(HttpMessageConvers return ResponseEntity.status(badRequest.statusCode()).body(badRequest.response()); } + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFoundException(NoResourceFoundException e) { + log.debug(e.getMessage(), e); + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error(e.getMessage(), e); diff --git a/server/api/src/test/resources/migrate/sql b/server/api/src/test/resources/migrate/sql index ffd67b6c..dd0c6e85 160000 --- a/server/api/src/test/resources/migrate/sql +++ b/server/api/src/test/resources/migrate/sql @@ -1 +1 @@ -Subproject commit ffd67b6cc14c1b6ff70df58308406364149448f6 +Subproject commit dd0c6e85fdbd4d6b823e8fcbb45e5e1726c3fe5a