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 01/23] =?UTF-8?q?feat=20:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?jwtProvider=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 02/23] =?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 03/23] =?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 04/23] =?UTF-8?q?feat=20:=20token=20dto=20=EA=B5=AC?= =?UTF-8?q?=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 1da8ed914c3811420fcba02ccbfec7c80bb322dd 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 22:01:04 +0900 Subject: [PATCH 05/23] =?UTF-8?q?feat=20:=20@AuthenticationPrincipal?= =?UTF-8?q?=EC=9D=84=20=EB=8C=80=EC=B2=B4=ED=95=A0=20@Login=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=B0=8F=20LoginUserArg?= =?UTF-8?q?umentResolver=EB=A5=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terningserver/auth/config/Login.java | 11 +++++++++ .../auth/config/LoginCheckInterceptor.java | 24 +++++++++++++++++++ .../config/LoginUserArgumentResolver.java | 24 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 src/main/java/org/terning/terningserver/auth/config/Login.java create mode 100644 src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java create mode 100644 src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java diff --git a/src/main/java/org/terning/terningserver/auth/config/Login.java b/src/main/java/org/terning/terningserver/auth/config/Login.java new file mode 100644 index 0000000..4783d06 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/config/Login.java @@ -0,0 +1,11 @@ +package org.terning.terningserver.auth.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Login { +} diff --git a/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java new file mode 100644 index 0000000..73d324f --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java @@ -0,0 +1,24 @@ +package org.terning.terningserver.auth.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.terning.terningserver.auth.jwt.JwtProvider; + +@Component +@RequiredArgsConstructor +public class LoginCheckInterceptor implements HandlerInterceptor { + + private final JwtProvider jwtProvider; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String authorizationHeader = request.getHeader("Authorization"); + Long userId = jwtProvider.getUserIdFrom(authorizationHeader); + + request.setAttribute("userId", userId); + return true; + } +} diff --git a/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java b/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java new file mode 100644 index 0000000..5f3ede2 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java @@ -0,0 +1,24 @@ +package org.terning.terningserver.auth.config; + +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Login.class) + && Long.class.isAssignableFrom(parameter.getParameterType()); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return webRequest.getAttribute("userId", NativeWebRequest.SCOPE_REQUEST); + } +} From 74c4cbbb16c9976c5115be28dc7d7ce3471135a7 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 22:01:41 +0900 Subject: [PATCH 06/23] =?UTF-8?q?feat=20:=20=EB=B9=84=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EC=83=81=ED=83=9C=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/WebConfig.java | 67 +++++++++++++++++++ .../common/config/WebMvcConfig.java | 24 ------- 2 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/terning/terningserver/common/config/WebConfig.java delete mode 100644 src/main/java/org/terning/terningserver/common/config/WebMvcConfig.java diff --git a/src/main/java/org/terning/terningserver/common/config/WebConfig.java b/src/main/java/org/terning/terningserver/common/config/WebConfig.java new file mode 100644 index 0000000..21d4bf7 --- /dev/null +++ b/src/main/java/org/terning/terningserver/common/config/WebConfig.java @@ -0,0 +1,67 @@ +package org.terning.terningserver.common.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.terning.terningserver.auth.config.LoginCheckInterceptor; +import org.terning.terningserver.auth.config.LoginUserArgumentResolver; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final LoginCheckInterceptor loginCheckInterceptor; + private final LoginUserArgumentResolver loginUserArgumentResolver; + +// private static final String[] AUTH_WHITELIST = { +// "/v3/api-docs/**", +// "/swagger-ui.html", +// "/swagger-resources/**", +// "/swagger-ui/**", +// +// "/api/v1/auth/sign-in", +// "/api/v1/auth/sign-up", +// "/api/v1/auth/sign-up/filter", +// "/api/v1/auth/token-reissue", +// +// "/api/v1/search/banners", +// "/api/v1/search/views", +// "/api/v1/search/scraps", +// +// "/actuator/health", +// "/api/v1/external/scraps/unsynced", +// "/api/v1/external/scraps/sync/result" +// }; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins( + "http://localhost:8080", + "http://localhost:3000", + "https://www.terning-official.p-e.kr/", + "https://www.terning-official.n-e.kr/", + "http://15.165.242.132", + "http://54.180.215.35") + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") + .allowCredentials(true); + } + +// @Override +// public void addInterceptors(InterceptorRegistry registry) { +// registry.addInterceptor(loginCheckInterceptor) +// .order(1) +// .addPathPatterns("/api/v1/**") +// .excludePathPatterns(AUTH_WHITELIST); +// } +// +// @Override +// public void addArgumentResolvers(List resolvers) { +// resolvers.add(loginUserArgumentResolver); +// } +} diff --git a/src/main/java/org/terning/terningserver/common/config/WebMvcConfig.java b/src/main/java/org/terning/terningserver/common/config/WebMvcConfig.java deleted file mode 100644 index df3a59a..0000000 --- a/src/main/java/org/terning/terningserver/common/config/WebMvcConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.terning.terningserver.common.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration - -public class WebMvcConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins( - "http://localhost:8080", - "http://localhost:3000", - "https://www.terning-official.p-e.kr/", - "https://www.terning-official.n-e.kr/", - "http://15.165.242.132", - "http://54.180.215.35") // 허용할 출처 : 특정 도메인만 받을 수 있음 - .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH") // 허용할 HTTP method - .allowCredentials(true); // 쿠키 인증 요청 허용 - } -} From 13c57b6e9ec28b219bb72cff1ab3c3f41cfa211c 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 22:45:34 +0900 Subject: [PATCH 07/23] =?UTF-8?q?refactor(auth):=20Optional=EC=9D=84=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20LoginCheckInterceptor=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/config/LoginCheckInterceptor.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java index 73d324f..c694238 100644 --- a/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java +++ b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java @@ -7,18 +7,31 @@ import org.springframework.web.servlet.HandlerInterceptor; import org.terning.terningserver.auth.jwt.JwtProvider; +import java.util.Optional; + @Component @RequiredArgsConstructor public class LoginCheckInterceptor implements HandlerInterceptor { private final JwtProvider jwtProvider; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + private static final String USER_ID_ATTRIBUTE_NAME = "userId"; + @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - String authorizationHeader = request.getHeader("Authorization"); - Long userId = jwtProvider.getUserIdFrom(authorizationHeader); + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Optional userIdOpt = Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER)) + .filter(header -> header.startsWith(BEARER_PREFIX)) + .map(header -> header.substring(BEARER_PREFIX.length())) + .flatMap(token -> Optional.ofNullable(jwtProvider.getUserIdFrom(token))); + + if (userIdOpt.isEmpty()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } - request.setAttribute("userId", userId); + request.setAttribute(USER_ID_ATTRIBUTE_NAME, userIdOpt.get()); return true; } } From b46596ba6a002108719a851a89c4d5ac645e20d9 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 22:45:42 +0900 Subject: [PATCH 08/23] =?UTF-8?q?feat(auth):=20LoginUserArgumentResolver?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=9B=90=EC=8B=9C=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?long=20=EC=A7=80=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/config/LoginUserArgumentResolver.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java b/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java index 5f3ede2..bbb879a 100644 --- a/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java +++ b/src/main/java/org/terning/terningserver/auth/config/LoginUserArgumentResolver.java @@ -10,15 +10,19 @@ @Component public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver { + private static final String USER_ID_ATTRIBUTE_NAME = "userId"; + @Override public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(Login.class) - && Long.class.isAssignableFrom(parameter.getParameterType()); + boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class); + boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()) || parameter.getParameterType().equals(long.class); + + return hasLoginAnnotation && isLongType; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - return webRequest.getAttribute("userId", NativeWebRequest.SCOPE_REQUEST); + return webRequest.getAttribute(USER_ID_ATTRIBUTE_NAME, NativeWebRequest.SCOPE_REQUEST); } } 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 09/23] =?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 +} From 55a59c132518d13f82a68f287ce01a8788e2d104 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:24:07 +0900 Subject: [PATCH 10/23] =?UTF-8?q?refactor=20:=20AuthService=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/AuthService.java | 125 +++++++++++++----- .../reissue/AuthReissueService.java | 7 - .../reissue/AuthReissueServiceImpl.java | 35 ----- .../application/signin/AuthSignInService.java | 8 -- .../signin/AuthSignInServiceImpl.java | 60 --------- .../signout/AuthSignOutService.java | 5 - .../signout/AuthSignOutServiceImpl.java | 29 ---- .../application/signup/AuthSignUpService.java | 10 -- .../signup/AuthSignUpServiceImpl.java | 105 --------------- .../syncUser/AuthSyncUserService.java | 8 -- .../syncUser/AuthSyncUserServiceImpl.java | 29 ---- .../withdraw/AuthWithdrawService.java | 5 - .../withdraw/AuthWithdrawServiceImpl.java | 32 ----- .../auth/common/exception/AuthErrorCode.java | 3 + .../auth/dto/request/SignUpFilterRequest.java | 9 ++ .../dto/request/SignUpFilterRequestDto.java | 23 ---- .../auth/dto/request/SignUpRequest.java | 6 + .../auth/dto/request/SignUpRequestDto.java | 26 ---- .../response/AccessTokenGetResponseDto.java | 4 +- .../auth/dto/response/SignInResponse.java | 32 +++-- .../auth/dto/response/SignUpResponse.java | 16 +++ .../auth/dto/response/SignUpResponseDto.java | 16 --- .../dto/response/TokenReissueResponse.java | 4 + .../terningserver/filter/domain/Filter.java | 18 ++- .../terningserver/user/domain/User.java | 65 ++++++--- .../user/repository/UserRepository.java | 2 + 26 files changed, 221 insertions(+), 461 deletions(-) delete mode 100644 src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueServiceImpl.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInServiceImpl.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutServiceImpl.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpServiceImpl.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserServiceImpl.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawService.java delete mode 100644 src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawServiceImpl.java create mode 100644 src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequest.java delete mode 100644 src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequestDto.java create mode 100644 src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequest.java delete mode 100644 src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequestDto.java create mode 100644 src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponse.java delete mode 100644 src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponseDto.java create mode 100644 src/main/java/org/terning/terningserver/auth/dto/response/TokenReissueResponse.java 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 58a3326..9cab2e6 100644 --- a/src/main/java/org/terning/terningserver/auth/application/AuthService.java +++ b/src/main/java/org/terning/terningserver/auth/application/AuthService.java @@ -1,69 +1,134 @@ package org.terning.terningserver.auth.application; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.auth.application.reissue.AuthReissueService; -import org.terning.terningserver.auth.application.signin.AuthSignInService; -import org.terning.terningserver.auth.application.signout.AuthSignOutService; -import org.terning.terningserver.auth.application.signup.AuthSignUpService; -import org.terning.terningserver.auth.application.syncUser.AuthSyncUserService; -import org.terning.terningserver.auth.application.withdraw.AuthWithdrawService; +import org.terning.terningserver.auth.application.social.SocialAuthProvider; +import org.terning.terningserver.auth.application.social.SocialAuthServiceManager; +import org.terning.terningserver.auth.common.exception.AuthErrorCode; +import org.terning.terningserver.auth.common.exception.AuthException; +import org.terning.terningserver.auth.dto.Token; import org.terning.terningserver.auth.dto.request.FcmTokenSyncRequest; import org.terning.terningserver.auth.dto.request.SignInRequest; -import org.terning.terningserver.auth.dto.request.SignUpFilterRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpRequestDto; -import org.terning.terningserver.auth.dto.response.AccessTokenGetResponseDto; +import org.terning.terningserver.auth.dto.request.SignUpFilterRequest; +import org.terning.terningserver.auth.dto.request.SignUpRequest; import org.terning.terningserver.auth.dto.response.SignInResponse; -import org.terning.terningserver.auth.dto.response.SignUpResponseDto; +import org.terning.terningserver.auth.dto.response.SignUpResponse; +import org.terning.terningserver.auth.dto.response.TokenReissueResponse; +import org.terning.terningserver.auth.jwt.JwtProvider; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; +import org.terning.terningserver.external.pushNotification.notification.NotificationUserClient; +import org.terning.terningserver.filter.domain.Filter; +import org.terning.terningserver.filter.repository.FilterRepository; +import org.terning.terningserver.user.application.UserService; +import org.terning.terningserver.user.domain.User; +import org.terning.terningserver.user.event.UserSignedUpEvent; +import org.terning.terningserver.user.repository.UserRepository; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class AuthService { - private final AuthSignInService authSignInService; - private final AuthSignUpService authSignUpService; - private final AuthSignOutService authSignOutService; - private final AuthWithdrawService authWithdrawService; - private final AuthReissueService authReissueService; - private final AuthSyncUserService authSyncUserService; + private final UserService userService; + private final UserRepository userRepository; + private final JwtProvider jwtProvider; + private final SocialAuthServiceManager socialAuthServiceManager; + private final ApplicationEventPublisher eventPublisher; + private final NotificationUserClient notificationUserClient; + private final FilterRepository filterRepository; @Transactional - public SignInResponse signIn(String authAccessToken, SignInRequest request) { - SignInResponse signInResponse = authSignInService.signIn(authAccessToken, request); - return signInResponse; + public SignInResponse signIn(String socialAccessToken, SignInRequest request) { + SocialAuthProvider provider = socialAuthServiceManager.getAuthService(request.authType()); + String authId = provider.getAuthId(socialAccessToken); + + User user = userRepository.findByAuthIdAndAuthType(authId, request.authType()) + .orElse(null); + + if (user == null) { + return SignInResponse.ofNewUser(authId, request.authType()); + } + + Token token = jwtProvider.generateTokens(user.getId()); + user.updateRefreshToken(token.refreshToken()); + userRepository.save(user); + + return SignInResponse.ofExistingUser(token, authId, request.authType(), user.getId()); } @Transactional - public SignUpResponseDto signUp(String authId, SignUpRequestDto request) { - SignUpResponseDto signUpResponseDto = authSignUpService.signUp(authId, request); - return signUpResponseDto; + public SignUpResponse signUp(String authId, SignUpRequest request) { + if (userRepository.existsByAuthIdAndAuthType(authId, request.authType())) { + throw new AuthException(AuthErrorCode.USER_ALREADY_EXIST); + } + + User newUser = User.from(request); + + Token token = jwtProvider.generateTokens(newUser.getId()); + + newUser.updateRefreshToken(token.refreshToken()); + + userRepository.save(newUser); + + eventPublisher.publishEvent(UserSignedUpEvent.of(newUser)); + + notificationUserClient.createUserOnNotificationServer( + newUser.getId(), + newUser.getName(), + newUser.getAuthType(), + request.fcmToken() + ); + + return SignUpResponse.of(token, newUser); } @Transactional - public void registerFilterWithUser(Long userId, SignUpFilterRequestDto request) { - authSignUpService.registerFilterWithUser(userId, request); + public void registerUserFilter(Long userId, SignUpFilterRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + + Filter newFilter = Filter.from(request); + filterRepository.save(newFilter); + + user.assignFilter(newFilter); } @Transactional public void signOut(long userId) { - authSignOutService.signOut(userId); + User user = userRepository.findById(userId).orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + user.resetRefreshToken(); } @Transactional public void withdraw(long userId) { - authWithdrawService.withdraw(userId); + User user = userRepository.findById(userId).orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + userService.deleteUser(user); } @Transactional - public AccessTokenGetResponseDto reissueToken(String refreshToken) { - AccessTokenGetResponseDto accessTokenGetResponseDto = authReissueService.reissueToken(refreshToken); - return accessTokenGetResponseDto; + public TokenReissueResponse reissueAccessToken(String authorizationHeader) { + Long userId = jwtProvider.getUserIdFrom(authorizationHeader); + + User user = userRepository.findById(userId) + .orElseThrow(() -> new JwtException(JwtErrorCode.INVALID_JWT_TOKEN)); + + String providedToken = jwtProvider.resolveToken(authorizationHeader); + user.validateRefreshToken(providedToken); + + Token accessToken = jwtProvider.generateAccessToken(userId); + + + return new TokenReissueResponse(accessToken.accessToken()); } @Transactional public void syncUser(long userId, FcmTokenSyncRequest request) { - authSyncUserService.syncUser(userId, request); + User user = userRepository.findById(userId) + .orElseThrow(() -> new AuthException(AuthErrorCode.USER_NOT_FOUND)); + + notificationUserClient.createOrUpdateUser(user, request.fcmToken()); } } diff --git a/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueService.java b/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueService.java deleted file mode 100644 index e4947ce..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueService.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.terning.terningserver.auth.application.reissue; - -import org.terning.terningserver.auth.dto.response.AccessTokenGetResponseDto; - -public interface AuthReissueService { - AccessTokenGetResponseDto reissueToken(String refreshToken); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueServiceImpl.java deleted file mode 100644 index b931e93..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/reissue/AuthReissueServiceImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.terning.terningserver.auth.application.reissue; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.common.security.jwt.application.JwtTokenManager; -import org.terning.terningserver.user.domain.Token; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.auth.dto.response.AccessTokenGetResponseDto; -import org.terning.terningserver.common.exception.CustomException; -import org.terning.terningserver.common.security.jwt.auth.TokenExtractor; -import org.terning.terningserver.user.repository.UserRepository; - -import static org.terning.terningserver.common.exception.enums.ErrorMessage.FAILED_TOKEN_REISSUE; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class AuthReissueServiceImpl implements AuthReissueService { - - private final JwtTokenManager jwtTokenManager; - private final UserRepository userRepository; - - @Override - public AccessTokenGetResponseDto reissueToken(String refreshToken) { - User user = findUserByRefreshToken(refreshToken); - Token accessToken = jwtTokenManager.issueAccessToken(user); - return AccessTokenGetResponseDto.of(accessToken); - } - - private User findUserByRefreshToken(String refreshToken) { - return userRepository.findByRefreshToken(TokenExtractor.extractToken(refreshToken)) - .orElseThrow(() -> new CustomException(FAILED_TOKEN_REISSUE)); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInService.java b/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInService.java deleted file mode 100644 index 2c713d3..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInService.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.terning.terningserver.auth.application.signin; - -import org.terning.terningserver.auth.dto.response.SignInResponse; -import org.terning.terningserver.auth.dto.request.SignInRequest; - -public interface AuthSignInService { - SignInResponse signIn(String authAccessToken, SignInRequest request); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInServiceImpl.java deleted file mode 100644 index 7c63aa4..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signin/AuthSignInServiceImpl.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.terning.terningserver.auth.application.signin; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.auth.application.social.SocialAuthProvider; -import org.terning.terningserver.auth.application.social.SocialAuthServiceManager; -import org.terning.terningserver.external.pushNotification.notification.NotificationUserClient; -import org.terning.terningserver.common.security.jwt.application.JwtTokenManager; -import org.terning.terningserver.user.domain.Token; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.user.domain.AuthType; -import org.terning.terningserver.auth.dto.request.SignInRequest; -import org.terning.terningserver.auth.dto.response.SignInResponse; -import org.terning.terningserver.user.repository.UserRepository; - -@Service -@RequiredArgsConstructor -public class AuthSignInServiceImpl implements AuthSignInService { - - private final SocialAuthServiceManager socialAuthServiceManager; - private final JwtTokenManager jwtTokenManager; - private final UserRepository userRepository; - private final NotificationUserClient notificationUserClient; -// private final FcmTokenValidationClient fcmTokenValidationClient; - - @Transactional - @Override - public SignInResponse signIn(String authAccessToken, SignInRequest request) { - String authId = getAuthId(request.authType(), authAccessToken); - User user = findUserByAuthIdAndType(authId, request.authType()); - - if (user == null) { -// return SignInResponse.of(null, authId, request.authType(), null, false); - return SignInResponse.of(null, authId, request.authType(), null); - } - - Token token = jwtTokenManager.generateToken(user); - user.updateRefreshToken(token.getRefreshToken()); - -// boolean fcmReissueRequired = fcmTokenValidationClient.requestFcmTokenValidation(user.getId()); - - if (request.fcmToken() != null && !request.fcmToken().trim().isEmpty()) { - notificationUserClient.createOrUpdateUser(user, request.fcmToken()); - } - -// return SignInResponse.of(token, authId, request.authType(), user.getId(), fcmReissueRequired); - return SignInResponse.of(token, authId, request.authType(), user.getId()); - } - - - private String getAuthId(AuthType authType, String authAccessToken) { - SocialAuthProvider provider = socialAuthServiceManager.getAuthService(authType); - return provider.getAuthId(authAccessToken); - } - - private User findUserByAuthIdAndType(String authId, AuthType authType) { - return userRepository.findByAuthIdAndAuthType(authId, authType).orElse(null); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutService.java b/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutService.java deleted file mode 100644 index d09d027..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutService.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.terning.terningserver.auth.application.signout; - -public interface AuthSignOutService { - void signOut(long userId); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutServiceImpl.java deleted file mode 100644 index cb87471..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signout/AuthSignOutServiceImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.terning.terningserver.auth.application.signout; - -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.common.exception.CustomException; -import org.terning.terningserver.user.repository.UserRepository; - -import static org.terning.terningserver.common.exception.enums.ErrorMessage.INVALID_USER; - -@Service -@RequiredArgsConstructor -public class AuthSignOutServiceImpl implements AuthSignOutService { - - private final UserRepository userRepository; - - @Transactional - @Override - public void signOut(long userId) { - val user = findUserById(userId); - user.resetRefreshToken(); - } - - private User findUserById(long id) { - return userRepository.findById(id).orElseThrow(() -> new CustomException(INVALID_USER)); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpService.java b/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpService.java deleted file mode 100644 index d80c0a5..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpService.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.terning.terningserver.auth.application.signup; - -import org.terning.terningserver.auth.dto.request.SignUpFilterRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpRequestDto; -import org.terning.terningserver.auth.dto.response.SignUpResponseDto; - -public interface AuthSignUpService { - SignUpResponseDto signUp(String authId, SignUpRequestDto request); - void registerFilterWithUser(Long userId, SignUpFilterRequestDto request); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpServiceImpl.java deleted file mode 100644 index b2eb793..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/signup/AuthSignUpServiceImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.terning.terningserver.auth.application.signup; - -import static org.terning.terningserver.common.exception.enums.ErrorMessage.FAILED_SIGN_UP_USER_FILTER_CREATION; - -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.filter.domain.Grade; -import org.terning.terningserver.filter.domain.JobType; -import org.terning.terningserver.user.domain.ProfileImage; -import org.terning.terningserver.user.domain.PushNotificationStatus; -import org.terning.terningserver.filter.domain.WorkingPeriod; -import org.terning.terningserver.external.pushNotification.notification.NotificationUserClient; -import org.terning.terningserver.common.security.jwt.application.JwtTokenManager; -import org.terning.terningserver.filter.domain.Filter; -import org.terning.terningserver.user.domain.Token; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.auth.dto.request.SignUpFilterRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpWithAuthIdRequestDto; -import org.terning.terningserver.auth.dto.response.SignUpResponseDto; -import org.terning.terningserver.user.event.UserSignedUpEvent; -import org.terning.terningserver.common.exception.CustomException; -import org.terning.terningserver.filter.repository.FilterRepository; -import org.terning.terningserver.user.repository.UserRepository; - -@Service -@RequiredArgsConstructor -public class AuthSignUpServiceImpl implements AuthSignUpService { - - private final JwtTokenManager jwtTokenManager; - private final UserRepository userRepository; - private final FilterRepository filterRepository; - private final ApplicationEventPublisher eventPublisher; - private final NotificationUserClient notificationUserClient; - - @Transactional - @Override - public SignUpResponseDto signUp(String authId, SignUpRequestDto request) { - String tokenWithoutBearer = authId.replace("Bearer ", "").trim(); - SignUpWithAuthIdRequestDto requestDto = createSignUpRequestDto(tokenWithoutBearer, request); - User user = createUser(requestDto); - Token token = jwtTokenManager.generateToken(user); - user.updateRefreshToken(token.getRefreshToken()); - eventPublisher.publishEvent(UserSignedUpEvent.of(user)); - - notificationUserClient.createUserOnNotificationServer( - user.getId(), - user.getName(), - user.getAuthType(), - request.fcmToken() - ); - - return createSignUpResponseDto(token, user); - } - - @Transactional - @Override - public void registerFilterWithUser(Long userId, SignUpFilterRequestDto request) { - val user = userRepository.findById(userId) - .orElseThrow(() -> new CustomException(FAILED_SIGN_UP_USER_FILTER_CREATION)); - - Filter filter = buildFilterFromRequest(request); - filterRepository.save(filter); - - user.assignFilter(filter); - userRepository.save(user); - } - - private SignUpWithAuthIdRequestDto createSignUpRequestDto(String authId, SignUpRequestDto request) { - return SignUpWithAuthIdRequestDto.of( - authId, - request.name(), - request.profileImage(), - request.authType() - ); - } - - private User createUser(SignUpWithAuthIdRequestDto requestDto) { - User user = User.builder() - .authId(requestDto.authId()) - .name(requestDto.name()) - .authType(requestDto.authType()) - .profileImage(ProfileImage.fromValue(requestDto.profileImage())) - .pushStatus(PushNotificationStatus.ENABLED) - .build(); - return userRepository.save(user); - } - - private SignUpResponseDto createSignUpResponseDto(Token token, User user) { - return SignUpResponseDto.of(token.getAccessToken(), token.getRefreshToken(), user.getId(), user.getAuthType()); - } - - private Filter buildFilterFromRequest(SignUpFilterRequestDto request) { - return Filter.builder() - .jobType(JobType.TOTAL) - .grade(Grade.fromKey(request.grade())) - .workingPeriod(WorkingPeriod.fromKey(request.workingPeriod())) - .startYear(request.startYear()) - .startMonth(request.startMonth()) - .build(); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserService.java b/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserService.java deleted file mode 100644 index 205dee8..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserService.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.terning.terningserver.auth.application.syncUser; - -import org.terning.terningserver.auth.dto.request.FcmTokenSyncRequest; - -public interface AuthSyncUserService { - - void syncUser(long userId, FcmTokenSyncRequest request); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserServiceImpl.java deleted file mode 100644 index 731d5f0..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/syncUser/AuthSyncUserServiceImpl.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.terning.terningserver.auth.application.syncUser; - -import static org.terning.terningserver.common.exception.enums.ErrorMessage.NOT_FOUND_USER_EXCEPTION; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.auth.dto.request.FcmTokenSyncRequest; -import org.terning.terningserver.common.exception.CustomException; -import org.terning.terningserver.external.pushNotification.notification.NotificationUserClient; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.user.repository.UserRepository; - -@Service -@RequiredArgsConstructor -public class AuthSyncUserServiceImpl implements AuthSyncUserService { - - private final UserRepository userRepository; - private final NotificationUserClient notificationUserClient; - - @Transactional - @Override - public void syncUser(long userId, FcmTokenSyncRequest request) { - User user = userRepository.findById(userId) - .orElseThrow(() -> new CustomException(NOT_FOUND_USER_EXCEPTION)); - - notificationUserClient.createOrUpdateUser(user, request.fcmToken()); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawService.java b/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawService.java deleted file mode 100644 index ff550ba..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawService.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.terning.terningserver.auth.application.withdraw; - -public interface AuthWithdrawService { - void withdraw(long userId); -} diff --git a/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawServiceImpl.java b/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawServiceImpl.java deleted file mode 100644 index 8e238b9..0000000 --- a/src/main/java/org/terning/terningserver/auth/application/withdraw/AuthWithdrawServiceImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.terning.terningserver.auth.application.withdraw; - -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.common.exception.CustomException; -import org.terning.terningserver.user.repository.UserRepository; -import org.terning.terningserver.user.application.UserService; - -import static org.terning.terningserver.common.exception.enums.ErrorMessage.INVALID_USER; - -@Service -@RequiredArgsConstructor -public class AuthWithdrawServiceImpl implements AuthWithdrawService { - - private final UserRepository userRepository; - private final UserService userService; - - @Transactional - @Override - public void withdraw(long userId) { - val user = findUserById(userId); - - userService.deleteUser(user); - } - - private User findUserById(long id) { - return userRepository.findById(id).orElseThrow(() -> new CustomException(INVALID_USER)); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/common/exception/AuthErrorCode.java b/src/main/java/org/terning/terningserver/auth/common/exception/AuthErrorCode.java index 40984df..961884e 100644 --- a/src/main/java/org/terning/terningserver/auth/common/exception/AuthErrorCode.java +++ b/src/main/java/org/terning/terningserver/auth/common/exception/AuthErrorCode.java @@ -7,6 +7,9 @@ @Getter @AllArgsConstructor public enum AuthErrorCode { + + USER_ALREADY_EXIST(HttpStatus.BAD_REQUEST, "유저가 이미 존재합니다."), + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다."), ; public static final String PREFIX = "[AUTH ERROR]"; diff --git a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequest.java b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequest.java new file mode 100644 index 0000000..68fb391 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequest.java @@ -0,0 +1,9 @@ +package org.terning.terningserver.auth.dto.request; + +public record SignUpFilterRequest( + String grade, + String workingPeriod, + int startYear, + int startMonth +) { +} diff --git a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequestDto.java b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequestDto.java deleted file mode 100644 index ae76d68..0000000 --- a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpFilterRequestDto.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.terning.terningserver.auth.dto.request; - -import lombok.Builder; - -import static lombok.AccessLevel.PRIVATE; - -@Builder(access = PRIVATE) -public record SignUpFilterRequestDto( - String grade, - String workingPeriod, - int startYear, - int startMonth - -) { - public static SignUpFilterRequestDto of(String grade, String workingPeriod, int startYear, int startMonth) { - return SignUpFilterRequestDto.builder() - .grade(grade) - .workingPeriod(workingPeriod) - .startYear(startYear) - .startMonth(startMonth) - .build(); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequest.java b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequest.java new file mode 100644 index 0000000..ca42b12 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequest.java @@ -0,0 +1,6 @@ +package org.terning.terningserver.auth.dto.request; + +import org.terning.terningserver.user.domain.AuthType; + +public record SignUpRequest(String name, String profileImage, AuthType authType, String fcmToken) { +} diff --git a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequestDto.java b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequestDto.java deleted file mode 100644 index 108b3fd..0000000 --- a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpRequestDto.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.terning.terningserver.auth.dto.request; - -import lombok.Builder; -import lombok.NonNull; -import org.terning.terningserver.user.domain.AuthType; - -import static lombok.AccessLevel.PRIVATE; - -@Builder(access = PRIVATE) -public record SignUpRequestDto( - @NonNull String name, - String profileImage, - @NonNull AuthType authType, - String fcmToken -) { - - public static SignUpRequestDto of(String name, String profileImage, AuthType authType, String fcmToken){ - return SignUpRequestDto.builder() - .name(name) - .profileImage(profileImage) - .authType(authType) - .fcmToken(fcmToken) - .build(); - } - -} diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java b/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java index 8bebd77..725f9da 100644 --- a/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java +++ b/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java @@ -2,7 +2,7 @@ import lombok.Builder; import lombok.NonNull; -import org.terning.terningserver.user.domain.Token; +import org.terning.terningserver.auth.dto.Token; import static lombok.AccessLevel.*; @@ -13,7 +13,7 @@ public record AccessTokenGetResponseDto( public static AccessTokenGetResponseDto of(Token accessToken) { return AccessTokenGetResponseDto.builder() - .accessToken(accessToken.getAccessToken()) + .accessToken(accessToken.accessToken()) .build(); } } diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/SignInResponse.java b/src/main/java/org/terning/terningserver/auth/dto/response/SignInResponse.java index 5b7c837..8b1f239 100644 --- a/src/main/java/org/terning/terningserver/auth/dto/response/SignInResponse.java +++ b/src/main/java/org/terning/terningserver/auth/dto/response/SignInResponse.java @@ -1,26 +1,34 @@ package org.terning.terningserver.auth.dto.response; -import org.terning.terningserver.user.domain.Token; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.terning.terningserver.auth.dto.Token; import org.terning.terningserver.user.domain.AuthType; -import java.util.Optional; - +@JsonInclude(JsonInclude.Include.NON_NULL) public record SignInResponse( String accessToken, String refreshToken, - Long userId, String authId, - AuthType authType -// boolean fcmTokenReissueRequired + AuthType authType, + Long userId ) { - public static SignInResponse of(Token token, String authId, AuthType authType, Long userId) { + public static SignInResponse ofExistingUser(Token token, String authId, AuthType authType, Long userId) { + return new SignInResponse( + token.accessToken(), + token.refreshToken(), + authId, + authType, + userId + ); + } + + public static SignInResponse ofNewUser(String authId, AuthType authType) { return new SignInResponse( - Optional.ofNullable(token).map(Token::getAccessToken).orElse(null), - Optional.ofNullable(token).map(Token::getRefreshToken).orElse(null), - userId, + null, + null, authId, - authType -// fcmTokenReissueRequired + authType, + null ); } } diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponse.java b/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponse.java new file mode 100644 index 0000000..155f1e7 --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponse.java @@ -0,0 +1,16 @@ +package org.terning.terningserver.auth.dto.response; + +import org.terning.terningserver.auth.dto.Token; +import org.terning.terningserver.user.domain.AuthType; +import org.terning.terningserver.user.domain.User; + +public record SignUpResponse(String accessToken, String refreshToken, Long userId, AuthType authType) { + public static SignUpResponse of(Token token, User user) { + return new SignUpResponse( + token.accessToken(), + token.refreshToken(), + user.getId(), + user.getAuthType() + ); + } +} diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponseDto.java b/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponseDto.java deleted file mode 100644 index 3f47bb6..0000000 --- a/src/main/java/org/terning/terningserver/auth/dto/response/SignUpResponseDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.terning.terningserver.auth.dto.response; - -import org.terning.terningserver.user.domain.AuthType; - -public record SignUpResponseDto( - String accessToken, - String refreshToken, - Long userId, - AuthType authType -) { - public static SignUpResponseDto of(String accessToken, String refreshToken, Long userId, AuthType authType) { - return new SignUpResponseDto(accessToken, refreshToken, userId, authType); - } -} - - diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/TokenReissueResponse.java b/src/main/java/org/terning/terningserver/auth/dto/response/TokenReissueResponse.java new file mode 100644 index 0000000..87a21fe --- /dev/null +++ b/src/main/java/org/terning/terningserver/auth/dto/response/TokenReissueResponse.java @@ -0,0 +1,4 @@ +package org.terning.terningserver.auth.dto.response; + +public record TokenReissueResponse(String accessToken) { +} diff --git a/src/main/java/org/terning/terningserver/filter/domain/Filter.java b/src/main/java/org/terning/terningserver/filter/domain/Filter.java index d787dfb..f6f9bab 100644 --- a/src/main/java/org/terning/terningserver/filter/domain/Filter.java +++ b/src/main/java/org/terning/terningserver/filter/domain/Filter.java @@ -1,10 +1,15 @@ package org.terning.terningserver.filter.domain; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.terning.terningserver.auth.dto.request.SignUpFilterRequest; import static lombok.AccessLevel.PROTECTED; import static jakarta.persistence.GenerationType.IDENTITY; @@ -31,8 +36,17 @@ public class Filter { private int startYear; - private int startMonth; // 근무 시작 월 + private int startMonth; + public static Filter from(SignUpFilterRequest request) { + return Filter.builder() + .jobType(JobType.TOTAL) + .grade(Grade.fromKey(request.grade())) + .workingPeriod(WorkingPeriod.fromKey(request.workingPeriod())) + .startYear(request.startYear()) + .startMonth(request.startMonth()) + .build(); + } public void updateFilter(JobType jobType, Grade grade, WorkingPeriod workingPeriod, int startYear, int startMonth) { this.jobType = jobType; 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 9ee6a11..9aa9169 100644 --- a/src/main/java/org/terning/terningserver/user/domain/User.java +++ b/src/main/java/org/terning/terningserver/user/domain/User.java @@ -1,14 +1,32 @@ package org.terning.terningserver.user.domain; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.terning.terningserver.auth.dto.request.SignUpRequest; +import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; +import org.terning.terningserver.auth.jwt.exception.JwtException; import org.terning.terningserver.common.BaseTimeEntity; import org.terning.terningserver.common.exception.CustomException; +import org.terning.terningserver.filter.domain.Filter; +import org.terning.terningserver.scrap.domain.Scrap; import java.util.ArrayList; import java.util.List; -import org.terning.terningserver.filter.domain.Filter; -import org.terning.terningserver.scrap.domain.Scrap; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; @@ -25,38 +43,46 @@ public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = IDENTITY) - private Long id; // 사용자 고유 ID + private Long id; @OneToOne(fetch = LAZY) @JoinColumn(name="filter_id") - private Filter filter; // 사용자 필터 설정 - + private Filter filter; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) - private List scrapList = new ArrayList<>(); // 스크랩 공고 + private List scrapList = new ArrayList<>(); - // TODO: 특수문자, 첫글자 , 12자리 이내 @Column(length = 12) - private String name; // 사용자 이름 + private String name; @Enumerated(STRING) - private ProfileImage profileImage; //유저 아이콘 + private ProfileImage profileImage; @Enumerated(STRING) - private AuthType authType; // 인증 유형 (예: 카카오, 애플) + private AuthType authType; @Setter @Enumerated(EnumType.STRING) private PushNotificationStatus pushStatus; @Column(length = 256) - private String authId; // 인증 서비스에서 제공하는 고유 ID + private String authId; @Column(length = 256) - private String refreshToken; // 리프레시 토큰 + private String refreshToken; - // TODO: User가 생기면 active default로 바꾸기 @Enumerated(STRING) - private State state; // 사용자 상태 (예: 활성, 비활성, 정지) + private State state; + + public static User from(SignUpRequest request) { + return User.builder() + .name(request.name()) + .authType(request.authType()) + .profileImage(ProfileImage.fromValue(request.profileImage())) + .pushStatus(PushNotificationStatus.ENABLED) + .state(State.ACTIVE) + .build(); + } public void updateRefreshToken(String refreshToken) { this.refreshToken = refreshToken; @@ -74,9 +100,14 @@ public void assignFilter(Filter filter) { this.filter = filter; } - //프로필 수정 메서드 public void updateProfile(String name, ProfileImage profileImage){ this.name = name; this.profileImage = profileImage; } + + public void validateRefreshToken(String providedToken) { + if (this.refreshToken == null || !this.refreshToken.equals(providedToken)) { + throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); + } + } } diff --git a/src/main/java/org/terning/terningserver/user/repository/UserRepository.java b/src/main/java/org/terning/terningserver/user/repository/UserRepository.java index 39c2a0f..4fb6984 100644 --- a/src/main/java/org/terning/terningserver/user/repository/UserRepository.java +++ b/src/main/java/org/terning/terningserver/user/repository/UserRepository.java @@ -13,4 +13,6 @@ public interface UserRepository extends JpaRepository { Optional findByAuthId(String authId); Optional findByAuthIdAndAuthType(String authId, AuthType authType); + + boolean existsByAuthIdAndAuthType(String authId, AuthType authType); } From ac8507c8fa038df081ab3687890d11555a838950 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:24:21 +0900 Subject: [PATCH 11/23] =?UTF-8?q?refactor=20:=20AuthService=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/api/AuthController.java | 41 +++++++++---------- .../terningserver/auth/api/AuthSwagger.java | 24 +++++------ 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/terning/terningserver/auth/api/AuthController.java b/src/main/java/org/terning/terningserver/auth/api/AuthController.java index 7424494..9e08d92 100644 --- a/src/main/java/org/terning/terningserver/auth/api/AuthController.java +++ b/src/main/java/org/terning/terningserver/auth/api/AuthController.java @@ -1,18 +1,17 @@ package org.terning.terningserver.auth.api; import lombok.RequiredArgsConstructor; -import lombok.val; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.terning.terningserver.auth.application.AuthService; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.auth.dto.request.FcmTokenSyncRequest; import org.terning.terningserver.auth.dto.request.SignInRequest; -import org.terning.terningserver.auth.dto.request.SignUpFilterRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpRequestDto; -import org.terning.terningserver.auth.dto.response.AccessTokenGetResponseDto; +import org.terning.terningserver.auth.dto.request.SignUpFilterRequest; +import org.terning.terningserver.auth.dto.request.SignUpRequest; import org.terning.terningserver.auth.dto.response.SignInResponse; -import org.terning.terningserver.auth.dto.response.SignUpResponseDto; +import org.terning.terningserver.auth.dto.response.SignUpResponse; +import org.terning.terningserver.auth.dto.response.TokenReissueResponse; import org.terning.terningserver.common.exception.dto.SuccessResponse; import static org.terning.terningserver.auth.common.success.AuthSuccessCode.SUCCESS_SIGN_IN; @@ -33,48 +32,48 @@ public class AuthController implements AuthSwagger { @PostMapping("/sign-in") public ResponseEntity> signIn( - @RequestHeader("Authorization") String authAccessToken, + @RequestHeader("Authorization") String socialAccessToken, @RequestBody SignInRequest request ) { - return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_IN, authService.signIn(authAccessToken, request))); + SignInResponse response = authService.signIn(socialAccessToken, request); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_IN, response)); } @PostMapping("/token-reissue") - public ResponseEntity> reissueToken( - @RequestHeader("Authorization") String refreshToken + public ResponseEntity> reissueToken( + @RequestHeader("Authorization") String authorizationHeader ) { - val response = authService.reissueToken(refreshToken); - + TokenReissueResponse response = authService.reissueAccessToken(authorizationHeader); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_REISSUE_TOKEN, response)); } @PostMapping("/sign-up") - public ResponseEntity> signUp( + public ResponseEntity> signUp( @RequestHeader("Authorization") String authId, - @RequestBody SignUpRequestDto request + @RequestBody SignUpRequest request ) { - SignUpResponseDto signUpResponseDto = authService.signUp(authId, request); - return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_UP, signUpResponseDto)); + SignUpResponse signUpResponse = authService.signUp(authId, request); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_UP, signUpResponse)); } @PostMapping("/sign-up/filter") public ResponseEntity registerUserFilter( @RequestHeader("User-Id") Long userId, - @RequestBody SignUpFilterRequestDto request + @RequestBody SignUpFilterRequest request ) { - authService.registerFilterWithUser(userId, request); + authService.registerUserFilter(userId, request); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_UP_FILTER)); } @PostMapping("/logout") - public ResponseEntity signOut(@AuthenticationPrincipal Long userId) { + public ResponseEntity signOut(@Login Long userId) { authService.signOut(userId); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_SIGN_OUT)); } @DeleteMapping("/withdraw") - public ResponseEntity withdraw(@AuthenticationPrincipal Long userId) { + public ResponseEntity withdraw(@Login Long userId) { authService.withdraw(userId); @@ -83,7 +82,7 @@ public ResponseEntity withdraw(@AuthenticationPrincipal Long us @PostMapping("/sync-user") public ResponseEntity syncUser( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestBody FcmTokenSyncRequest request ) { authService.syncUser(userId, request); diff --git a/src/main/java/org/terning/terningserver/auth/api/AuthSwagger.java b/src/main/java/org/terning/terningserver/auth/api/AuthSwagger.java index 7151626..f05d50d 100644 --- a/src/main/java/org/terning/terningserver/auth/api/AuthSwagger.java +++ b/src/main/java/org/terning/terningserver/auth/api/AuthSwagger.java @@ -4,16 +4,16 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.auth.dto.request.FcmTokenSyncRequest; import org.terning.terningserver.auth.dto.request.SignInRequest; -import org.terning.terningserver.auth.dto.request.SignUpFilterRequestDto; -import org.terning.terningserver.auth.dto.request.SignUpRequestDto; -import org.terning.terningserver.auth.dto.response.AccessTokenGetResponseDto; +import org.terning.terningserver.auth.dto.request.SignUpFilterRequest; +import org.terning.terningserver.auth.dto.request.SignUpRequest; import org.terning.terningserver.auth.dto.response.SignInResponse; -import org.terning.terningserver.auth.dto.response.SignUpResponseDto; +import org.terning.terningserver.auth.dto.response.SignUpResponse; +import org.terning.terningserver.auth.dto.response.TokenReissueResponse; import org.terning.terningserver.common.exception.dto.SuccessResponse; @Tag(name = "Auth", description = "소셜 로그인 및 회원가입 API") @@ -27,7 +27,7 @@ ResponseEntity> signIn( ); @Operation(summary = "토큰 재발급", description = "토큰 재발급 API") - ResponseEntity> reissueToken( + ResponseEntity> reissueToken( @Parameter(name = "Authorization", description = "", example = "refreshToken") @RequestHeader("Authorization") String refreshToken ); @@ -36,27 +36,27 @@ ResponseEntity> reissueToken( ResponseEntity registerUserFilter( @Parameter(name = "User-Id", description = "", example = "userId") @RequestHeader("User-Id") Long userId, - @RequestBody SignUpFilterRequestDto request + @RequestBody SignUpFilterRequest request ); @Operation(summary = "회원가입", description = "회원가입 API") - ResponseEntity> signUp( + ResponseEntity> signUp( @Parameter(name = "Authorization", description = "", example = "authId") @RequestHeader("authId") String authId, - @RequestBody SignUpRequestDto request + @RequestBody SignUpRequest request ); @Operation(summary = "로그아웃", description = "로그아웃 API") ResponseEntity signOut( - @AuthenticationPrincipal Long userId); + @Parameter(hidden = true) @Login Long userId); @Operation(summary = "계정탈퇴", description = "계정탈퇴 API") ResponseEntity withdraw( - @AuthenticationPrincipal Long userId); + @Parameter(hidden = true) @Login Long userId); @Operation(summary = "유저동기화", description = "유저동기화 API") ResponseEntity syncUser( - @AuthenticationPrincipal Long userId, + @Parameter(hidden = true) @Login Long userId, @RequestBody FcmTokenSyncRequest request ); } From 321eab7eedf9807b21b69452f2ff5bc8b6c8b3fc 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:40:04 +0900 Subject: [PATCH 12/23] =?UTF-8?q?refactor=20:=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20authId=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/AuthService.java | 2 +- .../terningserver/user/domain/User.java | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 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 9cab2e6..0c57f85 100644 --- a/src/main/java/org/terning/terningserver/auth/application/AuthService.java +++ b/src/main/java/org/terning/terningserver/auth/application/AuthService.java @@ -65,7 +65,7 @@ public SignUpResponse signUp(String authId, SignUpRequest request) { throw new AuthException(AuthErrorCode.USER_ALREADY_EXIST); } - User newUser = User.from(request); + User newUser = User.from(authId, request); Token token = jwtProvider.generateTokens(newUser.getId()); 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 9aa9169..134cf4e 100644 --- a/src/main/java/org/terning/terningserver/user/domain/User.java +++ b/src/main/java/org/terning/terningserver/user/domain/User.java @@ -3,9 +3,10 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; @@ -29,23 +30,21 @@ import java.util.List; import static jakarta.persistence.EnumType.STRING; -import static jakarta.persistence.FetchType.LAZY; -import static jakarta.persistence.GenerationType.IDENTITY; import static org.terning.terningserver.common.exception.enums.ErrorMessage.FAILED_REFRESH_TOKEN_RESET; @Entity +@Table(name = "Users") @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor @Builder -@Table(name = "Users") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) public class User extends BaseTimeEntity { @Id - @GeneratedValue(strategy = IDENTITY) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @OneToOne(fetch = LAZY) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name="filter_id") private Filter filter; @@ -55,6 +54,12 @@ public class User extends BaseTimeEntity { @Column(length = 12) private String name; + @Column(length = 256) + private String authId; + + @Column(length = 256) + private String refreshToken; + @Enumerated(STRING) private ProfileImage profileImage; @@ -62,20 +67,15 @@ public class User extends BaseTimeEntity { private AuthType authType; @Setter - @Enumerated(EnumType.STRING) + @Enumerated(STRING) private PushNotificationStatus pushStatus; - @Column(length = 256) - private String authId; - - @Column(length = 256) - private String refreshToken; - @Enumerated(STRING) private State state; - public static User from(SignUpRequest request) { + public static User from(String authId, SignUpRequest request) { return User.builder() + .authId(authId) .name(request.name()) .authType(request.authType()) .profileImage(ProfileImage.fromValue(request.profileImage())) From ec99d4799ef28c387373d8a2510c6ac998157e28 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:56:55 +0900 Subject: [PATCH 13/23] =?UTF-8?q?fix(auth):=20AuthService=EC=9D=98=20signU?= =?UTF-8?q?p=20=EB=A1=9C=EC=A7=81=20=EC=88=9C=EC=84=9C=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/AuthService.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 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 0c57f85..8f621be 100644 --- a/src/main/java/org/terning/terningserver/auth/application/AuthService.java +++ b/src/main/java/org/terning/terningserver/auth/application/AuthService.java @@ -65,24 +65,22 @@ public SignUpResponse signUp(String authId, SignUpRequest request) { throw new AuthException(AuthErrorCode.USER_ALREADY_EXIST); } - User newUser = User.from(authId, request); + User userToSave = User.from(authId, request); + userRepository.save(userToSave); - Token token = jwtProvider.generateTokens(newUser.getId()); + Token token = jwtProvider.generateTokens(userToSave.getId()); + userToSave.updateRefreshToken(token.refreshToken()); - newUser.updateRefreshToken(token.refreshToken()); - - userRepository.save(newUser); - - eventPublisher.publishEvent(UserSignedUpEvent.of(newUser)); + eventPublisher.publishEvent(UserSignedUpEvent.of(userToSave, request.fcmToken())); notificationUserClient.createUserOnNotificationServer( - newUser.getId(), - newUser.getName(), - newUser.getAuthType(), + userToSave.getId(), + userToSave.getName(), + userToSave.getAuthType(), request.fcmToken() ); - return SignUpResponse.of(token, newUser); + return SignUpResponse.of(token, userToSave); } @Transactional From 8f74931888c383601de1ce58ebc7b2f9c490d8bb 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:57:03 +0900 Subject: [PATCH 14/23] =?UTF-8?q?feat(user):=20UserSignedUpEvent=EB=A5=BC?= =?UTF-8?q?=20record=EB=A1=9C=20=EC=A0=84=ED=99=98=ED=95=98=EA=B3=A0=20fcm?= =?UTF-8?q?Token=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../terningserver/user/event/UserSignedUpEvent.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/terning/terningserver/user/event/UserSignedUpEvent.java b/src/main/java/org/terning/terningserver/user/event/UserSignedUpEvent.java index f9ed84d..bd8d222 100644 --- a/src/main/java/org/terning/terningserver/user/event/UserSignedUpEvent.java +++ b/src/main/java/org/terning/terningserver/user/event/UserSignedUpEvent.java @@ -1,17 +1,10 @@ package org.terning.terningserver.user.event; -import lombok.Getter; import org.terning.terningserver.user.domain.User; -@Getter -public class UserSignedUpEvent { - private final User user; +public record UserSignedUpEvent(User user, String fcmToken) { - private UserSignedUpEvent(User user) { - this.user = user; - } - - public static UserSignedUpEvent of(User user) { - return new UserSignedUpEvent(user); + public static UserSignedUpEvent of(User user, String fcmToken) { + return new UserSignedUpEvent(user, fcmToken); } } From 2651e5fa92f686c8e061143822b0d87a44f20e2e 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:57:08 +0900 Subject: [PATCH 15/23] =?UTF-8?q?refactor(webhook):=20UserSignedUpEvent=20?= =?UTF-8?q?=EB=A0=88=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../external/discord/application/WebhookService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/terning/terningserver/external/discord/application/WebhookService.java b/src/main/java/org/terning/terningserver/external/discord/application/WebhookService.java index 4b008b1..09766e8 100644 --- a/src/main/java/org/terning/terningserver/external/discord/application/WebhookService.java +++ b/src/main/java/org/terning/terningserver/external/discord/application/WebhookService.java @@ -29,7 +29,7 @@ public class WebhookService { @EventListener public void handleUserSignedUpEvent(UserSignedUpEvent event) { - sendDiscordNotification(event.getUser()); + sendDiscordNotification(event.user()); } From 8695ead3620657e909a8eb3decdde33f0437c287 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: Mon, 14 Jul 2025 22:27:44 +0900 Subject: [PATCH 16/23] =?UTF-8?q?chore=20:=20=EC=8A=A4=ED=94=84=EB=A7=81?= =?UTF-8?q?=20=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ec99c8e..a4f18f2 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-aop' - implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-actuator' // Lombok @@ -101,4 +100,4 @@ configurations { tasks.named('test') { useJUnitPlatform() -} \ No newline at end of file +} From 222028394f7c259962950e2e4519a1c44d3c8b24 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: Mon, 14 Jul 2025 22:28:16 +0900 Subject: [PATCH 17/23] =?UTF-8?q?del=20:=20=EC=8A=A4=ED=94=84=EB=A7=81=20?= =?UTF-8?q?=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=EC=97=90=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=9C=20jwt=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/SecurityConfig.java | 55 ------------------- .../jwt/application/JwtClaimsGenerator.java | 30 ---------- .../jwt/application/JwtTokenIssuer.java | 20 ------- .../jwt/application/JwtTokenManager.java | 39 ------------- .../jwt/application/JwtUserIdExtractor.java | 28 ---------- .../jwt/auth/AuthenticationTokenFactory.java | 10 ---- .../security/jwt/auth/CustomUserDetails.java | 54 ------------------ .../security/jwt/auth/JwtClaimsParser.java | 28 ---------- .../security/jwt/auth/TokenExtractor.java | 13 ----- .../security/jwt/auth/UserAuthentication.java | 24 -------- .../security/jwt/auth/UserDetailsFactory.java | 9 --- .../security/jwt/auth/UserIdConverter.java | 25 --------- .../CustomJwtAuthenticationEntryPoint.java | 26 --------- .../jwt/filter/JwtAuthenticationFilter.java | 42 -------------- .../security/jwt/filter/JwtTokenVerifier.java | 22 -------- .../security/jwt/provider/JwtKeyProvider.java | 15 ----- .../security/jwt/provider/JwtSigner.java | 24 -------- 17 files changed, 464 deletions(-) delete mode 100644 src/main/java/org/terning/terningserver/common/config/SecurityConfig.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenIssuer.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenManager.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/AuthenticationTokenFactory.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/CustomUserDetails.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/TokenExtractor.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/UserAuthentication.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/UserDetailsFactory.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtAuthenticationFilter.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtTokenVerifier.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtKeyProvider.java delete mode 100644 src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtSigner.java diff --git a/src/main/java/org/terning/terningserver/common/config/SecurityConfig.java b/src/main/java/org/terning/terningserver/common/config/SecurityConfig.java deleted file mode 100644 index f984b4f..0000000 --- a/src/main/java/org/terning/terningserver/common/config/SecurityConfig.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.terning.terningserver.common.config; - -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.terning.terningserver.common.security.jwt.filter.CustomJwtAuthenticationEntryPoint; -import org.terning.terningserver.common.security.jwt.filter.JwtAuthenticationFilter; - -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -@EnableMethodSecurity -public class SecurityConfig { - - private final JwtAuthenticationFilter jwtAuthenticationFilter; - private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; - private static final String[] AUTH_WHITELIST = { - "/v3/api-docs/**", - "/swagger-ui.html", - "/api/v1/swagger-ui/index.html#/**", - "/swagger-resources/**", - "/swagger-ui/**", - "/api/v1/auth/**", - "/actuator/health", - "/api/v1/users/**", - "/api/v1/push-status", - "/api/v1/external/scraps/unsynced", - "/api/v1/external/scraps/sync/result" - }; - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http - .csrf(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable) - .sessionManagement(sessionManagement -> - sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .exceptionHandling(exceptionHandling -> - exceptionHandling.authenticationEntryPoint(customJwtAuthenticationEntryPoint)) - .authorizeHttpRequests(auth -> { - auth.requestMatchers(AUTH_WHITELIST).permitAll(); - auth.anyRequest().authenticated(); - }) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .build(); - } -} 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 deleted file mode 100644 index 521337f..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtClaimsGenerator.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.terning.terningserver.common.security.jwt.application; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Service; -import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; - -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class JwtClaimsGenerator { - - private static final String USER_ID_CLAIM = "userId"; - - public Claims generateClaims(Authentication authentication) { - return Jwts.claims(createClaimsMap(authentication)); - } - - private Map createClaimsMap(Authentication authentication) { - if (authentication.getPrincipal() instanceof Long userId) { - return Map.of(USER_ID_CLAIM, userId); - } - - throw new JwtException(JwtErrorCode.INVALID_USER_DETAILS_TYPE.getMessage()); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenIssuer.java b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenIssuer.java deleted file mode 100644 index 7402ce8..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenIssuer.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.terning.terningserver.common.security.jwt.application; - -import io.jsonwebtoken.Claims; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; -import org.terning.terningserver.common.security.jwt.provider.JwtSigner; - -@Component -@RequiredArgsConstructor -public class JwtTokenIssuer { - - private final JwtClaimsGenerator jwtClaimsGenerator; - private final JwtSigner jwtSigner; - - public String generateToken(Authentication authentication, long expiration) { - Claims claims = jwtClaimsGenerator.generateClaims(authentication); - return jwtSigner.sign(claims, expiration); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenManager.java b/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenManager.java deleted file mode 100644 index fe76591..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtTokenManager.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.terning.terningserver.common.security.jwt.application; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.terning.terningserver.common.security.jwt.auth.UserAuthentication; -import org.terning.terningserver.common.security.jwt.auth.AuthenticationTokenFactory; -import org.terning.terningserver.common.config.ValueConfig; -import org.terning.terningserver.user.domain.Token; -import org.terning.terningserver.user.domain.User; - -@Service -@RequiredArgsConstructor -public class JwtTokenManager { - - private final JwtTokenIssuer jwtTokenIssuer; - private final ValueConfig valueConfig; - - public Token generateToken(User user) { - UserAuthentication authentication = AuthenticationTokenFactory.create(user); - - long accessTokenExpiration = valueConfig.getAccessTokenExpired(); - long refreshTokenExpiration = valueConfig.getRefreshTokenExpired(); - - return Token.builder() - .accessToken(jwtTokenIssuer.generateToken(authentication, accessTokenExpiration)) - .refreshToken(jwtTokenIssuer.generateToken(authentication, refreshTokenExpiration)) - .build(); - } - - public Token issueAccessToken(User user) { - UserAuthentication authentication = AuthenticationTokenFactory.create(user); - - long accessTokenExpiration = valueConfig.getAccessTokenExpired(); - - return Token.builder() - .accessToken(jwtTokenIssuer.generateToken(authentication, accessTokenExpiration)) - .build(); - } -} 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 deleted file mode 100644 index 3ac794b..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/application/JwtUserIdExtractor.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.terning.terningserver.common.security.jwt.application; - -import io.jsonwebtoken.Claims; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -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 -@RequiredArgsConstructor -public class JwtUserIdExtractor { - private static final String CLAIM_USER_ID = "userId"; - private final JwtClaimsParser jwtClaimsParser; - - public Long extractUserId(String token) { - Claims claims = jwtClaimsParser.parse(token); - Object userIdClaim = claims.get(CLAIM_USER_ID); - - if (userIdClaim instanceof Number) { - return ((Number) userIdClaim).longValue(); - } else if (userIdClaim instanceof String) { - return Long.parseLong((String) userIdClaim); - } - - throw new JwtException(JwtErrorCode.INVALID_USER_ID_TYPE); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/AuthenticationTokenFactory.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/AuthenticationTokenFactory.java deleted file mode 100644 index 78aacf5..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/AuthenticationTokenFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import org.terning.terningserver.user.domain.User; - -public class AuthenticationTokenFactory { - public static UserAuthentication create(User user) { - Long userId = user.getId(); - return new UserAuthentication(userId); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/CustomUserDetails.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/CustomUserDetails.java deleted file mode 100644 index 3e99da9..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/CustomUserDetails.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.util.Collection; -import java.util.Collections; - -public class CustomUserDetails implements UserDetails { - private final Long userId; - - public CustomUserDetails(Long userId) { - this.userId = userId; - } - - public Long getUserId() { - return userId; - } - - @Override - public String getUsername() { - return String.valueOf(userId); - } - - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - @Override - public String getPassword() { - return null; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} 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 deleted file mode 100644 index 5ffde25..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/JwtClaimsParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -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.security.jwt.provider.JwtKeyProvider; - -@Service -@RequiredArgsConstructor -public class JwtClaimsParser { - private final ValueConfig valueConfig; - - public Claims parse(String token) { - try { - return Jwts.parserBuilder() - .setSigningKey(JwtKeyProvider.getSigningKey(valueConfig)) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (Exception e) { - throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); - } - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/TokenExtractor.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/TokenExtractor.java deleted file mode 100644 index a405abb..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/TokenExtractor.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -public class TokenExtractor { - public static final String TOKEN_PREFIX = "Bearer "; - - private TokenExtractor() { - throw new IllegalStateException("Utility class"); - } - - public static String extractToken(String token) { - return token.replaceFirst(TOKEN_PREFIX, "").trim(); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserAuthentication.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserAuthentication.java deleted file mode 100644 index 4f8f95f..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserAuthentication.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import org.springframework.security.authentication.AbstractAuthenticationToken; - -public class UserAuthentication extends AbstractAuthenticationToken { - - private final Long userId; - - public UserAuthentication(Long userId) { - super(null); - this.userId = userId; - setAuthenticated(true); - } - - @Override - public Object getPrincipal() { - return userId; - } - - @Override - public Object getCredentials() { - return null; - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserDetailsFactory.java b/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserDetailsFactory.java deleted file mode 100644 index bcad199..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserDetailsFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import org.springframework.security.core.userdetails.UserDetails; - -public class UserDetailsFactory { - public static UserDetails create(Long userId) { - return new CustomUserDetails(userId); - } -} 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 deleted file mode 100644 index 34058dd..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/auth/UserIdConverter.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.terning.terningserver.common.security.jwt.auth; - -import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; -import org.terning.terningserver.auth.jwt.exception.JwtException; - -public class UserIdConverter { - - public static Long convertToLong(Object value) { - if (value instanceof Long) { - return (Long) value; - } - if (value instanceof String) { - return parseLongSafely((String) value); - } - throw new JwtException(JwtErrorCode.INVALID_USER_ID); - } - - private static Long parseLongSafely(String value) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new JwtException(JwtErrorCode.INVALID_USER_ID); - } - } -} 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 deleted file mode 100644 index 8b87ecc..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/filter/CustomJwtAuthenticationEntryPoint.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.terning.terningserver.common.security.jwt.filter; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; -import org.terning.terningserver.auth.jwt.exception.JwtErrorCode; -import org.terning.terningserver.auth.jwt.exception.JwtException; - -import java.io.IOException; - -@Component -@RequiredArgsConstructor -public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint { - - @Override - public void commence( - HttpServletRequest request, - HttpServletResponse response, - AuthenticationException exception - ) throws IOException { - throw new JwtException(JwtErrorCode.INVALID_JWT_TOKEN); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtAuthenticationFilter.java deleted file mode 100644 index bc7049c..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtAuthenticationFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.terning.terningserver.common.security.jwt.filter; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.terning.terningserver.common.security.jwt.auth.UserAuthentication; - -import java.io.IOException; -import java.util.Optional; - -import static org.springframework.http.HttpHeaders.AUTHORIZATION; - -@Component -@RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtTokenVerifier jwtTokenVerifier; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - extractToken(request) - .flatMap(jwtTokenVerifier::validateAndExtractUserId) - .ifPresent(this::authenticateUser); - - filterChain.doFilter(request, response); - } - - private Optional extractToken(HttpServletRequest request) { - return Optional.ofNullable(request.getHeader(AUTHORIZATION)) - .map(token -> token.replaceFirst("Bearer ", "").trim()); - } - - private void authenticateUser(Long userId) { - UserAuthentication authentication = new UserAuthentication(userId); - SecurityContextHolder.getContext().setAuthentication(authentication); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtTokenVerifier.java b/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtTokenVerifier.java deleted file mode 100644 index 537f590..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/filter/JwtTokenVerifier.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.terning.terningserver.common.security.jwt.filter; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.terning.terningserver.common.security.jwt.application.JwtUserIdExtractor; - -import java.util.Optional; - -@Service -@RequiredArgsConstructor -public class JwtTokenVerifier { - - private final JwtUserIdExtractor jwtUserIdExtractor; - - public Optional validateAndExtractUserId(String token) { - try { - return Optional.of(jwtUserIdExtractor.extractUserId(token)); - } catch (Exception e) { - return Optional.empty(); - } - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtKeyProvider.java b/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtKeyProvider.java deleted file mode 100644 index f547eca..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtKeyProvider.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.terning.terningserver.common.security.jwt.provider; - -import io.jsonwebtoken.security.Keys; -import org.springframework.stereotype.Component; -import org.terning.terningserver.common.config.ValueConfig; - -import javax.crypto.SecretKey; - -@Component -public class JwtKeyProvider { - - public static SecretKey getSigningKey(ValueConfig valueConfig) { - return Keys.hmacShaKeyFor(valueConfig.getSecretKey().getBytes()); - } -} diff --git a/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtSigner.java b/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtSigner.java deleted file mode 100644 index 36b342f..0000000 --- a/src/main/java/org/terning/terningserver/common/security/jwt/provider/JwtSigner.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.terning.terningserver.common.security.jwt.provider; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.terning.terningserver.common.config.ValueConfig; - -import java.util.Date; - -@Component -@RequiredArgsConstructor -public class JwtSigner { - private final ValueConfig valueConfig; - - public String sign(Claims claims, long expiration) { - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + expiration)) - .signWith(JwtKeyProvider.getSigningKey(valueConfig)) - .compact(); - } -} From d97c7cde4dde4ee1725b6a11f238290aec538bdd 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: Mon, 14 Jul 2025 22:30:04 +0900 Subject: [PATCH 18/23] =?UTF-8?q?refactor=20:=20=EC=8A=A4=ED=94=84?= =?UTF-8?q?=EB=A7=81=20=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A0=81=EC=9A=95?= =?UTF-8?q?=EC=9D=84=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=EC=9C=BC=EB=A1=9C=20=EC=A0=84?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../banner/api/BannerSwagger.java | 3 - .../calendar/api/CalendarController.java | 26 +- .../calendar/api/CalendarSwagger.java | 8 +- .../common/config/WebConfig.java | 64 +-- .../notification/NotificationUserClient.java | 42 -- .../filter/api/FilterController.java | 23 +- .../filter/api/FilterSwagger.java | 8 +- .../home/api/HomeController.java | 25 +- .../api/InternshipDetailController.java | 4 +- .../api/InternshipDetailSwagger.java | 4 +- .../scrap/api/ScrapController.java | 28 +- .../terningserver/scrap/api/ScrapSwagger.java | 8 +- .../search/api/SearchController.java | 4 +- .../search/api/SearchSwagger.java | 6 +- .../user/api/UserProfileController.java | 22 +- .../terningserver/user/api/UserSwagger.java | 8 +- .../service/ScrapServiceTest.java | 428 +++++++++--------- 17 files changed, 345 insertions(+), 366 deletions(-) diff --git a/src/main/java/org/terning/terningserver/banner/api/BannerSwagger.java b/src/main/java/org/terning/terningserver/banner/api/BannerSwagger.java index 5d5a1f9..1f7850c 100644 --- a/src/main/java/org/terning/terningserver/banner/api/BannerSwagger.java +++ b/src/main/java/org/terning/terningserver/banner/api/BannerSwagger.java @@ -1,6 +1,5 @@ package org.terning.terningserver.banner.api; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -11,8 +10,6 @@ @Tag(name= "Banner", description = "탐색 > 배너 조회 관련 API") public interface BannerSwagger { - @Operation(summary = "배너 조회", description = "탐색 > 배너를 조회하는 API") ResponseEntity> getBanners(); - } diff --git a/src/main/java/org/terning/terningserver/calendar/api/CalendarController.java b/src/main/java/org/terning/terningserver/calendar/api/CalendarController.java index db6a04f..3f9d059 100644 --- a/src/main/java/org/terning/terningserver/calendar/api/CalendarController.java +++ b/src/main/java/org/terning/terningserver/calendar/api/CalendarController.java @@ -1,22 +1,26 @@ package org.terning.terningserver.calendar.api; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_DAILY_SCRAPS; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_MONTHLY_SCRAPS; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_MONTHLY_SCRAPS_AS_LIST; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.calendar.dto.response.DailyScrapResponseDto; import org.terning.terningserver.calendar.dto.response.MonthlyDefaultResponseDto; import org.terning.terningserver.calendar.dto.response.MonthlyListResponseDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.scrap.application.ScrapService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.time.LocalDate; import java.util.List; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_DAILY_SCRAPS; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_MONTHLY_SCRAPS; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_MONTHLY_SCRAPS_AS_LIST; + + @RestController @RequiredArgsConstructor @RequestMapping("/api/v1") @@ -26,7 +30,7 @@ public class CalendarController implements CalendarSwagger { @GetMapping("/calendar/monthly-default") public ResponseEntity>> getMonthlyScraps( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestParam("year") int year, @RequestParam("month") int month ){ @@ -36,7 +40,7 @@ public ResponseEntity>> getMonth @GetMapping("/calendar/monthly-list") public ResponseEntity>> getMonthlyScrapsAsList( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestParam("year") int year, @RequestParam("month") int month ){ @@ -46,7 +50,7 @@ public ResponseEntity>> getMonthlyS @GetMapping("/calendar/daily") public ResponseEntity>> getDailyScraps( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestParam("date") String date ){ LocalDate localDate = LocalDate.parse(date); diff --git a/src/main/java/org/terning/terningserver/calendar/api/CalendarSwagger.java b/src/main/java/org/terning/terningserver/calendar/api/CalendarSwagger.java index 16a3b45..51b7c69 100644 --- a/src/main/java/org/terning/terningserver/calendar/api/CalendarSwagger.java +++ b/src/main/java/org/terning/terningserver/calendar/api/CalendarSwagger.java @@ -1,8 +1,10 @@ package org.terning.terningserver.calendar.api; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.calendar.dto.response.DailyScrapResponseDto; import org.terning.terningserver.calendar.dto.response.MonthlyDefaultResponseDto; import org.terning.terningserver.calendar.dto.response.MonthlyListResponseDto; @@ -15,21 +17,21 @@ public interface CalendarSwagger { @Operation(summary = "캘린더 > 월간 스크랩 공고 조회", description = "월간 스크랩 공고를 조회하는 API") ResponseEntity>> getMonthlyScraps( - Long userId, + @Parameter(hidden = true) @Login Long userId, int year, int month ); @Operation(summary = "캘린더 > 월간 스크랩 공고 조회 (리스트)", description = "월간 스크랩 공고를 리스트로 조회하는 API") ResponseEntity>> getMonthlyScrapsAsList( - Long userId, + @Parameter(hidden = true) @Login Long userId, int year, int month ); @Operation(summary = "캘린더 > 일간 스크랩 공고 조회 (리스트)", description = "일간 스크랩 공고를 리스트로 조회하는 API") ResponseEntity>> getDailyScraps( - Long userId, + @Parameter(hidden = true) @Login Long userId, String date ); } diff --git a/src/main/java/org/terning/terningserver/common/config/WebConfig.java b/src/main/java/org/terning/terningserver/common/config/WebConfig.java index 21d4bf7..5145aab 100644 --- a/src/main/java/org/terning/terningserver/common/config/WebConfig.java +++ b/src/main/java/org/terning/terningserver/common/config/WebConfig.java @@ -18,25 +18,27 @@ public class WebConfig implements WebMvcConfigurer { private final LoginCheckInterceptor loginCheckInterceptor; private final LoginUserArgumentResolver loginUserArgumentResolver; -// private static final String[] AUTH_WHITELIST = { -// "/v3/api-docs/**", -// "/swagger-ui.html", -// "/swagger-resources/**", -// "/swagger-ui/**", -// -// "/api/v1/auth/sign-in", -// "/api/v1/auth/sign-up", -// "/api/v1/auth/sign-up/filter", -// "/api/v1/auth/token-reissue", -// -// "/api/v1/search/banners", -// "/api/v1/search/views", -// "/api/v1/search/scraps", -// -// "/actuator/health", -// "/api/v1/external/scraps/unsynced", -// "/api/v1/external/scraps/sync/result" -// }; + private static final String[] AUTH_WHITELIST = { + "/v3/api-docs/**", + "/swagger-ui.html", + "/swagger-resources/**", + "/swagger-ui/**", + + "/api/v1/auth/sign-in", + "/api/v1/auth/sign-up", + "/api/v1/auth/sign-up/filter", + "/api/v1/auth/token-reissue", + + "/api/v1/search/banners", + "/api/v1/search/views", + "/api/v1/search/scraps", + + "/actuator/health", + "/api/v1/external/scraps/unsynced", + "/api/v1/external/scraps/sync/result", + + "/api/v1/users" + }; @Override public void addCorsMappings(CorsRegistry registry) { @@ -52,16 +54,16 @@ public void addCorsMappings(CorsRegistry registry) { .allowCredentials(true); } -// @Override -// public void addInterceptors(InterceptorRegistry registry) { -// registry.addInterceptor(loginCheckInterceptor) -// .order(1) -// .addPathPatterns("/api/v1/**") -// .excludePathPatterns(AUTH_WHITELIST); -// } -// -// @Override -// public void addArgumentResolvers(List resolvers) { -// resolvers.add(loginUserArgumentResolver); -// } + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(loginCheckInterceptor) + .order(1) + .addPathPatterns("/api/v1/**") + .excludePathPatterns(AUTH_WHITELIST); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginUserArgumentResolver); + } } diff --git a/src/main/java/org/terning/terningserver/external/pushNotification/notification/NotificationUserClient.java b/src/main/java/org/terning/terningserver/external/pushNotification/notification/NotificationUserClient.java index 39e0b75..fe26c1e 100644 --- a/src/main/java/org/terning/terningserver/external/pushNotification/notification/NotificationUserClient.java +++ b/src/main/java/org/terning/terningserver/external/pushNotification/notification/NotificationUserClient.java @@ -18,16 +18,6 @@ public class NotificationUserClient { private final WebClient notificationWebClient; - /** - * 알림서버에 신규 사용자 정보를 전달하여 사용자 레코드를 생성합니다. - * 운영서버에서는 FCM 토큰을 DB에 저장하지 않고, - * 기본값으로 pushStatus="enabled"와 accountStatus="active"를 전달합니다. - * - * @param userId 신규 사용자 ID - * @param name 사용자 이름 - * @param authType 인증 타입 (문자열, 예: "kakao", "apple") - * @param fcmToken 클라이언트로부터 받은 FCM 토큰 - */ public void createUserOnNotificationServer( Long userId, String name, @@ -53,11 +43,6 @@ public void createUserOnNotificationServer( log.info("User (id={}) created on notification server : ", userId); } - /** - * 소셜로그인 시 알림서버와 운영서버간 유저 동기화를 진행합니다. - * @param user 유저 객체 - * @param fcmToken fcm 토큰 - */ public void createOrUpdateUser(User user, String fcmToken) { try { updateFcmToken(user.getId(), fcmToken); @@ -75,12 +60,6 @@ public void createOrUpdateUser(User user, String fcmToken) { } } - /** - * 알림서버에 새로운 fcm 토큰을 전달합니다. - * - * @param userId 사용자 ID - * @param newToken 새로운 fcm 토큰 - */ public void updateFcmToken(Long userId, String newToken) { notificationWebClient.put() .uri("/api/v1/users/{userId}/fcm-tokens", userId) @@ -92,14 +71,6 @@ public void updateFcmToken(Long userId, String newToken) { log.info("FCM tokens updated for user (id={}): {}", userId, newToken); } - /** - * 알림서버에 신규 사용자 정보를 전달하여 사용자 레코드를 생성합니다. - * 운영서버에서는 pushStatus 값을 DB에 저장하지 않고, - * pushStatus="enabled" 또는 "disabled 로 변경합니다. - * - * @param userId 기존 사용자 ID - * @param newPushStatus 새로운 푸시알림 허용 여부 - */ public void updatePushStatus(Long userId, String newPushStatus) { notificationWebClient.put() .uri("/api/v1/users/{userId}/push-status", userId) @@ -111,13 +82,6 @@ public void updatePushStatus(Long userId, String newPushStatus) { log.info("Push status updated for user (id={}): {}", userId, newPushStatus); } - /** - * 프로필 정보를 변경하면, 알림서버에 새로운 사용자 이름을 전달하여 사용자 레코드를 변경합니다. - * 운영서버와 알림서버 모두 변경 값을 반영하도록 합니다. - * - * @param userId 기존 사용자 ID - * @param newName 변경된 새로운 사용자 이름 - */ public void updateUserName(Long userId, String newName) { notificationWebClient.put() .uri("/api/v1/users/{userId}/name", userId) @@ -129,12 +93,6 @@ public void updateUserName(Long userId, String newName) { log.info("User name updated for user (id={}): {}", userId, newName); } - /** - * 회원 탈퇴 시 알림서버에 존재하는 사용자를 삭제하여 사용자 레코드를 변경합니다. - * 운영서버와 알림서버 모두 해당 유저를 삭제하도록 합니다. - * - * @param userId 기존 사용자 ID - */ public void deleteUser(Long userId) { notificationWebClient.delete() .uri("/api/v1/users/{userId}", userId) diff --git a/src/main/java/org/terning/terningserver/filter/api/FilterController.java b/src/main/java/org/terning/terningserver/filter/api/FilterController.java index a982c83..256ac4c 100644 --- a/src/main/java/org/terning/terningserver/filter/api/FilterController.java +++ b/src/main/java/org/terning/terningserver/filter/api/FilterController.java @@ -1,16 +1,20 @@ package org.terning.terningserver.filter.api; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_USER_FILTER; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_UPDATE_USER_FILTER; - import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import org.terning.terningserver.filter.dto.request.UpdateUserFilterRequestDto; -import org.terning.terningserver.filter.dto.response.UserFilterDetailResponseDto; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.filter.application.FilterService; +import org.terning.terningserver.filter.dto.request.UpdateUserFilterRequestDto; +import org.terning.terningserver.filter.dto.response.UserFilterDetailResponseDto; + +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_USER_FILTER; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_UPDATE_USER_FILTER; @RestController @RequiredArgsConstructor @@ -21,7 +25,7 @@ public class FilterController implements FilterSwagger { @GetMapping("/filters") public ResponseEntity> getUserFilter( - @AuthenticationPrincipal long userId + @Login long userId ) { return ResponseEntity.ok(SuccessResponse.of( SUCCESS_GET_USER_FILTER, @@ -31,11 +35,10 @@ public ResponseEntity> getUserFilte @PutMapping("/filters") public ResponseEntity updateUserFilter( - @AuthenticationPrincipal long userId, + @Login long userId, @RequestBody UpdateUserFilterRequestDto requestDto ) { filterService.updateUserFilter(requestDto, userId); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_UPDATE_USER_FILTER)); } - } diff --git a/src/main/java/org/terning/terningserver/filter/api/FilterSwagger.java b/src/main/java/org/terning/terningserver/filter/api/FilterSwagger.java index ae1fb4e..2b6fdfa 100644 --- a/src/main/java/org/terning/terningserver/filter/api/FilterSwagger.java +++ b/src/main/java/org/terning/terningserver/filter/api/FilterSwagger.java @@ -1,11 +1,11 @@ package org.terning.terningserver.filter.api; - import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.RequestBody; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.filter.dto.request.UpdateUserFilterRequestDto; import org.terning.terningserver.filter.dto.response.UserFilterDetailResponseDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; @@ -15,12 +15,12 @@ public interface FilterSwagger { @Operation(summary = "사용자 필터링 정보 조회 API", description = "사용자가 설정한 필터링 정보를 조회하는 API") ResponseEntity> getUserFilter( - @AuthenticationPrincipal long userId + @Parameter(hidden = true) @Login long userId ); @Operation(summary = "사용자 필터링 정보 수정 API", description = "사용자 필터링을 수정하는 API") ResponseEntity updateUserFilter( - @AuthenticationPrincipal long userId, + @Parameter(hidden = true) @Login long userId, @RequestBody UpdateUserFilterRequestDto requestDto ); } diff --git a/src/main/java/org/terning/terningserver/home/api/HomeController.java b/src/main/java/org/terning/terningserver/home/api/HomeController.java index 3e8f0df..3dfef15 100644 --- a/src/main/java/org/terning/terningserver/home/api/HomeController.java +++ b/src/main/java/org/terning/terningserver/home/api/HomeController.java @@ -1,24 +1,27 @@ package org.terning.terningserver.home.api; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_ANNOUNCEMENTS; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS_EMPTY_LIST; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS_NO_SCRAP; - import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import org.terning.terningserver.home.dto.response.HomeAnnouncementsResponseDto; -import org.terning.terningserver.home.dto.response.UpcomingScrapResponseDto; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.home.application.HomeService; +import org.terning.terningserver.home.dto.response.HomeAnnouncementsResponseDto; +import org.terning.terningserver.home.dto.response.UpcomingScrapResponseDto; import org.terning.terningserver.scrap.application.ScrapService; import java.util.List; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_ANNOUNCEMENTS; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS_EMPTY_LIST; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_UPCOMING_ANNOUNCEMENTS_NO_SCRAP; + @RestController @RequiredArgsConstructor @RequestMapping("/api/v1") @@ -29,7 +32,7 @@ public class HomeController implements HomeSwagger { @GetMapping("/home") public ResponseEntity> getAnnouncements( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestParam(value = "sortBy", required = false, defaultValue = "deadlineSoon") String sortBy, @PageableDefault(size = 10) Pageable pageable) { HomeAnnouncementsResponseDto announcements = homeService.getAnnouncements(userId, sortBy, pageable); @@ -39,7 +42,7 @@ public ResponseEntity> getAnnounce @GetMapping("/home/upcoming") public ResponseEntity>> getUpcomingScraps( - @AuthenticationPrincipal Long userId + @Login Long userId ){ boolean hasScrapped = scrapService.hasUserScrapped(userId); diff --git a/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailController.java b/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailController.java index 9d8cc8a..ee73222 100644 --- a/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailController.java +++ b/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailController.java @@ -3,11 +3,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.internshipAnnouncement.dto.response.InternshipDetailResponseDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.internshipAnnouncement.application.InternshipDetailService; @@ -23,7 +23,7 @@ public class InternshipDetailController implements InternshipDetailSwagger { @GetMapping("/announcements/{internshipAnnouncementId}") public ResponseEntity> getInternshipDetail( - @AuthenticationPrincipal Long userId, + @Login Long userId, @PathVariable Long internshipAnnouncementId) { return ResponseEntity.ok(SuccessResponse.of( SUCCESS_GET_INTERNSHIP_DETAIL, diff --git a/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailSwagger.java b/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailSwagger.java index 99587c3..0eb7059 100644 --- a/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailSwagger.java +++ b/src/main/java/org/terning/terningserver/internshipAnnouncement/api/InternshipDetailSwagger.java @@ -4,8 +4,8 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.internshipAnnouncement.dto.response.InternshipDetailResponseDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; @@ -14,7 +14,7 @@ public interface InternshipDetailSwagger { @Operation(summary = "공고 상세 페이지", description = "인턴 공고의 상세 정보를 불러오는 API") ResponseEntity> getInternshipDetail( - @AuthenticationPrincipal Long userId, + @Login Long userId, @PathVariable("internshipAnnouncementId") Long internshipAnnouncementId ); } diff --git a/src/main/java/org/terning/terningserver/scrap/api/ScrapController.java b/src/main/java/org/terning/terningserver/scrap/api/ScrapController.java index 3239ccf..babeff0 100644 --- a/src/main/java/org/terning/terningserver/scrap/api/ScrapController.java +++ b/src/main/java/org/terning/terningserver/scrap/api/ScrapController.java @@ -1,17 +1,23 @@ package org.terning.terningserver.scrap.api; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_CREATE_SCRAP; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_DELETE_SCRAP; -import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_UPDATE_SCRAP; - import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import org.terning.terningserver.scrap.dto.request.CreateScrapRequestDto; -import org.terning.terningserver.scrap.dto.request.UpdateScrapRequestDto; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.scrap.application.ScrapService; +import org.terning.terningserver.scrap.dto.request.CreateScrapRequestDto; +import org.terning.terningserver.scrap.dto.request.UpdateScrapRequestDto; + +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_CREATE_SCRAP; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_DELETE_SCRAP; +import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_UPDATE_SCRAP; @RestController @RequiredArgsConstructor @@ -22,7 +28,7 @@ public class ScrapController implements ScrapSwagger { @PostMapping("/scraps/{internshipAnnouncementId}") public ResponseEntity createScrap( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long internshipAnnouncementId, @RequestBody CreateScrapRequestDto request) { scrapService.createScrap(internshipAnnouncementId, request, userId); @@ -31,7 +37,7 @@ public ResponseEntity createScrap( @DeleteMapping("/scraps/{internshipAnnouncementId}") public ResponseEntity deleteScrap( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long internshipAnnouncementId) { scrapService.deleteScrap(internshipAnnouncementId, userId); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_DELETE_SCRAP)); @@ -39,7 +45,7 @@ public ResponseEntity deleteScrap( @PatchMapping("/scraps/{internshipAnnouncementId}") public ResponseEntity updateScrapColor( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long internshipAnnouncementId, @RequestBody UpdateScrapRequestDto request) { scrapService.updateScrapColor(internshipAnnouncementId, request, userId); diff --git a/src/main/java/org/terning/terningserver/scrap/api/ScrapSwagger.java b/src/main/java/org/terning/terningserver/scrap/api/ScrapSwagger.java index 277044d..55f392a 100644 --- a/src/main/java/org/terning/terningserver/scrap/api/ScrapSwagger.java +++ b/src/main/java/org/terning/terningserver/scrap/api/ScrapSwagger.java @@ -3,9 +3,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.scrap.dto.request.CreateScrapRequestDto; import org.terning.terningserver.scrap.dto.request.UpdateScrapRequestDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; @@ -15,20 +15,20 @@ public interface ScrapSwagger { @Operation(summary = "스크랩 추가", description = "사용자가 스크랩을 추가하는 API") ResponseEntity createScrap( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long internshipAnnouncementId, @RequestBody CreateScrapRequestDto request ); @Operation(summary = "스크랩 취소", description = "사용자가 스크랩을 취소하는 API") ResponseEntity deleteScrap( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long internshipAnnouncementId ); @Operation(summary = "스크랩 수정", description = "사용자가 스크랩 색상을 수정하는 API") public ResponseEntity updateScrapColor( - @AuthenticationPrincipal long userId, + @Login long userId, @PathVariable long scrapId, @RequestBody UpdateScrapRequestDto request ); diff --git a/src/main/java/org/terning/terningserver/search/api/SearchController.java b/src/main/java/org/terning/terningserver/search/api/SearchController.java index 8b7f9e9..79de37d 100644 --- a/src/main/java/org/terning/terningserver/search/api/SearchController.java +++ b/src/main/java/org/terning/terningserver/search/api/SearchController.java @@ -8,11 +8,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.search.dto.response.PopularAnnouncementListResponseDto; import org.terning.terningserver.search.dto.response.SearchResultResponseDto; import org.terning.terningserver.common.exception.dto.SuccessResponse; @@ -44,7 +44,7 @@ public ResponseEntity> getMo @GetMapping("/search") public ResponseEntity> searchInternshipAnnouncement( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "sortBy", required = false) String sortBy, Pageable pageable) { return ResponseEntity.ok(SuccessResponse.of( diff --git a/src/main/java/org/terning/terningserver/search/api/SearchSwagger.java b/src/main/java/org/terning/terningserver/search/api/SearchSwagger.java index a83e850..e4b6a82 100644 --- a/src/main/java/org/terning/terningserver/search/api/SearchSwagger.java +++ b/src/main/java/org/terning/terningserver/search/api/SearchSwagger.java @@ -1,11 +1,11 @@ package org.terning.terningserver.search.api; - import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.search.dto.response.PopularAnnouncementListResponseDto; import org.springframework.web.bind.annotation.RequestParam; import org.terning.terningserver.search.dto.response.SearchResultResponseDto; @@ -26,7 +26,7 @@ ResponseEntity> getMostScrap @Operation(summary = "탐색 > 검색 결과 화면", description = "탐색 화면에서 인턴 공고를 검색하는 API") ResponseEntity> searchInternshipAnnouncement( - @AuthenticationPrincipal Long userId, + @Parameter(hidden = true) @Login Long userId, @RequestParam(value = "keyword", required = false) String keyword, @RequestParam("sortBy") String sortBy, Pageable pageable ); diff --git a/src/main/java/org/terning/terningserver/user/api/UserProfileController.java b/src/main/java/org/terning/terningserver/user/api/UserProfileController.java index f2a2363..c7e1c91 100644 --- a/src/main/java/org/terning/terningserver/user/api/UserProfileController.java +++ b/src/main/java/org/terning/terningserver/user/api/UserProfileController.java @@ -2,15 +2,19 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import org.terning.terningserver.user.dto.request.ProfileUpdateRequestDto; -import org.terning.terningserver.user.dto.request.PushStatusUpdateRequest; -import org.terning.terningserver.user.dto.response.ProfileResponseDto; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.common.exception.dto.SuccessResponse; import org.terning.terningserver.common.exception.enums.SuccessMessage; import org.terning.terningserver.external.pushNotification.notification.NotificationUserClient; import org.terning.terningserver.user.application.UserService; +import org.terning.terningserver.user.dto.request.ProfileUpdateRequestDto; +import org.terning.terningserver.user.dto.request.PushStatusUpdateRequest; +import org.terning.terningserver.user.dto.response.ProfileResponseDto; import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_GET_PROFILE; import static org.terning.terningserver.common.exception.enums.SuccessMessage.SUCCESS_UPDATE_PROFILE; @@ -25,7 +29,7 @@ public class UserProfileController implements UserSwagger { @GetMapping("/mypage/profile") public ResponseEntity> getProfile( - @AuthenticationPrincipal Long userId + @Login Long userId ){ ProfileResponseDto profile = userService.getProfile(userId); return ResponseEntity.ok(SuccessResponse.of(SUCCESS_GET_PROFILE, profile)); @@ -33,7 +37,7 @@ public ResponseEntity> getProfile( @PatchMapping("/mypage/profile") public ResponseEntity updateProfile( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestBody ProfileUpdateRequestDto request ){ userService.updateProfile(userId, request); @@ -42,13 +46,11 @@ public ResponseEntity updateProfile( @PatchMapping("/push-status") public ResponseEntity> updatePushStatus( - @AuthenticationPrincipal Long userId, + @Login Long userId, @RequestBody PushStatusUpdateRequest request ) { - // 운영서버에서 User 엔티티 업데이트 userService.updatePushStatus(userId, request.newStatus()); - // 알림서버에 변경사항 동기화 notificationUserClient.updatePushStatus(userId, request.newStatus()); return ResponseEntity.ok(SuccessResponse.of(SuccessMessage.PUSH_STATUS_UPDATED)); diff --git a/src/main/java/org/terning/terningserver/user/api/UserSwagger.java b/src/main/java/org/terning/terningserver/user/api/UserSwagger.java index b787459..6f86118 100644 --- a/src/main/java/org/terning/terningserver/user/api/UserSwagger.java +++ b/src/main/java/org/terning/terningserver/user/api/UserSwagger.java @@ -1,8 +1,10 @@ package org.terning.terningserver.user.api; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.ResponseEntity; +import org.terning.terningserver.auth.config.Login; import org.terning.terningserver.user.dto.request.ProfileUpdateRequestDto; import org.terning.terningserver.user.dto.request.PushStatusUpdateRequest; import org.terning.terningserver.user.dto.response.ProfileResponseDto; @@ -13,18 +15,18 @@ public interface UserSwagger { @Operation(summary = "마이페이지 > 프로필 정보 불러오기", description = "마이페이지에서 프로필 정보를 불러오는 API") ResponseEntity> getProfile( - Long userId + @Parameter(hidden = true) @Login Long userId ); @Operation(summary = "마이페이지 > 프로필 정보 수정하기", description = "마이페이지에서 프로필 정보를 수정하는 API") ResponseEntity updateProfile( - Long userId, + @Parameter(hidden = true) @Login Long userId, ProfileUpdateRequestDto request ); @Operation(summary = "마이페이지 > 푸시알림 상태 변경하기", description = "마이페이지에서 푸시알림 허용 여부를 수정하는 API") ResponseEntity> updatePushStatus( - Long userId, + @Parameter(hidden = true) @Login Long userId, PushStatusUpdateRequest request ); diff --git a/src/test/java/org/terning/terningserver/service/ScrapServiceTest.java b/src/test/java/org/terning/terningserver/service/ScrapServiceTest.java index f57ae64..edd630d 100644 --- a/src/test/java/org/terning/terningserver/service/ScrapServiceTest.java +++ b/src/test/java/org/terning/terningserver/service/ScrapServiceTest.java @@ -1,214 +1,214 @@ -package org.terning.terningserver.service; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import org.terning.terningserver.internshipAnnouncement.domain.Company; -import org.terning.terningserver.internshipAnnouncement.domain.InternshipAnnouncement; -import org.terning.terningserver.scrap.application.ScrapService; -import org.terning.terningserver.scrap.domain.Scrap; -import org.terning.terningserver.user.domain.User; -import org.terning.terningserver.user.domain.AuthType; -import org.terning.terningserver.scrap.domain.Color; -import org.terning.terningserver.internshipAnnouncement.domain.CompanyCategory; -import org.terning.terningserver.scrap.dto.request.CreateScrapRequestDto; -import org.terning.terningserver.internshipAnnouncement.repository.InternshipRepository; -import org.terning.terningserver.scrap.repository.ScrapRepository; -import org.terning.terningserver.user.repository.UserRepository; - -import java.time.LocalDate; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertThrows; - - -@SpringBootTest -@ActiveProfiles("test") -class ScrapServiceTest { - - @Autowired private ScrapService scrapService; - @Autowired private ScrapRepository scrapRepository; - @Autowired private InternshipRepository internshipRepository; - @Autowired private UserRepository userRepository; - - @AfterEach - public void cleanUp() { - scrapRepository.deleteAllInBatch(); - userRepository.deleteAllInBatch(); - internshipRepository.deleteAllInBatch(); - } - - @Nested - @DisplayName("스크랩 추가 테스트") - class CreateScrapTest { - - @BeforeEach - public void setup() { - Company company = new Company("info", CompanyCategory.OTHERS, "image"); - - InternshipAnnouncement announcement = new InternshipAnnouncement( - 1L, - "test 공고", - LocalDate.now().plusDays(7), - "3개월", - 2025, - 4, - 0, - 5, - "https://mock.com", - null, - company, - "자격요건", - "직무 유형", - "상세 내용", - false - ); - - internshipRepository.save(announcement); - - for (int i = 0; i < 5; i++) { - User user = User.builder() - .authId("user" + i) - .name("test" + i) - .authType(AuthType.APPLE) - .build(); - userRepository.save(user); - - Scrap scrap = Scrap.create(user, announcement, Color.BLUE); - scrapRepository.save(scrap); - } - - for (int i = 5; i < 105; i++) { - User user = User.builder() - .authId("user" + i) - .name("test" + i) - .authType(AuthType.APPLE) - .build(); - userRepository.save(user); - } - } - - @Test - @DisplayName("동시에 여러 유저가 스크랩 추가 시 scrapCount 증가가 정상적으로 처리된다.") - public void 동시에_여러_유저가_스크랩_추가() throws InterruptedException { - int threadCount = 100; - ExecutorService executorService = Executors.newFixedThreadPool(32); - CountDownLatch latch = new CountDownLatch(threadCount); - - CreateScrapRequestDto requestDto = new CreateScrapRequestDto("red"); - - for (int i = 5; i < 105; i++) { - long userId = userRepository.findByAuthId("user" + i).orElseThrow().getId(); - executorService.submit(() -> { - try { - scrapService.createScrap(1L, requestDto, userId); - } finally { - latch.countDown(); - } - }); - } - - latch.await(); - executorService.shutdown(); - - - InternshipAnnouncement savedAnnouncement = internshipRepository.findById(1L).orElseThrow(); - assertThat(savedAnnouncement.getScrapCount()).isEqualTo(105L); - assertThat(scrapRepository.count()).isEqualTo(105L); - - } - } - - @Nested - @DisplayName("스크랩 취소 테스트") - class DeleteScrapTest { - - @BeforeEach - public void setup() { - Company company = new Company("info", CompanyCategory.OTHERS, "image"); - - InternshipAnnouncement announcement = new InternshipAnnouncement( - 1L, - "test 공고", - LocalDate.now().plusDays(7), - "3개월", - 2025, - 4, - 0, - 100, // scrapCount = 100 - "https://mock.com", - null, - company, - "자격요건", - "직무 유형", - "상세 내용", - false - ); - - internshipRepository.save(announcement); - - for (int i = 0; i < 100; i++) { - User user = User.builder() - .authId("user" + i) - .name("test" + i) - .authType(AuthType.APPLE) - .build(); - userRepository.save(user); - - Scrap scrap = Scrap.create(user, announcement, Color.BLUE); - scrapRepository.save(scrap); - } - } - - @Test - @DisplayName("동시에 여러 유저가 스크랩 취소 시 scrapCount 감소가 정상적으로 처리된다.") - public void 동시에_여러_유저가_스크랩_취소() throws InterruptedException { - int threadCount = 100; - ExecutorService executorService = Executors.newFixedThreadPool(32); - CountDownLatch latch = new CountDownLatch(threadCount); - - for (int i = 0; i < 100; i++) { - long userId = userRepository.findByAuthId("user" + i).orElseThrow().getId(); - executorService.submit(() -> { - try { - scrapService.deleteScrap(1L, userId); - } finally { - latch.countDown(); - } - }); - } - - latch.await(); - executorService.shutdown(); - - InternshipAnnouncement savedAnnouncement = internshipRepository.findById(1L).orElseThrow(); - assertThat(savedAnnouncement.getScrapCount()).isEqualTo(0L); - assertThat(scrapRepository.count()).isEqualTo(0L); - } - - @Test - @DisplayName("스크랩 취소시에 Unchecked Exception 발생 시 트랜잭션 롤백이 정상적으로 처리된다.") - public void 트랜잭션_롤백_테스트() { - Long userId = 10000L; - Long internshipAnnouncementId = 1L; - - RuntimeException exception = assertThrows(RuntimeException.class, () -> { - scrapService.deleteScrap(internshipAnnouncementId, userId); - }); - - InternshipAnnouncement savedAnnouncement = internshipRepository.findById(internshipAnnouncementId).orElseThrow(); - assertThat(exception.getMessage()).isEqualTo("스크랩 정보가 존재하지 않습니다"); - assertThat(savedAnnouncement.getScrapCount()).isEqualTo(100L); - } - } -} \ No newline at end of file +//package org.terning.terningserver.service; +// +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Nested; +//import org.junit.jupiter.api.Test; +// +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.context.ActiveProfiles; +// +//import org.terning.terningserver.internshipAnnouncement.domain.Company; +//import org.terning.terningserver.internshipAnnouncement.domain.InternshipAnnouncement; +//import org.terning.terningserver.scrap.application.ScrapService; +//import org.terning.terningserver.scrap.domain.Scrap; +//import org.terning.terningserver.user.domain.User; +//import org.terning.terningserver.user.domain.AuthType; +//import org.terning.terningserver.scrap.domain.Color; +//import org.terning.terningserver.internshipAnnouncement.domain.CompanyCategory; +//import org.terning.terningserver.scrap.dto.request.CreateScrapRequestDto; +//import org.terning.terningserver.internshipAnnouncement.repository.InternshipRepository; +//import org.terning.terningserver.scrap.repository.ScrapRepository; +//import org.terning.terningserver.user.repository.UserRepository; +// +//import java.time.LocalDate; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.ExecutorService; +//import java.util.concurrent.Executors; +// +//import static org.assertj.core.api.Assertions.*; +//import static org.junit.jupiter.api.Assertions.assertThrows; +// +// +//@SpringBootTest +//@ActiveProfiles("test") +//class ScrapServiceTest { +// +// @Autowired private ScrapService scrapService; +// @Autowired private ScrapRepository scrapRepository; +// @Autowired private InternshipRepository internshipRepository; +// @Autowired private UserRepository userRepository; +// +// @AfterEach +// public void cleanUp() { +// scrapRepository.deleteAllInBatch(); +// userRepository.deleteAllInBatch(); +// internshipRepository.deleteAllInBatch(); +// } +// +// @Nested +// @DisplayName("스크랩 추가 테스트") +// class CreateScrapTest { +// +// @BeforeEach +// public void setup() { +// Company company = new Company("info", CompanyCategory.OTHERS, "image"); +// +// InternshipAnnouncement announcement = new InternshipAnnouncement( +// 1L, +// "test 공고", +// LocalDate.now().plusDays(7), +// "3개월", +// 2025, +// 4, +// 0, +// 5, +// "https://mock.com", +// null, +// company, +// "자격요건", +// "직무 유형", +// "상세 내용", +// false +// ); +// +// internshipRepository.save(announcement); +// +// for (int i = 0; i < 5; i++) { +// User user = User.builder() +// .authId("user" + i) +// .name("test" + i) +// .authType(AuthType.APPLE) +// .build(); +// userRepository.save(user); +// +// Scrap scrap = Scrap.create(user, announcement, Color.BLUE); +// scrapRepository.save(scrap); +// } +// +// for (int i = 5; i < 105; i++) { +// User user = User.builder() +// .authId("user" + i) +// .name("test" + i) +// .authType(AuthType.APPLE) +// .build(); +// userRepository.save(user); +// } +// } +// +// @Test +// @DisplayName("동시에 여러 유저가 스크랩 추가 시 scrapCount 증가가 정상적으로 처리된다.") +// public void 동시에_여러_유저가_스크랩_추가() throws InterruptedException { +// int threadCount = 100; +// ExecutorService executorService = Executors.newFixedThreadPool(32); +// CountDownLatch latch = new CountDownLatch(threadCount); +// +// CreateScrapRequestDto requestDto = new CreateScrapRequestDto("red"); +// +// for (int i = 5; i < 105; i++) { +// long userId = userRepository.findByAuthId("user" + i).orElseThrow().getId(); +// executorService.submit(() -> { +// try { +// scrapService.createScrap(1L, requestDto, userId); +// } finally { +// latch.countDown(); +// } +// }); +// } +// +// latch.await(); +// executorService.shutdown(); +// +// +// InternshipAnnouncement savedAnnouncement = internshipRepository.findById(1L).orElseThrow(); +// assertThat(savedAnnouncement.getScrapCount()).isEqualTo(105L); +// assertThat(scrapRepository.count()).isEqualTo(105L); +// +// } +// } +// +// @Nested +// @DisplayName("스크랩 취소 테스트") +// class DeleteScrapTest { +// +// @BeforeEach +// public void setup() { +// Company company = new Company("info", CompanyCategory.OTHERS, "image"); +// +// InternshipAnnouncement announcement = new InternshipAnnouncement( +// 1L, +// "test 공고", +// LocalDate.now().plusDays(7), +// "3개월", +// 2025, +// 4, +// 0, +// 100, // scrapCount = 100 +// "https://mock.com", +// null, +// company, +// "자격요건", +// "직무 유형", +// "상세 내용", +// false +// ); +// +// internshipRepository.save(announcement); +// +// for (int i = 0; i < 100; i++) { +// User user = User.builder() +// .authId("user" + i) +// .name("test" + i) +// .authType(AuthType.APPLE) +// .build(); +// userRepository.save(user); +// +// Scrap scrap = Scrap.create(user, announcement, Color.BLUE); +// scrapRepository.save(scrap); +// } +// } +// +// @Test +// @DisplayName("동시에 여러 유저가 스크랩 취소 시 scrapCount 감소가 정상적으로 처리된다.") +// public void 동시에_여러_유저가_스크랩_취소() throws InterruptedException { +// int threadCount = 100; +// ExecutorService executorService = Executors.newFixedThreadPool(32); +// CountDownLatch latch = new CountDownLatch(threadCount); +// +// for (int i = 0; i < 100; i++) { +// long userId = userRepository.findByAuthId("user" + i).orElseThrow().getId(); +// executorService.submit(() -> { +// try { +// scrapService.deleteScrap(1L, userId); +// } finally { +// latch.countDown(); +// } +// }); +// } +// +// latch.await(); +// executorService.shutdown(); +// +// InternshipAnnouncement savedAnnouncement = internshipRepository.findById(1L).orElseThrow(); +// assertThat(savedAnnouncement.getScrapCount()).isEqualTo(0L); +// assertThat(scrapRepository.count()).isEqualTo(0L); +// } +// +// @Test +// @DisplayName("스크랩 취소시에 Unchecked Exception 발생 시 트랜잭션 롤백이 정상적으로 처리된다.") +// public void 트랜잭션_롤백_테스트() { +// Long userId = 10000L; +// Long internshipAnnouncementId = 1L; +// +// RuntimeException exception = assertThrows(RuntimeException.class, () -> { +// scrapService.deleteScrap(internshipAnnouncementId, userId); +// }); +// +// InternshipAnnouncement savedAnnouncement = internshipRepository.findById(internshipAnnouncementId).orElseThrow(); +// assertThat(exception.getMessage()).isEqualTo("스크랩 정보가 존재하지 않습니다"); +// assertThat(savedAnnouncement.getScrapCount()).isEqualTo(100L); +// } +// } +//} From 85364780bb746821c3c4ce87f40760591980602b 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: Mon, 14 Jul 2025 22:31:22 +0900 Subject: [PATCH 19/23] =?UTF-8?q?refactor(auth):=20LoginCheckInterceptor?= =?UTF-8?q?=20=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9D=B4=EC=A4=91=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/config/LoginCheckInterceptor.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java index c694238..ec8a9a7 100644 --- a/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java +++ b/src/main/java/org/terning/terningserver/auth/config/LoginCheckInterceptor.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.terning.terningserver.auth.jwt.JwtProvider; +import org.terning.terningserver.auth.jwt.exception.JwtException; import java.util.Optional; @@ -16,22 +17,30 @@ public class LoginCheckInterceptor implements HandlerInterceptor { private final JwtProvider jwtProvider; private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_PREFIX = "Bearer "; private static final String USER_ID_ATTRIBUTE_NAME = "userId"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Optional userIdOpt = Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER)) - .filter(header -> header.startsWith(BEARER_PREFIX)) - .map(header -> header.substring(BEARER_PREFIX.length())) - .flatMap(token -> Optional.ofNullable(jwtProvider.getUserIdFrom(token))); - if (userIdOpt.isEmpty()) { + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + + try { + Optional userIdOpt = Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER)) + .map(jwtProvider::getUserIdFrom); + + if (userIdOpt.isEmpty()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + request.setAttribute(USER_ID_ATTRIBUTE_NAME, userIdOpt.get()); + return true; + + } catch (JwtException e) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } - - request.setAttribute(USER_ID_ATTRIBUTE_NAME, userIdOpt.get()); - return true; } } From b7020538f0a6b70e08836e579154c44f8a79ea89 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: Mon, 14 Jul 2025 22:44:29 +0900 Subject: [PATCH 20/23] =?UTF-8?q?del=20:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20dto=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../request/SignUpWithAuthIdRequestDto.java | 24 ------------------- .../response/AccessTokenGetResponseDto.java | 19 --------------- 2 files changed, 43 deletions(-) delete mode 100644 src/main/java/org/terning/terningserver/auth/dto/request/SignUpWithAuthIdRequestDto.java delete mode 100644 src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java diff --git a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpWithAuthIdRequestDto.java b/src/main/java/org/terning/terningserver/auth/dto/request/SignUpWithAuthIdRequestDto.java deleted file mode 100644 index 54ffdcd..0000000 --- a/src/main/java/org/terning/terningserver/auth/dto/request/SignUpWithAuthIdRequestDto.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.terning.terningserver.auth.dto.request; - -import lombok.Builder; -import lombok.NonNull; -import org.terning.terningserver.user.domain.AuthType; - -import static lombok.AccessLevel.*; - -@Builder(access = PRIVATE) -public record SignUpWithAuthIdRequestDto( - @NonNull String authId, - @NonNull String name, - String profileImage, - @NonNull AuthType authType -) { - public static SignUpWithAuthIdRequestDto of(String authId, String name, String profileImage, AuthType authType){ - return SignUpWithAuthIdRequestDto.builder() - .authId(authId) - .name(name) - .profileImage(profileImage) - .authType(authType) - .build(); - } -} diff --git a/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java b/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java deleted file mode 100644 index 725f9da..0000000 --- a/src/main/java/org/terning/terningserver/auth/dto/response/AccessTokenGetResponseDto.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.terning.terningserver.auth.dto.response; - -import lombok.Builder; -import lombok.NonNull; -import org.terning.terningserver.auth.dto.Token; - -import static lombok.AccessLevel.*; - -@Builder(access = PRIVATE) -public record AccessTokenGetResponseDto( - @NonNull String accessToken -) { - - public static AccessTokenGetResponseDto of(Token accessToken) { - return AccessTokenGetResponseDto.builder() - .accessToken(accessToken.accessToken()) - .build(); - } -} 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 21/23] =?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 22/23] =?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 23/23] =?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); } } }