From 4d34ffd133c94d28e4ed9576e7435f6954aa67c8 Mon Sep 17 00:00:00 2001 From: msk226 Date: Thu, 31 Jul 2025 20:46:01 +0900 Subject: [PATCH 01/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Feign=20Client=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Config=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 35 ++++++++++--------- .../spot/common/config/FeignConfig.java | 7 ++++ 2 files changed, 25 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/example/spot/common/config/FeignConfig.java diff --git a/build.gradle b/build.gradle index b2ba73cd..1043faa3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,3 @@ - plugins { id 'java' id 'jacoco' @@ -27,21 +26,21 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-validation' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - // h2 - implementation 'com.h2database:h2' - // Swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' - // apache common csv - implementation 'org.apache.commons:commons-csv:1.8' + implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // h2 + implementation 'com.h2database:h2' + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + // apache common csv + implementation 'org.apache.commons:commons-csv:1.8' // Oauth2 implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' @@ -81,6 +80,9 @@ dependencies { // P6spy implementation 'p6spy:p6spy:3.9.1' implementation 'com.github.gavlyukovskiy:datasource-decorator-spring-boot-autoconfigure:1.9.0' + + // Feign + implementation "org.springframework.cloud:spring-cloud-starter-openfeign:4.1.3" } sentry { @@ -119,7 +121,6 @@ jacocoTestCoverageVerification { } - tasks.named('test') { useJUnitPlatform() } diff --git a/src/main/java/com/example/spot/common/config/FeignConfig.java b/src/main/java/com/example/spot/common/config/FeignConfig.java new file mode 100644 index 00000000..fae435b1 --- /dev/null +++ b/src/main/java/com/example/spot/common/config/FeignConfig.java @@ -0,0 +1,7 @@ +package com.example.spot.common.config; + +import org.springframework.cloud.openfeign.EnableFeignClients; + +@EnableFeignClients +public class FeignConfig { +} From 450d98732561b2404cf044f05e7f8204e307624d Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:32:15 +0900 Subject: [PATCH 02/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/auth/application/refactor/OAuthStrategy.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java new file mode 100644 index 00000000..1899d8ae --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java @@ -0,0 +1,11 @@ +package com.example.spot.auth.application.refactor; + +import com.example.spot.member.domain.enums.LoginType; + +public interface OAuthStrategy { + LoginType getType(); + + String getOauthRedirectURL(); + + String extractEmail(String code); +} From f7a9e3ab974019e5dff8c1bb63507aa292d1cd2a Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:32:43 +0900 Subject: [PATCH 03/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EC=99=B8=EB=B6=80=20API=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20Feign=20=EB=A1=9C=EC=A7=81=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 --- .../client/kakao/KaKaoApiClient.java | 18 +++++++++++++ .../client/kakao/KaKaoAuthClient.java | 21 ++++++++++++++++ .../presentation/dto/KaKaoOAuthTokenDTO.java | 10 ++++++++ .../spot/auth/presentation/dto/KaKaoUser.java | 25 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java new file mode 100644 index 00000000..69f082f2 --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java @@ -0,0 +1,18 @@ +package com.example.spot.auth.infrastructure.client.kakao; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_AUTHORIZATION; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; + +import com.example.spot.auth.presentation.dto.KaKaoUser; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "kakaoApiClient", url = "https://kapi.kakao.com") +public interface KaKaoApiClient { + + @GetMapping("/v2/user/me") + KaKaoUser getKaKaoUserInfo( + @RequestHeader(HEADER_AUTHORIZATION) String accessToken, + @RequestHeader(HEADER_CONTENT_TYPE) String contentType); +} diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java new file mode 100644 index 00000000..3a43f794 --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java @@ -0,0 +1,21 @@ +package com.example.spot.auth.infrastructure.client.kakao; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; + +import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "kakaoAuthClient", url = "https://kauth.kakao.com") +public interface KaKaoAuthClient { + + @PostMapping("/oauth/token") + KaKaoOAuthTokenDTO getKaKaoAccessToken( + @RequestHeader(HEADER_CONTENT_TYPE) String contentType, + @RequestParam String grant_type, + @RequestParam String redirectUri, + @RequestParam String client_id, + @RequestParam(defaultValue = "authorization_code") String code); +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java b/src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java new file mode 100644 index 00000000..98a324c9 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java @@ -0,0 +1,10 @@ +package com.example.spot.auth.presentation.dto; + +public record KaKaoOAuthTokenDTO( + String token_type, + String access_token, + String refresh_token, + String expires_in, + String refresh_token_expires_in +) { +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java b/src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java new file mode 100644 index 00000000..98ca7c29 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java @@ -0,0 +1,25 @@ +package com.example.spot.auth.presentation.dto; + +public record KaKaoUser( + Long id, + String connected_at, + KaKaoPropertiesDTO properties, + KaKaoAccountDTO kakao_account +) { + + public record KaKaoAccountDTO( + Boolean has_email, + Boolean email_needs_agreement, + Boolean is_email_valid, + Boolean is_email_verified, + String email + ) { + } + + public record KaKaoPropertiesDTO( + String nickname, + String profile_image, + String thumbnail_image + ) { + } +} From 1bae48ee1bb06c03a97dd59cda83eb5037b5151b Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:32:51 +0900 Subject: [PATCH 04/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EA=B5=AC=EA=B8=80?= =?UTF-8?q?=20=EC=99=B8=EB=B6=80=20API=20=ED=98=B8=EC=B6=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Feign=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/google/GoogleApiClient.java | 15 ++++++++++++ .../client/google/GoogleAuthClient.java | 23 +++++++++++++++++++ .../presentation/dto/GoogleOAuthToken.java | 11 +++++++++ .../auth/presentation/dto/GoogleUser.java | 15 ++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java new file mode 100644 index 00000000..a019ba0f --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java @@ -0,0 +1,15 @@ +package com.example.spot.auth.infrastructure.client.google; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_AUTHORIZATION; + +import com.example.spot.auth.presentation.dto.GoogleUser; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "googleApiClient", url = "https://www.googleapis.com") +public interface GoogleApiClient { + + @GetMapping("/oauth2/v2/userinfo") + GoogleUser getGoogleUserInfo(@RequestHeader(HEADER_AUTHORIZATION) String authorization); +} diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java new file mode 100644 index 00000000..f0ae76a5 --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java @@ -0,0 +1,23 @@ +package com.example.spot.auth.infrastructure.client.google; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; + +import com.example.spot.auth.presentation.dto.GoogleOAuthToken; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "googleAuthClient", url = "https://oauth2.googleapis.com") +public interface GoogleAuthClient { + + @PostMapping("/token") + GoogleOAuthToken getGoogleAccessToken( + @RequestHeader(HEADER_CONTENT_TYPE) String contentType, + @RequestParam String code, + @RequestParam String clientId, + @RequestParam String clientSecret, + @RequestParam String redirectUri, + @RequestParam String grantType + ); +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java b/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java new file mode 100644 index 00000000..f3e16009 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java @@ -0,0 +1,11 @@ +package com.example.spot.auth.presentation.dto; + +// 구글에 일회성 코드를 다시 보내 받아올 액세스 토큰을 포함한 JSON 문자열을 담을 클래스 +public record GoogleOAuthToken( + String access_token, + int expires_in, + String scope, + String token_type, + String id_token +) { +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java b/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java new file mode 100644 index 00000000..f35130c4 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java @@ -0,0 +1,15 @@ +package com.example.spot.auth.presentation.dto; + +// 구글(서드파티)로 액세스 토큰을 보내 받아올 구글에 등록된 사용자 정보 +public record GoogleUser( + String id, + String email, + Boolean verifiedEmail, + String name, + String givenName, + String familyName, + String picture, + String locale +) { + // 필요한 경우, 여기에 메서드 정의 가능 (예: toEntity) +} From 7d6b853739d6e7898ad731bf9fd574aba96fb09d Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:33:11 +0900 Subject: [PATCH 05/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Auth=20=EB=B0=8F=20?= =?UTF-8?q?JWT=20=EC=83=81=EC=88=98=20=EA=B4=80=EB=A0=A8=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constants/AuthConstants.java | 23 +++++++++++++++++++ .../constants/JwtConstants.java | 9 ++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java create mode 100644 src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java new file mode 100644 index 00000000..947f4b59 --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java @@ -0,0 +1,23 @@ +package com.example.spot.auth.infrastructure.constants; + +public class AuthConstants { + + public static final String CONTENT_TYPE = "application/x-www-form-urlencoded;charset=utf-8"; + public static final String APPLICATION_JSON = "application/json"; + public static final String GRANT_TYPE = "authorization_code"; + + public static final String CLIENT_ID = "client_id"; + public static final String REDIRECT_URI = "redirect_uri"; + public static final String RESPONSE_TYPE = "response_type"; + public static final String RESPONSE_TYPE_CODE = "code"; + public static final String QUERY_DELIMITER = "&"; + public static final String KEY_VALUE_DELIMITER = "="; + public static final String QUERY_PREFIX = "?"; + public static final String SCOPE = "scope"; + + public static final String HEADER_AUTHORIZATION = "Authorization"; + public static final String KAKAO_AUTHORIZATION = "KakaoAK "; + public static final String HEADER_CONTENT_TYPE = "Content-Type"; + + public static final String EMAIL = "email"; +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java new file mode 100644 index 00000000..1052e0de --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java @@ -0,0 +1,9 @@ +package com.example.spot.auth.infrastructure.constants; + +public class JwtConstants { + + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String BEARER_PREFIX = "Bearer "; + public static final String ROLE_PREFIX = "ROLE_"; + public static final String CONTENT_TYPE = "application/json;charset=UTF-8"; +} From 24b023ebd62be940c4e02d3337abc680e04a489c Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:52:44 +0900 Subject: [PATCH 06/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Feign=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Config=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=95=A0?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=B6=80=EC=B0=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/spot/common/config/FeignConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/spot/common/config/FeignConfig.java b/src/main/java/com/example/spot/common/config/FeignConfig.java index fae435b1..e734abbf 100644 --- a/src/main/java/com/example/spot/common/config/FeignConfig.java +++ b/src/main/java/com/example/spot/common/config/FeignConfig.java @@ -1,7 +1,9 @@ package com.example.spot.common.config; import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; @EnableFeignClients +@Configuration public class FeignConfig { } From 7e73c689e4c43b61eebab9cd9d8d11ddb158fb04 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:53:05 +0900 Subject: [PATCH 07/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EA=B5=AC=EA=B8=80?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=EC=A0=84=EB=9E=B5=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/impl/oauth/GoogleOauth.java | 69 +++++++++++++++++++ .../impl/straegy/GoogleOAuthStrategy.java | 33 +++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java new file mode 100644 index 00000000..732dc23e --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java @@ -0,0 +1,69 @@ +package com.example.spot.auth.application.refactor.impl.oauth; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.REDIRECT_URI; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE_CODE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.SCOPE; +import static com.example.spot.auth.infrastructure.constants.JwtConstants.BEARER_PREFIX; + +import com.example.spot.auth.infrastructure.client.google.GoogleApiClient; +import com.example.spot.auth.infrastructure.client.google.GoogleAuthClient; +import com.example.spot.auth.presentation.dto.GoogleOAuthToken; +import com.example.spot.auth.presentation.dto.GoogleUser; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class GoogleOauth { + + @Value("${spring.oauth2.google.url}") + private String GOOGLE_SNS_URL; + @Value("${spring.oauth2.google.client-id}") + private String GOOGLE_SNS_CLIENT_ID; + @Value("${spring.oauth2.google.callback-login-url}") + private String GOOGLE_SNS_CALLBACK_LOGIN_URL; + @Value("${spring.oauth2.google.client-secret}") + private String GOOGLE_SNS_CLIENT_SECRET; + @Value("${spring.oauth2.google.scope}") + private String GOOGLE_DATA_ACCESS_SCOPE; + + private final GoogleAuthClient googleAuthClient; + private final GoogleApiClient googleApiClient; + + public String getOauthRedirectURL() { + Map params = new HashMap<>(); + + params.put(SCOPE, GOOGLE_DATA_ACCESS_SCOPE); + params.put(RESPONSE_TYPE, RESPONSE_TYPE_CODE); + params.put(CLIENT_ID, GOOGLE_SNS_CLIENT_ID); + params.put(REDIRECT_URI, GOOGLE_SNS_CALLBACK_LOGIN_URL); + + String parameterString = params.entrySet().stream() + .map(x -> x.getKey() + KEY_VALUE_DELIMITER + x.getValue()) + .collect(Collectors.joining(QUERY_DELIMITER)); + + return GOOGLE_SNS_URL + QUERY_PREFIX + parameterString; + } + + public GoogleOAuthToken requestAccessToken(String code) { + + return googleAuthClient.getGoogleAccessToken(CONTENT_TYPE, code, + GOOGLE_SNS_CLIENT_ID, GOOGLE_SNS_CLIENT_SECRET, + GOOGLE_SNS_CALLBACK_LOGIN_URL, GRANT_TYPE); + } + + public GoogleUser requestUserInfo(GoogleOAuthToken oAuthToken) { + return googleApiClient.getGoogleUserInfo(BEARER_PREFIX + oAuthToken.access_token()); + } +} diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java new file mode 100644 index 00000000..f18d3637 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java @@ -0,0 +1,33 @@ +package com.example.spot.auth.application.refactor.impl.straegy; + +import com.example.spot.auth.application.refactor.OAuthStrategy; +import com.example.spot.auth.application.refactor.impl.oauth.GoogleOauth; +import com.example.spot.auth.presentation.dto.GoogleOAuthToken; +import com.example.spot.auth.presentation.dto.GoogleUser; +import com.example.spot.member.domain.enums.LoginType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class GoogleOAuthStrategy implements OAuthStrategy { + + private final GoogleOauth googleOauth; + + @Override + public LoginType getType() { + return LoginType.GOOGLE; + } + + @Override + public String getOauthRedirectURL() { + return googleOauth.getOauthRedirectURL(); + } + + @Override + public String extractEmail(String code) { + GoogleOAuthToken token = googleOauth.requestAccessToken(code); + GoogleUser user = googleOauth.requestUserInfo(token); + return user.email(); + } +} From ef852665665112a4044aa9658eefffbfa598dd76 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 13:53:10 +0900 Subject: [PATCH 08/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EA=B4=80=EB=A0=A8=20=EC=A0=84=EB=9E=B5=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/impl/oauth/KaKaoOauth.java | 70 +++++++++++++++++++ .../impl/straegy/KakaoOAuthStrategy.java | 33 +++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java new file mode 100644 index 00000000..0866e8f8 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java @@ -0,0 +1,70 @@ +package com.example.spot.auth.application.refactor.impl.oauth; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.REDIRECT_URI; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE_CODE; + +import com.example.spot.auth.infrastructure.client.kakao.KaKaoApiClient; +import com.example.spot.auth.infrastructure.client.kakao.KaKaoAuthClient; +import com.example.spot.auth.infrastructure.constants.JwtConstants; +import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.KaKaoUser; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class KaKaoOauth { + + @Value("${spring.oauth2.kakao.url}") + private String KAKAO_SNS_URL; + + @Value("${spring.oauth2.kakao.client-id}") + private String KAKAO_SNS_CLIENT_ID; + + @Value("${spring.oauth2.kakao.callback-login-url}") + private String KAKAO_SNS_CALLBACK_LOGIN_URL; + + private final KaKaoApiClient kaKaoApiClient; + private final KaKaoAuthClient kaKaoAuthClient; + + public String getOauthRedirectURL() { + Map params = new HashMap<>(); + params.put(CLIENT_ID, KAKAO_SNS_CLIENT_ID); + params.put(REDIRECT_URI, KAKAO_SNS_CALLBACK_LOGIN_URL); + params.put(RESPONSE_TYPE, RESPONSE_TYPE_CODE); + + String parameterString = params.entrySet().stream() + .map(x -> x.getKey() + KEY_VALUE_DELIMITER + x.getValue()) + .collect(Collectors.joining(QUERY_DELIMITER)); + + return KAKAO_SNS_URL + QUERY_PREFIX + parameterString; + } + + public KaKaoOAuthTokenDTO requestAccessToken(String code) { + return kaKaoAuthClient.getKaKaoAccessToken( + CONTENT_TYPE, GRANT_TYPE, KAKAO_SNS_CALLBACK_LOGIN_URL, KAKAO_SNS_CLIENT_ID, code); + } + + + public KaKaoUser requestUserInfo(KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO) { + return kaKaoApiClient.getKaKaoUserInfo( + getAccessToken(kaKaoOAuthTokenDTO), CONTENT_TYPE); + } + + private static String getAccessToken(KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO) { + return JwtConstants.BEARER_PREFIX + kaKaoOAuthTokenDTO.access_token(); + } + + +} diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java new file mode 100644 index 00000000..7e9abc0b --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java @@ -0,0 +1,33 @@ +package com.example.spot.auth.application.refactor.impl.straegy; + +import com.example.spot.auth.application.refactor.OAuthStrategy; +import com.example.spot.auth.application.refactor.impl.oauth.KaKaoOauth; +import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.KaKaoUser; +import com.example.spot.member.domain.enums.LoginType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class KakaoOAuthStrategy implements OAuthStrategy { + + private final KaKaoOauth kaKaoOauth; + + @Override + public LoginType getType() { + return LoginType.KAKAO; + } + + @Override + public String getOauthRedirectURL() { + return kaKaoOauth.getOauthRedirectURL(); + } + + @Override + public String extractEmail(String code) { + KaKaoOAuthTokenDTO token = kaKaoOauth.requestAccessToken(code); + KaKaoUser user = kaKaoOauth.requestUserInfo(token); + return user.kakao_account().email(); + } +} From 2acf9906bc8269d4d05d9661c2751c562e054660 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:00:50 +0900 Subject: [PATCH 09/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/OAuthStrategyFactory.java | 26 +++++++++++++++++++ .../UnsupportedSocialLoginTypeException.java | 11 ++++++++ .../exception/base/UnsupportedException.java | 12 +++++++++ 3 files changed, 49 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java create mode 100644 src/main/java/com/example/spot/auth/exception/UnsupportedSocialLoginTypeException.java create mode 100644 src/main/java/com/example/spot/common/api/exception/base/UnsupportedException.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java new file mode 100644 index 00000000..75b13e35 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java @@ -0,0 +1,26 @@ +package com.example.spot.auth.application.refactor; + +import com.example.spot.auth.exception.UnsupportedSocialLoginTypeException; +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.member.domain.enums.LoginType; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.springframework.stereotype.Component; + +@Component +public class OAuthStrategyFactory { + + private final Map strategyMap; + + public OAuthStrategyFactory(List strategies) { + this.strategyMap = strategies.stream() + .collect(Collectors.toMap(OAuthStrategy::getType, s -> s)); + } + + public OAuthStrategy getStrategy(LoginType type) { + return Optional.ofNullable(strategyMap.get(type)) + .orElseThrow(() -> new UnsupportedSocialLoginTypeException(ErrorStatus._MEMBER_UNSUPPORTED_LOGIN_TYPE)); + } +} diff --git a/src/main/java/com/example/spot/auth/exception/UnsupportedSocialLoginTypeException.java b/src/main/java/com/example/spot/auth/exception/UnsupportedSocialLoginTypeException.java new file mode 100644 index 00000000..594ba80b --- /dev/null +++ b/src/main/java/com/example/spot/auth/exception/UnsupportedSocialLoginTypeException.java @@ -0,0 +1,11 @@ +package com.example.spot.auth.exception; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.base.UnsupportedException; + +public class UnsupportedSocialLoginTypeException extends UnsupportedException { + + public UnsupportedSocialLoginTypeException(ErrorStatus status) { + super(status); + } +} diff --git a/src/main/java/com/example/spot/common/api/exception/base/UnsupportedException.java b/src/main/java/com/example/spot/common/api/exception/base/UnsupportedException.java new file mode 100644 index 00000000..65c307bc --- /dev/null +++ b/src/main/java/com/example/spot/common/api/exception/base/UnsupportedException.java @@ -0,0 +1,12 @@ +package com.example.spot.common.api.exception.base; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.GeneralException; + + +public class UnsupportedException extends GeneralException { + + public UnsupportedException(ErrorStatus status) { + super(status); + } +} From b476cc660cddcd07b7f043725640d0069641626c Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:34:38 +0900 Subject: [PATCH 10/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=A0=95=EC=A0=81=20=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/spot/member/domain/Member.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/spot/member/domain/Member.java b/src/main/java/com/example/spot/member/domain/Member.java index c3834090..cef4139a 100644 --- a/src/main/java/com/example/spot/member/domain/Member.java +++ b/src/main/java/com/example/spot/member/domain/Member.java @@ -1,6 +1,7 @@ package com.example.spot.member.domain; import com.example.spot.common.entity.BaseEntity; +import com.example.spot.common.security.utils.MemberUtils; import com.example.spot.member.domain.enums.Carrier; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.LoginType; @@ -29,9 +30,10 @@ @DynamicUpdate @DynamicInsert @NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) public class Member extends BaseEntity { + public static final String DEFAULT_PASSWORD = "default"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, unique = true) @@ -94,6 +96,23 @@ public class Member extends BaseEntity { private Status status; + public static Member toMember(LoginType loginType, String name, String email, String profileImage) { + return Member.builder() + .name(name) + .nickname(name) + .email(email) + .profileImage(profileImage) + .carrier(Carrier.NONE) + .password(DEFAULT_PASSWORD) + .phone(MemberUtils.generatePhoneNumber()) + .birth(LocalDate.now()) + .personalInfo(false) + .idInfo(false) + .isAdmin(false) + .loginType(loginType) + .build(); + } + public void updateTerm(Boolean personalInfo, Boolean idInfo) { this.personalInfo = personalInfo; this.idInfo = idInfo; From 3b139bbaa365312383e9f274aaf3010d9d31ac8e Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:35:00 +0900 Subject: [PATCH 11/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EA=B0=81=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=EB=B3=84=20=ED=9A=8C=EC=9B=90=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=A0=95=EC=A0=81=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=9C=20=ED=9A=8C=EC=9B=90=20=EC=83=9D=EC=84=B1=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/auth/application/refactor/OAuthStrategy.java | 3 ++- .../refactor/impl/straegy/GoogleOAuthStrategy.java | 5 +++-- .../refactor/impl/straegy/KakaoOAuthStrategy.java | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java index 1899d8ae..3f447421 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java @@ -1,5 +1,6 @@ package com.example.spot.auth.application.refactor; +import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; public interface OAuthStrategy { @@ -7,5 +8,5 @@ public interface OAuthStrategy { String getOauthRedirectURL(); - String extractEmail(String code); + Member toMember(String code); // 전략별 구현에서 Member 객체 생성 } diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java index f18d3637..c34fd020 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java @@ -4,6 +4,7 @@ import com.example.spot.auth.application.refactor.impl.oauth.GoogleOauth; import com.example.spot.auth.presentation.dto.GoogleOAuthToken; import com.example.spot.auth.presentation.dto.GoogleUser; +import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -25,9 +26,9 @@ public String getOauthRedirectURL() { } @Override - public String extractEmail(String code) { + public Member toMember(String code) { GoogleOAuthToken token = googleOauth.requestAccessToken(code); GoogleUser user = googleOauth.requestUserInfo(token); - return user.email(); + return Member.toMember(getType(), user.name(), user.email(), user.picture()); } } diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java index 7e9abc0b..5e93b242 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java @@ -4,6 +4,7 @@ import com.example.spot.auth.application.refactor.impl.oauth.KaKaoOauth; import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; import com.example.spot.auth.presentation.dto.KaKaoUser; +import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -25,9 +26,10 @@ public String getOauthRedirectURL() { } @Override - public String extractEmail(String code) { + public Member toMember(String code) { KaKaoOAuthTokenDTO token = kaKaoOauth.requestAccessToken(code); KaKaoUser user = kaKaoOauth.requestUserInfo(token); - return user.kakao_account().email(); + return Member.toMember(getType(), user.properties().nickname(), user.properties().nickname(), + user.properties().profile_image()); } } From 372026d19653964ca10e174e43edd03a9b67fd19 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:35:27 +0900 Subject: [PATCH 12/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EA=B0=80=EC=9E=85=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=B3=84=EB=8F=84=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20-=20=EC=B6=94=ED=9B=84=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/impl/OAuthMemberProcessor.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java b/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java new file mode 100644 index 00000000..a5abe4ec --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java @@ -0,0 +1,92 @@ +package com.example.spot.auth.application.refactor.impl; + +import com.example.spot.auth.domain.RefreshToken; +import com.example.spot.auth.domain.RefreshTokenRepository; +import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.common.security.utils.JwtTokenProvider; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.enums.LoginType; +import com.example.spot.member.infrastructure.MemberRepository; +import com.example.spot.member.infrastructure.PreferredRegionRepository; +import com.example.spot.member.infrastructure.PreferredThemeRepository; +import com.example.spot.member.infrastructure.StudyJoinReasonRepository; +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OAuthMemberProcessor { + + private final MemberRepository memberRepository; + private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenProvider tokenProvider; + private final PreferredThemeRepository preferredThemeRepository; + private final PreferredRegionRepository preferredRegionRepository; + private final StudyJoinReasonRepository studyJoinReasonRepository; + @PersistenceContext + private EntityManager entityManager; + + public MemberResponseDTO.SocialLoginSignInDTO processOAuthMember(LoginType loginType, + Member providerMember) { + // 다른 로그인 타입으로 가입된 경우 + if (memberRepository.existsByEmailAndLoginTypeNot(providerMember.getEmail(), loginType)) { + Member existing = memberRepository.findByEmail(providerMember.getEmail()) + .orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND)); + if (existing.getInactive() != null) { + refreshTokenRepository.deleteByMemberId(existing.getId()); + memberRepository.deleteById(existing.getId()); + entityManager.flush(); + } else { + throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST); + } + } + + boolean isSpotMember = false; + Member member = memberRepository.findByEmail(providerMember.getEmail()).orElse(null); + + if (member != null && member.getInactive() != null) { + refreshTokenRepository.deleteByMemberId(member.getId()); + memberRepository.deleteById(member.getId()); + entityManager.flush(); + member = null; + } + + if (member == null) { + member = memberRepository.save(providerMember); + } + + isSpotMember = checkIsSpotMember(member); + + TokenResponseDTO.TokenDTO token = tokenProvider.createToken(member.getId()); + saveRefreshToken(member, token); + + return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember, + MemberResponseDTO.MemberSignInDTO.builder() + .tokens(token) + .memberId(member.getId()) + .loginType(member.getLoginType()) + .email(member.getEmail()) + .build()); + } + + private void saveRefreshToken(Member member, TokenResponseDTO.TokenDTO token) { + refreshTokenRepository.deleteAllByMemberId(member.getId()); + RefreshToken refreshToken = RefreshToken.builder() + .memberId(member.getId()) + .token(token.getRefreshToken()) + .build(); + refreshTokenRepository.save(refreshToken); + } + + private boolean checkIsSpotMember(Member member) { + Long id = member.getId(); + return preferredThemeRepository.existsByMemberId(id) && + preferredRegionRepository.existsByMemberId(id) && + studyJoinReasonRepository.existsByMemberId(id); + } +} \ No newline at end of file From 9c0c7b3455027d5730d6ea38a5c72d8ec67eba56 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:35:59 +0900 Subject: [PATCH 13/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=A0=84=EB=9E=B5?= =?UTF-8?q?=20=ED=8C=A8=ED=84=B4=20=EA=B8=B0=EB=B0=98=20OAuth=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/refactor/OAuthService.java | 24 +++++++++++++++++++ .../auth/presentation/dto/GoogleUser.java | 3 +-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/OAuthService.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java b/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java new file mode 100644 index 00000000..08c58a55 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java @@ -0,0 +1,24 @@ +package com.example.spot.auth.application.refactor; + +import com.example.spot.auth.application.refactor.impl.OAuthMemberProcessor; +import com.example.spot.member.domain.enums.LoginType; +import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class OAuthService { + + private final OAuthStrategyFactory strategyFactory; + private final OAuthMemberProcessor memberProcessor; + + public String redirectURL(LoginType type) { + return strategyFactory.getStrategy(type).getOauthRedirectURL(); + } + + public SocialLoginSignInDTO loginOrSignUp(LoginType type, String code) { + OAuthStrategy strategy = strategyFactory.getStrategy(type); + return memberProcessor.processOAuthMember(type, strategy.toMember(code)); + } +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java b/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java index f35130c4..f4c1c02f 100644 --- a/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java +++ b/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java @@ -1,6 +1,5 @@ package com.example.spot.auth.presentation.dto; -// 구글(서드파티)로 액세스 토큰을 보내 받아올 구글에 등록된 사용자 정보 public record GoogleUser( String id, String email, @@ -11,5 +10,5 @@ public record GoogleUser( String picture, String locale ) { - // 필요한 경우, 여기에 메서드 정의 가능 (예: toEntity) + } From c437495ec6ff9ad397438942361a7f91810859ac Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:50:10 +0900 Subject: [PATCH 14/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=ED=99=98=EA=B2=BD=20=EB=B3=80=EC=88=98=20Prefix=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/legacy/NaverOAuthService.java | 63 ++++++++++--------- .../kakao/KakaoOAuthClient.java | 29 +++++---- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java b/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java index 4be498b8..a428d47e 100644 --- a/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java +++ b/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java @@ -1,18 +1,13 @@ package com.example.spot.auth.application.legacy; -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.auth.presentation.dto.naver.NaverCallback; import com.example.spot.auth.presentation.dto.naver.NaverMember; import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken; +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.MemberHandler; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.util.UriComponentsBuilder; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -20,27 +15,31 @@ import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.util.UriComponentsBuilder; @Deprecated @Service @RequiredArgsConstructor public class NaverOAuthService { - @Value("${spring.OAuth2.naver.callback-url}") + @Value("${spring.oauth2.naver.callback-url}") private String NAVER_CALL_BACK_URL; - @Value("${spring.OAuth2.naver.client-id}") + @Value("${spring.oauth2.naver.client-id}") private String NAVER_CLIENT_ID; - @Value("${spring.OAuth2.naver.client-secret}") + @Value("${spring.oauth2.naver.client-secret}") private String NAVER_CLIENT_SECRET; - @Value("${spring.OAuth2.naver.csrf-token}") + @Value("${spring.oauth2.naver.csrf-token}") private String CSRF_TOKEN; /** - * 네이버 로그인 인증 요청 URL을 생성하는 메서드입니다. - * 네이버에서 발급받은 client id와 callback url을 쿼리로 포함하여 String 타입의 URL을 반환합니다. + * 네이버 로그인 인증 요청 URL을 생성하는 메서드입니다. 네이버에서 발급받은 client id와 callback url을 쿼리로 포함하여 String 타입의 URL을 반환합니다. + * * @return NAVER_OAUTH_URL */ public String getNaverAuthorizeUrl() { @@ -49,27 +48,28 @@ public String getNaverAuthorizeUrl() { .queryParam("response_type", "code") .queryParam("client_id", NAVER_CLIENT_ID) .queryParam("redirect_uri", NAVER_CALL_BACK_URL) - .queryParam("state",CSRF_TOKEN) + .queryParam("state", CSRF_TOKEN) .build() .toUriString(); } /** - * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. - * 내부적으로 issueNaverAccessToken, getNaverProfile 메서드를 수행합니다. - * @param request : HttpServletRequest - * @param response : HttpServletResponse + * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. 내부적으로 issueNaverAccessToken, getNaverProfile 메서드를 수행합니다. + * + * @param request : HttpServletRequest + * @param response : HttpServletResponse * @param naverCallback : Callback 함수 성공시 반환되는 요소(code, state, error, error_description) * @return 네이버 프로필 정보 */ - public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception { + public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, + NaverCallback naverCallback) throws Exception { // 네이버 액세스 토큰 발급 String accessToken = issueNaverAccessToken(naverCallback.getCode()); ObjectMapper mapper = new ObjectMapper(); NaverOAuthToken.NaverTokenIssuanceDTO naverTokenIssuanceDTO = mapper.readValue(accessToken, NaverOAuthToken.NaverTokenIssuanceDTO.class); - + // 네이버 프로필 반환 String naverMember = getNaverProfile(naverTokenIssuanceDTO); ObjectMapper objectMapper = new ObjectMapper(); @@ -77,13 +77,15 @@ public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpSe } /** - * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. - * 내부적으로 getNaverProfile 메서드를 수행합니다. - * @param request : HttpServletRequest + * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. 내부적으로 getNaverProfile 메서드를 수행합니다. + * + * @param request : HttpServletRequest * @param response : HttpServletResponse * @return 네이버 프로필 정보 */ - public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception { + public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, + NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) + throws Exception { // 네이버 프로필 반환 String naverMember = getNaverProfile(naverTokenDTO); ObjectMapper objectMapper = new ObjectMapper(); @@ -92,9 +94,9 @@ public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpSe /** * 네이버 액세스 토큰을 발급하는 메서드입니다. + * * @param authorizationCode : Callback 함수로부터 반환된 authorizationCode - * @return String 토큰 정보 - * (access_token, refresh_token, token_type, expires_in, error, error_description) + * @return String 토큰 정보 (access_token, refresh_token, token_type, expires_in, error, error_description) */ private String issueNaverAccessToken(String authorizationCode) { @@ -104,7 +106,7 @@ private String issueNaverAccessToken(String authorizationCode) { .queryParam("client_id", NAVER_CLIENT_ID) .queryParam("client_secret", NAVER_CLIENT_SECRET) .queryParam("code", authorizationCode) - .queryParam("state", URLEncoder.encode(CSRF_TOKEN,StandardCharsets.UTF_8)) + .queryParam("state", URLEncoder.encode(CSRF_TOKEN, StandardCharsets.UTF_8)) .build() .toUriString(); try { @@ -120,7 +122,9 @@ private String issueNaverAccessToken(String authorizationCode) { /** * 토큰 정보를 파라미터로 받아 네이버 프로필을 조회하는 메서드입니다. - * @param naverTokenIssuanceDTO : 토큰 객체 (access_token, refresh_token, token_type, expires_in, error, error_description) + * + * @param naverTokenIssuanceDTO : 토큰 객체 (access_token, refresh_token, token_type, expires_in, error, + * error_description) * @return String 프로필 정보 */ private String getNaverProfile(NaverOAuthToken.NaverTokenIssuanceDTO naverTokenIssuanceDTO) { @@ -131,7 +135,7 @@ private String getNaverProfile(NaverOAuthToken.NaverTokenIssuanceDTO naverTokenI try { URL url = new URL(NAVER_PROFILE_URL); - HttpURLConnection con = (HttpURLConnection)url.openConnection(); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setRequestProperty("Authorization", tokenType + " " + accessToken); @@ -143,6 +147,7 @@ private String getNaverProfile(NaverOAuthToken.NaverTokenIssuanceDTO naverTokenI /** * HTTP 응답을 버퍼를 통해 읽어오는 메서드입니다. + * * @param con : HttpURLConnection (HTTP 연결 정보) * @return String Response */ diff --git a/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java index 77958679..1f0ff591 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java @@ -26,13 +26,13 @@ @RequiredArgsConstructor public class KakaoOAuthClient { - @Value("${spring.OAuth2.kakao.url}") + @Value("${spring.oauth2.kakao.url}") private String KAKAO_SNS_URL; - @Value("${spring.OAuth2.kakao.client-id}") + @Value("${spring.oauth2.kakao.client-id}") private String KAKAO_SNS_CLIENT_ID; - @Value("${spring.OAuth2.kakao.callback-login-url}") + @Value("${spring.oauth2.kakao.callback-login-url}") private String KAKAO_SNS_CALLBACK_LOGIN_URL; @@ -41,6 +41,7 @@ public class KakaoOAuthClient { /** * 카카오 로그인 요청 URL을 생성합니다. + * * @return 카카오 로그인 요청 URL */ public String getOauthRedirectURL() { @@ -52,8 +53,8 @@ public String getOauthRedirectURL() { // URL 파라미터 생성 String parameterString = params.entrySet().stream() - .map(x -> x.getKey() + "=" + x.getValue()) - .collect(Collectors.joining("&")); + .map(x -> x.getKey() + "=" + x.getValue()) + .collect(Collectors.joining("&")); // 카카오 로그인 요청 URL return KAKAO_SNS_URL + "?" + parameterString; @@ -61,6 +62,7 @@ public String getOauthRedirectURL() { /** * 카카오 로그인 요청을 합니다. + * * @param code 카카오 로그인 요청 시 발급받은 코드 * @return 카카오 로그인 요청 결과 */ @@ -81,7 +83,8 @@ public ResponseEntity requestAccessToken(String code) { HttpEntity> request = new HttpEntity(params, headers); // 카카오 로그인 요청 - ResponseEntity responseEntity = restTemplate.postForEntity(KAKAO_TOKEN_REQUEST_URL, request, String.class); + ResponseEntity responseEntity = restTemplate.postForEntity(KAKAO_TOKEN_REQUEST_URL, request, + String.class); // 요청 결과 반환 if (responseEntity.getStatusCode() == HttpStatus.OK) { @@ -94,22 +97,24 @@ public ResponseEntity requestAccessToken(String code) { /** * 카카오 로그인 요청 결과에서 AccessToken을 추출합니다. + * * @param response 카카오 로그인 요청 결과 * @return 카카오 로그인 성공 시 반환되는 AccessToken * @throws JsonProcessingException JSON 파싱 예외 */ public KaKaoOAuthTokenDTO getAccessToken(ResponseEntity response) - throws JsonProcessingException { + throws JsonProcessingException { // 카카오 로그인 요청 결과에서 AccessToken 추출 - KaKaoOAuthToken.KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO= objectMapper.readValue(response.getBody(), - KaKaoOAuthTokenDTO.class); + KaKaoOAuthToken.KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO = objectMapper.readValue(response.getBody(), + KaKaoOAuthTokenDTO.class); return kaKaoOAuthTokenDTO; } /** * 카카오 로그인 시 발급된 accessToken을 이용하여 사용자 정보를 요청합니다. + * * @param accessToken 카카오 로그인 시 발급된 accessToken * @return 사용자 정보 요청 결과 */ @@ -126,19 +131,21 @@ public ResponseEntity requestUserInfo(String accessToken) { HttpEntity> request = new HttpEntity(headers); // 사용자 정보 요청 - ResponseEntity responseEntity = restTemplate.exchange(KAKAO_USER_INFO_REQUEST_URL, HttpMethod.GET, request, String.class); + ResponseEntity responseEntity = restTemplate.exchange(KAKAO_USER_INFO_REQUEST_URL, HttpMethod.GET, + request, String.class); return responseEntity; } /** * 카카오 로그인 요청 결과에서 사용자 정보를 추출합니다. + * * @param userInfoRes 카카오 로그인 요청 결과 * @return 카카오 로그인 성공 시 반환되는 사용자 정보 * @throws JsonProcessingException JSON 파싱 예외 */ public KaKaoUser getUserInfo(ResponseEntity userInfoRes) - throws JsonProcessingException { + throws JsonProcessingException { // 사용자 정보 파싱 KaKaoUser kaKaoUser = objectMapper.readValue(userInfoRes.getBody(), KaKaoUser.class); return kaKaoUser; From 71d58fa924c403fb05a1fc172c6ac809e7168509 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:50:31 +0900 Subject: [PATCH 15/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EA=B0=80=EC=9E=85=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=ED=8A=B8=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=95=A0=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=B6=80=EC=B0=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/refactor/impl/OAuthMemberProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java b/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java index a5abe4ec..1a5fd29c 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/OAuthMemberProcessor.java @@ -17,6 +17,7 @@ import jakarta.persistence.PersistenceContext; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor @@ -31,6 +32,7 @@ public class OAuthMemberProcessor { @PersistenceContext private EntityManager entityManager; + @Transactional public MemberResponseDTO.SocialLoginSignInDTO processOAuthMember(LoginType loginType, Member providerMember) { // 다른 로그인 타입으로 가입된 경우 From 0a1d20f4938ead3698ae93c447fa688355514c92 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 14:51:37 +0900 Subject: [PATCH 16/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Feign=20Client=20?= =?UTF-8?q?=EC=8A=A4=EC=BA=94=20Base=20Package=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=83=81=EC=9C=84=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EB=A1=9C=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/spot/common/config/FeignConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/spot/common/config/FeignConfig.java b/src/main/java/com/example/spot/common/config/FeignConfig.java index e734abbf..4fbc9e71 100644 --- a/src/main/java/com/example/spot/common/config/FeignConfig.java +++ b/src/main/java/com/example/spot/common/config/FeignConfig.java @@ -3,7 +3,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; -@EnableFeignClients +@EnableFeignClients(basePackages = "com.example.spot") @Configuration public class FeignConfig { } From 3a112933b9b2edff92ec8cd68d6dccc47cd39d29 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:27:45 +0900 Subject: [PATCH 17/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/legacy/NaverOAuthService.java | 175 ------------ .../refactor/KakaoAuthService.java | 18 -- .../refactor/impl/KakaoAuthServiceImpl.java | 258 ------------------ .../refactor/KakaoAuthController.java | 78 ------ .../dto/google/GoogleExampleResponse.java | 21 -- .../dto/kakao/KaKaoOAuthToken.java | 21 -- .../presentation/dto/kakao/KaKaoUser.java | 67 ----- .../presentation/dto/naver/NaverCallback.java | 26 -- .../presentation/dto/naver/NaverMember.java | 75 ----- .../dto/naver/NaverOAuthToken.java | 98 ------- 10 files changed, 837 deletions(-) delete mode 100644 src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java delete mode 100644 src/main/java/com/example/spot/auth/application/refactor/KakaoAuthService.java delete mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/KakaoAuthServiceImpl.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/controller/refactor/KakaoAuthController.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/google/GoogleExampleResponse.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoOAuthToken.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoUser.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/naver/NaverCallback.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/naver/NaverMember.java delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/naver/NaverOAuthToken.java diff --git a/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java b/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java deleted file mode 100644 index a428d47e..00000000 --- a/src/main/java/com/example/spot/auth/application/legacy/NaverOAuthService.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.example.spot.auth.application.legacy; - -import com.example.spot.auth.presentation.dto.naver.NaverCallback; -import com.example.spot.auth.presentation.dto.naver.NaverMember; -import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken; -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.web.util.UriComponentsBuilder; - -@Deprecated -@Service -@RequiredArgsConstructor -public class NaverOAuthService { - - @Value("${spring.oauth2.naver.callback-url}") - private String NAVER_CALL_BACK_URL; - - @Value("${spring.oauth2.naver.client-id}") - private String NAVER_CLIENT_ID; - - @Value("${spring.oauth2.naver.client-secret}") - private String NAVER_CLIENT_SECRET; - - @Value("${spring.oauth2.naver.csrf-token}") - private String CSRF_TOKEN; - - /** - * 네이버 로그인 인증 요청 URL을 생성하는 메서드입니다. 네이버에서 발급받은 client id와 callback url을 쿼리로 포함하여 String 타입의 URL을 반환합니다. - * - * @return NAVER_OAUTH_URL - */ - public String getNaverAuthorizeUrl() { - String NAVER_OAUTH_URL = "https://nid.naver.com/oauth2.0/authorize"; - return UriComponentsBuilder.fromHttpUrl(NAVER_OAUTH_URL) - .queryParam("response_type", "code") - .queryParam("client_id", NAVER_CLIENT_ID) - .queryParam("redirect_uri", NAVER_CALL_BACK_URL) - .queryParam("state", CSRF_TOKEN) - .build() - .toUriString(); - } - - /** - * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. 내부적으로 issueNaverAccessToken, getNaverProfile 메서드를 수행합니다. - * - * @param request : HttpServletRequest - * @param response : HttpServletResponse - * @param naverCallback : Callback 함수 성공시 반환되는 요소(code, state, error, error_description) - * @return 네이버 프로필 정보 - */ - public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, - NaverCallback naverCallback) throws Exception { - - // 네이버 액세스 토큰 발급 - String accessToken = issueNaverAccessToken(naverCallback.getCode()); - ObjectMapper mapper = new ObjectMapper(); - NaverOAuthToken.NaverTokenIssuanceDTO naverTokenIssuanceDTO - = mapper.readValue(accessToken, NaverOAuthToken.NaverTokenIssuanceDTO.class); - - // 네이버 프로필 반환 - String naverMember = getNaverProfile(naverTokenIssuanceDTO); - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(naverMember, NaverMember.ResponseDTO.class); - } - - /** - * 네이버 액세스 토큰을 발급하고 해당 액세스 토큰을 통해 네이버 프로필을 조회하는 메서드입니다. 내부적으로 getNaverProfile 메서드를 수행합니다. - * - * @param request : HttpServletRequest - * @param response : HttpServletResponse - * @return 네이버 프로필 정보 - */ - public NaverMember.ResponseDTO getNaverMember(HttpServletRequest request, HttpServletResponse response, - NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) - throws Exception { - // 네이버 프로필 반환 - String naverMember = getNaverProfile(naverTokenDTO); - ObjectMapper objectMapper = new ObjectMapper(); - return objectMapper.readValue(naverMember, NaverMember.ResponseDTO.class); - } - - /** - * 네이버 액세스 토큰을 발급하는 메서드입니다. - * - * @param authorizationCode : Callback 함수로부터 반환된 authorizationCode - * @return String 토큰 정보 (access_token, refresh_token, token_type, expires_in, error, error_description) - */ - private String issueNaverAccessToken(String authorizationCode) { - - String NAVER_ACCESS_TOKEN_URL = "https://nid.naver.com/oauth2.0/token"; - String urlString = UriComponentsBuilder.fromHttpUrl(NAVER_ACCESS_TOKEN_URL) - .queryParam("grant_type", "authorization_code") - .queryParam("client_id", NAVER_CLIENT_ID) - .queryParam("client_secret", NAVER_CLIENT_SECRET) - .queryParam("code", authorizationCode) - .queryParam("state", URLEncoder.encode(CSRF_TOKEN, StandardCharsets.UTF_8)) - .build() - .toUriString(); - try { - URL url = new URL(urlString); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - - return extractResponse(con); - } catch (Exception e) { - throw new MemberHandler(ErrorStatus._NAVER_ACCESS_TOKEN_ISSUANCE_FAILED); - } - } - - /** - * 토큰 정보를 파라미터로 받아 네이버 프로필을 조회하는 메서드입니다. - * - * @param naverTokenIssuanceDTO : 토큰 객체 (access_token, refresh_token, token_type, expires_in, error, - * error_description) - * @return String 프로필 정보 - */ - private String getNaverProfile(NaverOAuthToken.NaverTokenIssuanceDTO naverTokenIssuanceDTO) { - - String NAVER_PROFILE_URL = "https://openapi.naver.com/v1/nid/me"; - String accessToken = naverTokenIssuanceDTO.getAccessToken(); - String tokenType = naverTokenIssuanceDTO.getTokenType(); - - try { - URL url = new URL(NAVER_PROFILE_URL); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - con.setRequestProperty("Authorization", tokenType + " " + accessToken); - - return extractResponse(con); - } catch (Exception e) { - throw new MemberHandler(ErrorStatus._UNABLE_TO_RETRIEVE_NAVER_PROFILE); - } - } - - /** - * HTTP 응답을 버퍼를 통해 읽어오는 메서드입니다. - * - * @param con : HttpURLConnection (HTTP 연결 정보) - * @return String Response - */ - private static String extractResponse(HttpURLConnection con) throws IOException { - - int responseCode = con.getResponseCode(); - BufferedReader br; - - if (responseCode == HttpURLConnection.HTTP_OK) { - br = new BufferedReader(new InputStreamReader(con.getInputStream())); - } else { - br = new BufferedReader(new InputStreamReader(con.getErrorStream())); - } - - String line; - StringBuilder response = new StringBuilder(); - while ((line = br.readLine()) != null) { - response.append(line); - } - - br.close(); - return response.toString(); - } - -} diff --git a/src/main/java/com/example/spot/auth/application/refactor/KakaoAuthService.java b/src/main/java/com/example/spot/auth/application/refactor/KakaoAuthService.java deleted file mode 100644 index e255fae8..00000000 --- a/src/main/java/com/example/spot/auth/application/refactor/KakaoAuthService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.spot.auth.application.refactor; - -import java.io.IOException; - -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.fasterxml.jackson.core.JsonProcessingException; - -public interface KakaoAuthService { - - MemberResponseDTO.SocialLoginSignInDTO signUpByKAKAO(String code) throws JsonProcessingException; - - MemberResponseDTO.SocialLoginSignInDTO signUpByKAKAOForTest(String code) - throws JsonProcessingException; - - void redirectURL() throws IOException; - - -} diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/KakaoAuthServiceImpl.java b/src/main/java/com/example/spot/auth/application/refactor/impl/KakaoAuthServiceImpl.java deleted file mode 100644 index 5788b49e..00000000 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/KakaoAuthServiceImpl.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.example.spot.auth.application.refactor.impl; - -import com.example.spot.auth.application.refactor.KakaoAuthService; -import com.example.spot.auth.domain.RefreshToken; -import com.example.spot.auth.domain.RefreshTokenRepository; -import com.example.spot.auth.infrastructure.kakao.KakaoOAuthClient; -import com.example.spot.auth.presentation.dto.kakao.KaKaoOAuthToken; -import com.example.spot.auth.presentation.dto.kakao.KaKaoUser; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.GeneralException; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.member.domain.Member; -import com.example.spot.member.domain.enums.LoginType; -import com.example.spot.member.infrastructure.MemberRepository; -import com.example.spot.member.infrastructure.PreferredRegionRepository; -import com.example.spot.member.infrastructure.PreferredThemeRepository; -import com.example.spot.member.infrastructure.StudyJoinReasonRepository; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.fasterxml.jackson.core.JsonProcessingException; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@Transactional -@RequiredArgsConstructor -public class KakaoAuthServiceImpl implements KakaoAuthService { - - @PersistenceContext - private EntityManager entityManager; - - private final KakaoOAuthClient client; - - private final JwtTokenProvider jwtTokenProvider; - - private final HttpServletResponse response; - - private final MemberRepository memberRepository; - private final RefreshTokenRepository refreshTokenRepository; - private final PreferredThemeRepository preferredThemeRepository; - private final PreferredRegionRepository preferredRegionRepository; - private final StudyJoinReasonRepository studyJoinReasonRepository; - - /** - * 카카오 로그인을 통해 회원 가입 또는 로그인을 수행합니다. - * - * @param accessToken 카카오 OAuth 액세스 토큰 - * @return SPOT 서버에서 발급한 JWT 토큰 및 회원 정보 - * @throws JsonProcessingException 카카오 사용자 정보 파싱 중 발생하는 예외 - */ - @Override - public MemberResponseDTO.SocialLoginSignInDTO signUpByKAKAO(String accessToken) throws JsonProcessingException { - // 액세스 토큰을 사용하여 사용자 정보 요청 - ResponseEntity userInfoResponse = client.requestUserInfo(accessToken); - - // 응답에서 사용자 정보를 파싱 - KaKaoUser kaKaoUser = client.getUserInfo(userInfoResponse); - - if (memberRepository.existsByEmailAndLoginTypeNot(kaKaoUser.toMember().getEmail(), LoginType.KAKAO)) { - log.info(kaKaoUser.toMember().getEmail()); - throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST); - } - - Boolean isSpotMember = false; - // 사용자가 이미 존재하는지 확인 - if (memberRepository.existsByEmail(kaKaoUser.toMember().getEmail())) { - // 존재하는 경우, 사용자 정보를 가져옴 - Member member = memberRepository.findByEmail(kaKaoUser.toMember().getEmail()) - .orElseThrow(() -> new GeneralException(ErrorStatus._MEMBER_NOT_FOUND)); - - updateMemberProfileImage(member, kaKaoUser); - - if (isMemberExistsByCheckList(member)) { - isSpotMember = true; - } - - // JWT 토큰 생성 - TokenResponseDTO.TokenDTO token = jwtTokenProvider.createToken(member.getId()); - - saveRefreshToken(member, token); - - // 로그인 DTO 반환 - MemberResponseDTO.MemberSignInDTO dto = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember, dto); - } - - // 존재하지 않는 경우, 새로운 회원 정보 저장 - Member member = memberRepository.save(kaKaoUser.toMember()); - - // JWT 토큰 생성 - TokenResponseDTO.TokenDTO token = jwtTokenProvider.createToken(member.getId()); - - saveRefreshToken(member, token); - - // 회원 가입 DTO 반환 - MemberResponseDTO.MemberSignInDTO dto = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember, dto); - } - - private void updateMemberProfileImage(Member member, KaKaoUser kaKaoUser) { - if (!member.getProfileImage().equals(kaKaoUser.getProperties().getProfile_image())) { - member.updateProfileImage(kaKaoUser.getProperties().getProfile_image()); - } - } - - /** - * 카카오 로그인을 테스트용으로 수행합니다. 카카오 auth accessToken 발급을 포함한 모든 내부 로직이 구현 되어 있습니다. - * - * @param code 카카오 로그인 요청 시 발급받은 코드 - * @return SPOT 서버에서 발급한 JWT 토큰 및 회원 정보 - * @throws JsonProcessingException 카카오 사용자 정보 파싱 중 발생하는 예외 - * @throws MemberHandler 이메일로 가입된 내역이 존재하지만, 실제로는 회원이 존재하지 않을 경우 - */ - @Override - public MemberResponseDTO.SocialLoginSignInDTO signUpByKAKAOForTest(String code) throws JsonProcessingException { - // 카카오 OAuth 서비스에서 액세스 토큰 요청 - ResponseEntity accessTokenResponse = client.requestAccessToken(code); - - // 응답에서 액세스 토큰을 파싱 - KaKaoOAuthToken.KaKaoOAuthTokenDTO oAuthToken = client.getAccessToken(accessTokenResponse); - System.out.println(oAuthToken.getAccess_token()); - - // 액세스 토큰을 사용하여 사용자 정보 요청 - ResponseEntity userInfoResponse = client.requestUserInfo(oAuthToken.getAccess_token()); - - // 응답에서 사용자 정보를 파싱 - KaKaoUser kaKaoUser = client.getUserInfo(userInfoResponse); - - // 다른 로그인 방식을 사용한 계정이 있는지 확인 - if (memberRepository.existsByEmailAndLoginTypeNot(kaKaoUser.toMember().getEmail(), LoginType.KAKAO)) { - Member member = memberRepository.findByEmail(kaKaoUser.toMember().getEmail()) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 탈퇴한(inactive) 회원이면 기존 정보 삭제 - if (member.getInactive() != null) { - refreshTokenRepository.deleteByMemberId(member.getId()); - memberRepository.deleteById(member.getId()); - entityManager.flush(); - } else { - throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST); - } - } - - // 사용자가 이미 존재하는지 확인 - Boolean isSpotMember = false; - if (memberRepository.existsByEmail(kaKaoUser.toMember().getEmail())) { - // 존재하는 경우, 사용자 정보를 가져옴 - Member member = memberRepository.findByEmail(kaKaoUser.toMember().getEmail()) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - updateMemberProfileImage(member, kaKaoUser); - - // 탈퇴한(inactive) 회원이면 기존 정보 삭제 - if (member.getInactive() != null) { - refreshTokenRepository.deleteByMemberId(member.getId()); - memberRepository.deleteById(member.getId()); - entityManager.flush(); - } else { - // JWT 토큰 생성 - TokenResponseDTO.TokenDTO token = jwtTokenProvider.createToken(member.getId()); - - saveRefreshToken(member, token); - - if (isMemberExistsByCheckList(member)) { - isSpotMember = true; - } - - // 로그인 DTO 반환 - MemberResponseDTO.MemberSignInDTO memberSignInDto = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - - return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember, memberSignInDto); - } - } - - // 존재하지 않는 경우, 새로운 회원 정보 저장 - Member member = memberRepository.save(kaKaoUser.toMember()); - - // JWT 토큰 생성 - TokenResponseDTO.TokenDTO token = jwtTokenProvider.createToken(member.getId()); - - saveRefreshToken(member, token); - - // 회원 가입 DTO 반환 - MemberResponseDTO.MemberSignInDTO dto = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - return MemberResponseDTO.SocialLoginSignInDTO.toDTO(isSpotMember, dto); - } - - - /** - * redirectURL을 반환합니다. - * - * @throws IOException URL 리다이렉트 중 발생하는 예외 - */ - @Override - public void redirectURL() throws IOException { - // 카카오 OAuth 서비스에서 리다이렉트 URL 반환 - response.sendRedirect(client.getOauthRedirectURL()); - } - - /** - * 리프레시 토큰을 DB에 저장합니다. - * - * @param member 리프레시 토큰을 발급한 회원 정보 - * @param token 발급된 토큰 정보 - */ - private void saveRefreshToken(Member member, TokenResponseDTO.TokenDTO token) { - // 기존 리프레시 토큰 삭제 - if (refreshTokenRepository.existsByMemberId(member.getId())) { - refreshTokenRepository.deleteAllByMemberId(member.getId()); - } - - // DB에 저장하기 위한 새로운 리프레시 토큰 객체 생성 - RefreshToken refreshToken = RefreshToken.builder() - .memberId(member.getId()) - .token(token.getRefreshToken()) - .build(); - - // 리프레시 토큰 저장 - refreshTokenRepository.save(refreshToken); - } - - public boolean isMemberExistsByCheckList(Member member) { - Long memberId = member.getId(); - return preferredThemeRepository.existsByMemberId(memberId) && - preferredRegionRepository.existsByMemberId(memberId) && - studyJoinReasonRepository.existsByMemberId(memberId); - } -} - diff --git a/src/main/java/com/example/spot/auth/presentation/controller/refactor/KakaoAuthController.java b/src/main/java/com/example/spot/auth/presentation/controller/refactor/KakaoAuthController.java deleted file mode 100644 index f5346918..00000000 --- a/src/main/java/com/example/spot/auth/presentation/controller/refactor/KakaoAuthController.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.spot.auth.presentation.controller.refactor; - -import java.io.IOException; - -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 com.example.spot.auth.application.refactor.KakaoAuthService; -import com.example.spot.common.api.ApiResponse; -import com.example.spot.common.api.code.status.SuccessStatus; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.fasterxml.jackson.core.JsonProcessingException; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RestController -@RequestMapping("/spot") -@RequiredArgsConstructor -public class KakaoAuthController { - - private final KakaoAuthService kakaoAuthService; - - /* ----------------------------- 카카오 로그인/회원가입 API ------------------------------------- */ - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!테스트 용! [회원 가입 및 로그인] 카카오 로그인 및 회원가입 ", - description = """ - ## [회원 가입 및 로그인] 카카오 로그인의 모든 과정이 구현되어 있습니다. - 가입 테스트를 위해 구현한 테스트 용 카카오 로그인입니다. - 서버 파트 및 테스트를 원하는 분들은 본 API로 회원 가입 및 로그인을 진행하시면 됩니다. - Swagger에서 요청하는 것이 아닌, 브라우저에서 직접 요청해주세요. - ## www.teamspot.site/spot/login/kakao - ## localhost:8080/spot/login/kakao - - 생성된 회원의 액세스 토큰과 Email이 반환 됩니다. """) - @GetMapping("/login/kakao") - public void login() throws IOException { - kakaoAuthService.redirectURL(); - } - - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!서버 용! [회원 가입 및 로그인] 카카오 로그인 및 회원가입 리다이렉트용 API ", - description = """ - ## [회원 가입 및 로그인] 카카오 로그인의 모든 과정이 구현되어 있습니다. - 가입 테스트를 위해 구현한 테스트 용 리다이렉트 URL입니다. - 서버 파트 및 테스트를 원하는 분들은 본 API로 회원 가입 및 로그인을 진행하시면 됩니다. - Swagger에서 요청하는 것이 아닌, 브라우저에서 직접 요청해주세요. - ## www.teamspot.site/spot/login/kakao - ## localhost:8080/spot/login/kakao - - 생성된 회원의 액세스 토큰과 Email이 반환 됩니다. """) - @GetMapping("/members/sign-in/kakao/redirect") - public ApiResponse redirectURL(@RequestParam String code) throws IOException { - MemberResponseDTO.SocialLoginSignInDTO dto = kakaoAuthService.signUpByKAKAOForTest(code); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); - } - - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @Operation(summary = "[회원 가입 및 로그인] 카카오 로그인 및 회원가입. ", - description = """ - ## [회원 가입 및 로그인] 프론트에서 발급 밭은 액세스 토큰을 통해 회원 가입 및 로그인을 진행합니다. - 연동을 위해 구현된 API입니다. 발급 받은 accessToken을 Param에 첨부하여 API를 호출해주세요. - 생성된 회원의 액세스 토큰과 Email이 반환 됩니다. - """) - - @Parameter(name = "accessToken", description = "카카오 액세스 토큰을 입력 해 주세요. ", required = true) - @GetMapping("/members/sign-in/kakao") - public ApiResponse signInByKaKao(@RequestParam String accessToken) throws JsonProcessingException { - MemberResponseDTO.SocialLoginSignInDTO dto = kakaoAuthService.signUpByKAKAO(accessToken); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); - } -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/google/GoogleExampleResponse.java b/src/main/java/com/example/spot/auth/presentation/dto/google/GoogleExampleResponse.java deleted file mode 100644 index 695c85ca..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/google/GoogleExampleResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.spot.auth.presentation.dto.google; - -public class GoogleExampleResponse { - - public static final String EXAMPLE_RESPONSE = """ - { - "isSuccess": true, - "code": "COMMON200", - "message": "OK", - "result": { - "tokens": { - "accessToken": "eyABCDEFG.eyJtZW1i12341234123xNzMxNzU4MTAxLCJleHAiOjE3MzE3NjE3MDF9.ZplY8yGgO24234FQj0hPB6uY", - "refreshToken": "eyABCDEFG.eyJtZW123412341234312hdCI6MTczMTc1ODEwMSwiZXhwIjoxNzMxODQ0NTAxfQ.FGvZ5nL2342342yJ0I7LX-rac", - "accessTokenExpiresIn": 3600000 - }, - "email": "example@gmail.com", - "memberId": 1 - } - } - """; -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoOAuthToken.java b/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoOAuthToken.java deleted file mode 100644 index e8624f78..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoOAuthToken.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.spot.auth.presentation.dto.kakao; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -public class KaKaoOAuthToken { - - @Getter - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class KaKaoOAuthTokenDTO { - private String token_type; - private String access_token; - private String refresh_token; - private String expires_in; - private String refresh_token_expires_in; - } -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoUser.java b/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoUser.java deleted file mode 100644 index a0915b2d..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/kakao/KaKaoUser.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.example.spot.auth.presentation.dto.kakao; - -import com.example.spot.member.domain.Member; -import com.example.spot.member.domain.enums.Carrier; -import com.example.spot.member.domain.enums.LoginType; -import com.example.spot.common.security.utils.MemberUtils; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import java.time.LocalDate; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@JsonIgnoreProperties(ignoreUnknown = true) -@AllArgsConstructor -@NoArgsConstructor -public class KaKaoUser { - Long id; - String connected_at; - KaKaoPropertiesDTO properties; - KaKaoAccountDTO kakao_account; - - @Getter - @Setter - @JsonIgnoreProperties(ignoreUnknown = true) - @AllArgsConstructor - @NoArgsConstructor - public static class KaKaoAccountDTO { - Boolean has_email; - Boolean email_needs_agreement; - Boolean is_email_valid; - Boolean is_email_verified; - String email; - } - - @Getter - @Setter - @JsonIgnoreProperties(ignoreUnknown = true) - @AllArgsConstructor - @NoArgsConstructor - public static class KaKaoPropertiesDTO{ - String nickname; - String profile_image; - String thumbnail_image; - } - - public Member toMember(){ - return Member.builder() - .name(properties.getNickname()) - .nickname(properties.getNickname()) - .email(kakao_account.getEmail()) - .profileImage(properties.getProfile_image()) - .carrier(Carrier.NONE) - .password("default") - .phone(MemberUtils.generatePhoneNumber()) - .birth(LocalDate.now()) - .personalInfo(false) - .idInfo(false) - .isAdmin(false) - .loginType(LoginType.KAKAO) - .build(); - } - -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverCallback.java b/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverCallback.java deleted file mode 100644 index d2492c60..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverCallback.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.spot.auth.presentation.dto.naver; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; - -@Getter -public class NaverCallback { - - private final String code; - private final String state; - private final String error; - private final String errorDescription; - - @JsonCreator - public NaverCallback( - @JsonProperty("code") String code, - @JsonProperty("state") String state, - @JsonProperty("error") String error, - @JsonProperty("error_description") String errorDescription) { - this.code = code; - this.state = state; - this.error = error; - this.errorDescription = errorDescription; - } -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverMember.java b/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverMember.java deleted file mode 100644 index 9461d921..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverMember.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.example.spot.auth.presentation.dto.naver; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -public class NaverMember { - - @Getter - @RequiredArgsConstructor - public static class RequestDTO { - private final String id; - private final String name; - private final String email; - private final String profileImage; - private final String gender; - private final String birthDay; - private final String birthYear; - } - - @Getter - public static class ResponseDTO { - - private final String resultCode; - private final String message; - private final ProfileResponse response; - - @JsonCreator - public ResponseDTO( - @JsonProperty("resultcode") String resultCode, - @JsonProperty("message") String message, - @JsonProperty("response") ProfileResponse response) { - this.resultCode = resultCode; - this.message = message; - this.response = response; - } - } - - @Getter - public static class ProfileResponse { - - private final String id; - private final String name; - private final String nickname; - private final String email; - private final String profileImage; - private final String gender; - private final String birthDay; - private final String birthYear; - - @JsonCreator - public ProfileResponse( - @JsonProperty("id") String id, - @JsonProperty("name") String name, - @JsonProperty("nickname") String nickname, - @JsonProperty("email") String email, - @JsonProperty("profile_image") String profileImage, - @JsonProperty("gender") String gender, - @JsonProperty("birthday") String birthDay, - @JsonProperty("birthyear") String birthYear) { - this.id = id; - this.name = name; - this.nickname = nickname; - this.email = email; - this.profileImage = profileImage; - this.gender = gender; - this.birthDay = birthDay; - this.birthYear = birthYear; - } - } - - -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverOAuthToken.java b/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverOAuthToken.java deleted file mode 100644 index e8d5ec7f..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/naver/NaverOAuthToken.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.example.spot.auth.presentation.dto.naver; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; - -@Getter -public class NaverOAuthToken { - - @Getter - @JsonIgnoreProperties(ignoreUnknown = true) - public static class NaverTokenIssuanceDTO { - - @Schema(name = "access_token", description = "Access token") - private final String accessToken; - - @Schema(name = "refresh_token", description = "Refresh token") - private final String refreshToken; - - @Schema(name = "token_type", description = "Token type") - private final String tokenType; - - @Schema(name = "expires_in", description = "Expiration time in seconds") - private final Integer expiresIn; - - @Schema(name = "error", description = "Error code") - private final String error; - - @Schema(name = "error_description", description = "Error description") - private final String errorDescription; - - @JsonCreator - public NaverTokenIssuanceDTO( - @JsonProperty("access_token") String accessToken, - @JsonProperty("refresh_token") String refreshToken, - @JsonProperty("token_type") String tokenType, - @JsonProperty("expires_in") Integer expiresIn, - @JsonProperty("error") String error, - @JsonProperty("error_description") String errorDescription) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.tokenType = tokenType; - this.expiresIn = expiresIn; - this.error = error; - this.errorDescription = errorDescription; - } - } - - @Getter - public static class NaverTokenRenewalDTO { - - private final String accessToken; - private final String tokenType; - private final Integer expiresIn; - private final String error; - private final String errorDescription; - - public NaverTokenRenewalDTO( - @JsonProperty("access_token") String accessToken, - @JsonProperty("token_type") String tokenType, - @JsonProperty("expires_in") Integer expiresIn, - @JsonProperty("error") String error, - @JsonProperty("error_description") String errorDescription) { - this.accessToken = accessToken; - this.tokenType = tokenType; - this.expiresIn = expiresIn; - this.error = error; - this.errorDescription = errorDescription; - } - - } - - @Getter - public static class NaverTokenDeleteDTO { - - private final String accessToken; - private final String result; - private final Integer expiresIn; - private final String error; - private final String errorDescription; - - public NaverTokenDeleteDTO( - @JsonProperty("access_token") String accessToken, - @JsonProperty("result") String result, - @JsonProperty("expires_in") Integer expiresIn, - @JsonProperty("error") String error, - @JsonProperty("error_description") String errorDescription) { - this.accessToken = accessToken; - this.result = result; - this.expiresIn = expiresIn; - this.error = error; - this.errorDescription = errorDescription; - } - } - -} From aaae67b3122341e0f3c1155f1c325b80dd84df71 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:28:39 +0900 Subject: [PATCH 18/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20DTO=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/refactor/impl/oauth/GoogleOauth.java | 4 ++-- .../application/refactor/impl/oauth/KaKaoOauth.java | 4 ++-- .../refactor/impl/straegy/GoogleOAuthStrategy.java | 4 ++-- .../refactor/impl/straegy/KakaoOAuthStrategy.java | 4 ++-- .../infrastructure/client/google/GoogleApiClient.java | 2 +- .../client/google/GoogleAuthClient.java | 2 +- .../infrastructure/client/kakao/KaKaoApiClient.java | 2 +- .../infrastructure/client/kakao/KaKaoAuthClient.java | 2 +- .../spot/auth/presentation/dto/GoogleOAuthToken.java | 11 ----------- .../dto/oauth/google/GoogleOAuthToken.java | 10 ++++++++++ .../dto/{ => oauth/google}/GoogleUser.java | 2 +- .../dto/{ => oauth/kakao}/KaKaoOAuthTokenDTO.java | 2 +- .../presentation/dto/{ => oauth/kakao}/KaKaoUser.java | 2 +- 13 files changed, 25 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleOAuthToken.java rename src/main/java/com/example/spot/auth/presentation/dto/{ => oauth/google}/GoogleUser.java (78%) rename src/main/java/com/example/spot/auth/presentation/dto/{ => oauth/kakao}/KaKaoOAuthTokenDTO.java (76%) rename src/main/java/com/example/spot/auth/presentation/dto/{ => oauth/kakao}/KaKaoUser.java (89%) diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java index 732dc23e..509b4546 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java @@ -14,8 +14,8 @@ import com.example.spot.auth.infrastructure.client.google.GoogleApiClient; import com.example.spot.auth.infrastructure.client.google.GoogleAuthClient; -import com.example.spot.auth.presentation.dto.GoogleOAuthToken; -import com.example.spot.auth.presentation.dto.GoogleUser; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleOAuthToken; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleUser; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java index 0866e8f8..e177d47b 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java @@ -13,8 +13,8 @@ import com.example.spot.auth.infrastructure.client.kakao.KaKaoApiClient; import com.example.spot.auth.infrastructure.client.kakao.KaKaoAuthClient; import com.example.spot.auth.infrastructure.constants.JwtConstants; -import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; -import com.example.spot.auth.presentation.dto.KaKaoUser; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoUser; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java index c34fd020..e7a7fd82 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java @@ -2,8 +2,8 @@ import com.example.spot.auth.application.refactor.OAuthStrategy; import com.example.spot.auth.application.refactor.impl.oauth.GoogleOauth; -import com.example.spot.auth.presentation.dto.GoogleOAuthToken; -import com.example.spot.auth.presentation.dto.GoogleUser; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleOAuthToken; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleUser; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java index 5e93b242..a92bb93c 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java @@ -2,8 +2,8 @@ import com.example.spot.auth.application.refactor.OAuthStrategy; import com.example.spot.auth.application.refactor.impl.oauth.KaKaoOauth; -import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; -import com.example.spot.auth.presentation.dto.KaKaoUser; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoUser; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java index a019ba0f..0d428d8a 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleApiClient.java @@ -2,7 +2,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_AUTHORIZATION; -import com.example.spot.auth.presentation.dto.GoogleUser; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleUser; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java index f0ae76a5..befe1e33 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/google/GoogleAuthClient.java @@ -2,7 +2,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; -import com.example.spot.auth.presentation.dto.GoogleOAuthToken; +import com.example.spot.auth.presentation.dto.oauth.google.GoogleOAuthToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java index 69f082f2..ee1268b3 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoApiClient.java @@ -3,7 +3,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_AUTHORIZATION; import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; -import com.example.spot.auth.presentation.dto.KaKaoUser; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoUser; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java index 3a43f794..05ae8637 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java @@ -2,7 +2,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; -import com.example.spot.auth.presentation.dto.KaKaoOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoOAuthTokenDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; diff --git a/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java b/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java deleted file mode 100644 index f3e16009..00000000 --- a/src/main/java/com/example/spot/auth/presentation/dto/GoogleOAuthToken.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.spot.auth.presentation.dto; - -// 구글에 일회성 코드를 다시 보내 받아올 액세스 토큰을 포함한 JSON 문자열을 담을 클래스 -public record GoogleOAuthToken( - String access_token, - int expires_in, - String scope, - String token_type, - String id_token -) { -} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleOAuthToken.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleOAuthToken.java new file mode 100644 index 00000000..8f225f00 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleOAuthToken.java @@ -0,0 +1,10 @@ +package com.example.spot.auth.presentation.dto.oauth.google; + +public record GoogleOAuthToken( + String access_token, + int expires_in, + String scope, + String token_type, + String id_token +) { +} diff --git a/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleUser.java similarity index 78% rename from src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java rename to src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleUser.java index f4c1c02f..4760962b 100644 --- a/src/main/java/com/example/spot/auth/presentation/dto/GoogleUser.java +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/google/GoogleUser.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.presentation.dto; +package com.example.spot.auth.presentation.dto.oauth.google; public record GoogleUser( String id, diff --git a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoOAuthTokenDTO.java similarity index 76% rename from src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java rename to src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoOAuthTokenDTO.java index 98a324c9..d4908f81 100644 --- a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoOAuthTokenDTO.java +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoOAuthTokenDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.presentation.dto; +package com.example.spot.auth.presentation.dto.oauth.kakao; public record KaKaoOAuthTokenDTO( String token_type, diff --git a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoUser.java similarity index 89% rename from src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java rename to src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoUser.java index 98ca7c29..799ad190 100644 --- a/src/main/java/com/example/spot/auth/presentation/dto/KaKaoUser.java +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/kakao/KaKaoUser.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.presentation.dto; +package com.example.spot.auth.presentation.dto.oauth.kakao; public record KaKaoUser( Long id, From e84c6eb15d3784daacf303f378facae42f2b0a51 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:29:05 +0900 Subject: [PATCH 19/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B2=84=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20=EA=B8=B0=EC=A1=B4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/legacy/AuthService.java | 14 +- .../application/legacy/AuthServiceImpl.java | 178 +---------- .../controller/legacy/AuthController.java | 277 +++++++++--------- 3 files changed, 149 insertions(+), 320 deletions(-) diff --git a/src/main/java/com/example/spot/auth/application/legacy/AuthService.java b/src/main/java/com/example/spot/auth/application/legacy/AuthService.java index bd1313e6..cbaa878c 100644 --- a/src/main/java/com/example/spot/auth/application/legacy/AuthService.java +++ b/src/main/java/com/example/spot/auth/application/legacy/AuthService.java @@ -1,14 +1,10 @@ package com.example.spot.auth.application.legacy; -import com.example.spot.member.presentation.dto.MemberRequestDTO.SignUpDetailDTO; import com.example.spot.auth.presentation.dto.rsa.Rsa; +import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; import com.example.spot.member.presentation.dto.MemberRequestDTO; +import com.example.spot.member.presentation.dto.MemberRequestDTO.SignUpDetailDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; -import com.example.spot.auth.presentation.dto.naver.NaverCallback; -import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -19,12 +15,6 @@ public interface AuthService { MemberResponseDTO.InactiveMemberDTO withdraw(); - void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response); - - SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception; - - SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception; - MemberResponseDTO.MemberSignInDTO signIn(Long httpSession, MemberRequestDTO.SignInDTO signInDTO) throws Exception; Rsa.RSAPublicKey getRSAPublicKey() throws Exception; diff --git a/src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java b/src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java index 03eab3b2..7bc55423 100644 --- a/src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java +++ b/src/main/java/com/example/spot/auth/application/legacy/AuthServiceImpl.java @@ -7,9 +7,6 @@ import com.example.spot.auth.domain.VerificationCode; import com.example.spot.auth.domain.rsa.RSAKeyRepository; import com.example.spot.auth.domain.verification.VerificationCodeRepository; -import com.example.spot.auth.presentation.dto.naver.NaverCallback; -import com.example.spot.auth.presentation.dto.naver.NaverMember; -import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken; import com.example.spot.auth.presentation.dto.rsa.Rsa; import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO; @@ -18,7 +15,6 @@ import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.application.message.MailService; import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.common.security.utils.MemberUtils; import com.example.spot.common.security.utils.RSAUtils; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.member.domain.Member; @@ -35,7 +31,6 @@ import com.example.spot.member.presentation.dto.MemberResponseDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO.CheckMemberDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO.NicknameDuplicateDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; import com.example.spot.study.domain.repository.StudyMemberRepository; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -76,7 +71,6 @@ public class AuthServiceImpl implements AuthService { private final StudyJoinReasonRepository studyJoinReasonRepository; private final MailService mailService; - private final NaverOAuthService naverOAuthService; private final RSAUtils rsaUtils; private final RSAKeyRepository rsaKeyRepository; @@ -127,171 +121,6 @@ public MemberResponseDTO.InactiveMemberDTO withdraw() { return MemberResponseDTO.InactiveMemberDTO.toDTO(member); } - /* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ - - /** - * 네이버 로그인 인증 요청 URL로 실제 요청을 전송하고 로그인 페이지로 리디렉션하는 메서드입니다. - * - * @param request : HTTPServletRequest - * @param response : HttpServletResponse - */ - @Override - public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response) { - String url = naverOAuthService.getNaverAuthorizeUrl(); - try { - response.sendRedirect(url); - } catch (Exception e) { - throw new MemberHandler(ErrorStatus._NAVER_SIGN_IN_INTEGRATION_FAILED); - } - } - - /** - * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 로그인 Callback 성공시 반환되는 naverCallback을 바탕으로 액세스 토큰을 발급받고 프로필에 접근합니다. 현재 - * SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. - * - * @param request : HttpServletRequest - * @param response : HttpServletResponse - * @param naverCallback : Callback 함수 성공시 반환되는 요소(code, state, error, error_description) - * @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보) - */ - @Override - public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, - NaverCallback naverCallback) throws Exception { - NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverCallback); - return getSocialLoginSignInDTO(responseDTO); - } - - /** - * SPOT 서비스에 네이버를 통해 로그인과 회원가입을 수행하는 함수입니다. 클라이언트로부터 전달받은 액세스 토큰을 통해 프로필에 접근합니다. 현재 SPOT에 가입되지 않은 회원이라면, 반환된 프로필 정보를 - * 기반으로 회원 정보를 생성하여 DB에 저장합니다. 현재 SPOT에 가입되어 있는 회원이라면, 소셜로그인 후 토큰 정보를 반환합니다. - * - * @param request : HttpServletRequest - * @param response : HttpServletResponse - * @return SocialLoginSignInDTO(isSpotMember, signInDTO - 토큰정보) - */ - @Override - public SocialLoginSignInDTO signInWithNaver(HttpServletRequest request, HttpServletResponse response, - NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO) throws Exception { - NaverMember.ResponseDTO responseDTO = naverOAuthService.getNaverMember(request, response, naverTokenDTO); - return getSocialLoginSignInDTO(responseDTO); - } - - /** - * 네이버 회원 프로필을 통해 SocialLoginSignInDTO를 생성하는 함수입니다. - * - * @param responseDTO : 네이버 회원 프로필 DTO - * @return SocialLoginSignInDTO (SPOT 회원 정보 및 토큰 정보) - */ - private SocialLoginSignInDTO getSocialLoginSignInDTO(NaverMember.ResponseDTO responseDTO) { - String email = responseDTO.getResponse().getEmail(); - - // 다른 로그인 방식을 사용한 계정이 있는지 확인 - if (memberRepository.existsByEmailAndLoginTypeNot(email, LoginType.NAVER)) { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 탈퇴한(inactive) 회원이면 기존 정보 삭제 - if (member.getInactive() != null) { - refreshTokenRepository.deleteByMemberId(member.getId()); - memberRepository.deleteById(member.getId()); - entityManager.flush(); - } else { - throw new MemberHandler(ErrorStatus._MEMBER_EMAIL_ALREADY_EXISTS); - } - } - - // 네이버 로그인 계정이 있는지 확인 - Boolean isSpotMember = Boolean.TRUE; - if (memberRepository.existsByEmailAndLoginType(email, LoginType.NAVER)) { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 탈퇴한(inactive) 회원이면 기존 정보 삭제 후 회원 정보 저장 - if (member.getInactive() != null) { - refreshTokenRepository.deleteByMemberId(member.getId()); - memberRepository.deleteById(member.getId()); - entityManager.flush(); - isSpotMember = Boolean.FALSE; - signUpWithNaver(responseDTO); - } - } else { - isSpotMember = Boolean.FALSE; - signUpWithNaver(responseDTO); - } - - Member member = memberRepository.findByEmailAndLoginType(email, LoginType.NAVER) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - if (!isMemberExistsByCheckList(member)) { - isSpotMember = Boolean.FALSE; - } - - // 로그인을 위한 토큰 발급 - TokenDTO token = jwtTokenProvider.createToken(member.getId()); - saveRefreshToken(member, token); - - MemberResponseDTO.MemberSignInDTO signInDTO = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - - return SocialLoginSignInDTO.toDTO(isSpotMember, signInDTO); - } - - public boolean isMemberExistsByCheckList(Member member) { - Long memberId = member.getId(); - return preferredThemeRepository.existsByMemberId(memberId) && - preferredRegionRepository.existsByMemberId(memberId) && - studyJoinReasonRepository.existsByMemberId(memberId); - } - - /** - * 현재 SPOT에 가입되어 있지 않은 회원에 한해 회원 정보를 생성하여 DB에 저장합니다. - * - * @param memberDTO : naverCallback을 바탕으로 생성된 프로필 객체 - */ - private void signUpWithNaver(NaverMember.ResponseDTO memberDTO) { - String birthYear = memberDTO.getResponse().getBirthYear(); - String birthDay = memberDTO.getResponse().getBirthDay(); - - LocalDate birth = null; - if (birthYear != null && birthDay != null) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - birth = LocalDate.parse(birthYear + "-" + birthDay, formatter); - } - - Gender gender; - if (memberDTO.getResponse().getGender().equals("F")) { - gender = Gender.FEMALE; - } else if (memberDTO.getResponse().getGender().equals("M")) { - gender = Gender.MALE; - } else { - gender = Gender.UNKNOWN; - } - - Member member = Member.builder() - .name(memberDTO.getResponse().getName()) - .nickname(memberDTO.getResponse().getNickname()) - .birth(birth) - .gender(gender) - .email(memberDTO.getResponse().getEmail()) - .carrier(Carrier.NONE) - .phone(MemberUtils.generatePhoneNumber()) - .loginId(memberDTO.getResponse().getEmail()) - .password("") - .profileImage(memberDTO.getResponse().getProfileImage()) - .personalInfo(false) - .idInfo(false) - .isAdmin(Boolean.FALSE) - .loginType(LoginType.NAVER) - .status(Status.ON) - .build(); - - memberRepository.save(member); - } - /* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ /** @@ -638,6 +467,13 @@ public NicknameDuplicateDTO checkNicknameAvailability(String nickname) { return new NicknameDuplicateDTO(nickname, isDuplicate); } + public boolean isMemberExistsByCheckList(Member member) { + Long memberId = member.getId(); + return preferredThemeRepository.existsByMemberId(memberId) && + preferredRegionRepository.existsByMemberId(memberId) && + studyJoinReasonRepository.existsByMemberId(memberId); + } + /** * 임시 비밀번호를 발급하는 메서드입니다. 알파벳 대소문자, 숫자, 특수기호를 혼합하여 13자리 비밀번호를 생성합니다. * diff --git a/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java b/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java index 0b87db55..14c5faed 100644 --- a/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java +++ b/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java @@ -1,20 +1,16 @@ package com.example.spot.auth.presentation.controller.legacy; +import com.example.spot.auth.application.legacy.AuthService; +import com.example.spot.auth.presentation.dto.rsa.Rsa; +import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; +import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO; import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; -import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.member.presentation.dto.MemberResponseDTO.NicknameDuplicateDTO; -import com.example.spot.auth.presentation.dto.rsa.Rsa; -import com.example.spot.auth.application.legacy.AuthService; import com.example.spot.common.presentation.validator.TextLength; +import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; -import com.example.spot.auth.presentation.dto.naver.NaverCallback; -import com.example.spot.auth.presentation.dto.naver.NaverOAuthToken; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO; - +import com.example.spot.member.presentation.dto.MemberResponseDTO.NicknameDuplicateDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -22,8 +18,13 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; @Slf4j @@ -37,18 +38,18 @@ public class AuthController { private final AuthService authService; -/* ----------------------------- 공통 회원 관리 API ------------------------------------- */ + /* ----------------------------- 공통 회원 관리 API ------------------------------------- */ // 닉네임 중복 확인 @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[공통 회원 관리] 닉네임 중복 확인 API", description = """ - ## [공통 회원 관리] 닉네임 중복 확인 API입니다. - * Request Params : String nickname - * Response Body : Boolean isAvailable - - 중복된 이메일이 존재하면 duplicate 필드가 true로 설정됩니다. - """) + ## [공통 회원 관리] 닉네임 중복 확인 API입니다. + * Request Params : String nickname + * Response Body : Boolean isAvailable + + 중복된 이메일이 존재하면 duplicate 필드가 true로 설정됩니다. + """) @GetMapping("/check/nickname") public ApiResponse checkNicknameAvailability( @RequestParam @TextLength(max = 8) String nickname) { @@ -59,94 +60,95 @@ public ApiResponse checkNicknameAvailability( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[공통 회원 관리] 닉네임 생성 및 약관 동의 API", description = """ - ## [공통 회원 관리] 소셜 회원가입 혹은 일반 회원가입 이후 닉네임 및 약관 동의사항을 업데이트하는 API입니다. - * Authorization 헤더에 액세스 토큰을 포함해야 합니다. - * Request Params : String nickname, Boolean personalInfo, Boolean idInfo - * Response Body : Long memberId, LocalDateTime updatedAt - """) + ## [공통 회원 관리] 소셜 회원가입 혹은 일반 회원가입 이후 닉네임 및 약관 동의사항을 업데이트하는 API입니다. + * Authorization 헤더에 액세스 토큰을 포함해야 합니다. + * Request Params : String nickname, Boolean personalInfo, Boolean idInfo + * Response Body : Long memberId, LocalDateTime updatedAt + """) @PostMapping("/sign-up/update") public ApiResponse signUpAndPartialUpdate( @RequestBody MemberRequestDTO.SignUpDetailDTO signUpDetailDTO) { - MemberResponseDTO.MemberInfoCreationDTO memberInfoCreationDTO = authService.signUpAndPartialUpdate(signUpDetailDTO); + MemberResponseDTO.MemberInfoCreationDTO memberInfoCreationDTO = authService.signUpAndPartialUpdate( + signUpDetailDTO); return ApiResponse.onSuccess(SuccessStatus._MEMBER_UPDATED, memberInfoCreationDTO); } @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[공통 회원 관리] 회원탈퇴 API", description = """ - ## [공통 회원 관리] 로그인한 회원이 SPOT을 탈퇴할 때 사용되는 API입니다. - * Authorization 헤더에 액세스 토큰을 포함해야 합니다. - * 회원탈퇴 시 해당 회원의 inactive(LocalDateTime) 필드가 활성화 됩니다. - * 회원 정보 및 회원의 스터디 정보는 30일간 DB에 저장되며 30일이 지나면 자동으로 삭제됩니다. - * 30일 이후 정보 삭제 시 "로그인 이메일", "성명", "생년월일 정보", "진행중 스터디", \ - "모집중 스터디", "스터디 찜 정보", "게시글", "댓글", "사진", "관심사", "관심지역"이 삭제됩니다. - """) + ## [공통 회원 관리] 로그인한 회원이 SPOT을 탈퇴할 때 사용되는 API입니다. + * Authorization 헤더에 액세스 토큰을 포함해야 합니다. + * 회원탈퇴 시 해당 회원의 inactive(LocalDateTime) 필드가 활성화 됩니다. + * 회원 정보 및 회원의 스터디 정보는 30일간 DB에 저장되며 30일이 지나면 자동으로 삭제됩니다. + * 30일 이후 정보 삭제 시 "로그인 이메일", "성명", "생년월일 정보", "진행중 스터디", \ + "모집중 스터디", "스터디 찜 정보", "게시글", "댓글", "사진", "관심사", "관심지역"이 삭제됩니다. + """) @PatchMapping("/withdraw") public ApiResponse withdraw() { MemberResponseDTO.InactiveMemberDTO inactiveMemberDTO = authService.withdraw(); return ApiResponse.onSuccess(SuccessStatus._MEMBER_DELETED, inactiveMemberDTO); } -/* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ - - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!서버용! [네이버 로그인] 테스트용 인증코드 발급 API", - description = """ - ## [네이버 로그인] 네이버 액세스 토큰 발급에 필요한 인증코드를 발급 받는 API입니다. - * API를 호출하면 네이버 로그인 페이지로 리디렉션됩니다. - * 로그인을 완료하면 <네이버 회원 조회> 콜백 함수가 실행됩니다. - * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 - * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 - ** 테스트 시 API URL을 복사하여 웹 브라우저에서 실행해주세요!!** - """) - @GetMapping("/members/sign-in/naver/authorize/test") - public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response) { - authService.authorizeWithNaver(request, response); - } - - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!서버용! [네이버 로그인] 테스트용 네이버 로그인/회원가입 API", - description = """ - ## [네이버 로그인] 네이버 액세스 토큰을 발급하여 로그인/회원가입을 수행하는 콜백 함수입니다 - ### (직접 호출하는 API가 아닙니다) - * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 - * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 - * 콜백 함수의 결과로 토큰 정보가 반환됩니다. - """) - @GetMapping("/members/sign-in/naver/redirect") - public ApiResponse signInWithNaver( - HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception { - SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverCallback); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); - } + /* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ - @Tag(name = "네이버 로그인 API - 개발 완료", description = "네이버 로그인 API") - @Operation(summary = "[네이버 로그인] 네이버 로그인/회원가입 API", - description = """ - ## [네이버 로그인] 클라이언트로부터 네이버 액세스 토큰을 받아 로그인/회원가입을 수행하는 함수입니다 - * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 - * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 - """) - @PostMapping(value = "/members/sign-in/naver", produces = MediaType.APPLICATION_JSON_VALUE) - public ApiResponse signInWithNaver( - HttpServletRequest request, - HttpServletResponse response, - @RequestBody NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO - ) throws Exception { - SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverTokenDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); - } +// @Tag(name = "테스트 용 API", description = "테스트 용 API") +// @Operation(summary = "!서버용! [네이버 로그인] 테스트용 인증코드 발급 API", +// description = """ +// ## [네이버 로그인] 네이버 액세스 토큰 발급에 필요한 인증코드를 발급 받는 API입니다. +// * API를 호출하면 네이버 로그인 페이지로 리디렉션됩니다. +// * 로그인을 완료하면 <네이버 회원 조회> 콜백 함수가 실행됩니다. +// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 +// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 +// ** 테스트 시 API URL을 복사하여 웹 브라우저에서 실행해주세요!!** +// """) +// @GetMapping("/members/sign-in/naver/authorize/test") +// public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response) { +// authService.authorizeWithNaver(request, response); +// } +// +// @Tag(name = "테스트 용 API", description = "테스트 용 API") +// @Operation(summary = "!서버용! [네이버 로그인] 테스트용 네이버 로그인/회원가입 API", +// description = """ +// ## [네이버 로그인] 네이버 액세스 토큰을 발급하여 로그인/회원가입을 수행하는 콜백 함수입니다 +// ### (직접 호출하는 API가 아닙니다) +// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 +// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 +// * 콜백 함수의 결과로 토큰 정보가 반환됩니다. +// """) +// @GetMapping("/members/sign-in/naver/redirect") +// public ApiResponse signInWithNaver( +// HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception { +// SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverCallback); +// return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); +// } +// +// @Tag(name = "네이버 로그인 API - 개발 완료", description = "네이버 로그인 API") +// @Operation(summary = "[네이버 로그인] 네이버 로그인/회원가입 API", +// description = """ +// ## [네이버 로그인] 클라이언트로부터 네이버 액세스 토큰을 받아 로그인/회원가입을 수행하는 함수입니다 +// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 +// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 +// """) +// @PostMapping(value = "/members/sign-in/naver", produces = MediaType.APPLICATION_JSON_VALUE) +// public ApiResponse signInWithNaver( +// HttpServletRequest request, +// HttpServletResponse response, +// @RequestBody NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO +// ) throws Exception { +// SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverTokenDTO); +// return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); +// } -/* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ + /* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[인증메일] 인증번호 전송 API", description = """ - ## [인증메일] 인증번호 전송 API입니다. - * 입력받은 이메일로 인증번호가 전송됩니다. - """) + ## [인증메일] 인증번호 전송 API입니다. + * 입력받은 이메일로 인증번호가 전송됩니다. + """) @PostMapping("/send-verification-code") public ApiResponse sendVerificationCode( HttpServletRequest request, HttpServletResponse response, @@ -158,10 +160,10 @@ public ApiResponse sendVerificationCode( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[인증메일] 이메일 인증 API", description = """ - ## [인증메일] 이메일로 전송된 인증코드를 확인하는 API입니다. - * 사용자로부터 인증코드와 이메일을 받아 검증 작업을 수행한 후, 임시 토큰을 반환합니다. - * 임시 토큰은 최대 5분간 유효합니다. 임시 토큰이 만료된 경우 이메일 재인증이 필요합니다. - """) + ## [인증메일] 이메일로 전송된 인증코드를 확인하는 API입니다. + * 사용자로부터 인증코드와 이메일을 받아 검증 작업을 수행한 후, 임시 토큰을 반환합니다. + * 임시 토큰은 최대 5분간 유효합니다. 임시 토큰이 만료된 경우 이메일 재인증이 필요합니다. + """) @PostMapping("/verify") public ApiResponse verifyEmail( @RequestParam String verificationCode, @@ -173,9 +175,9 @@ public ApiResponse verifyEmail( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[회원 가입] 아이디 사용 가능 여부 확인 API", description = """ - ## [회원 가입] 아이디 사용 가능 여부 확인 API입니다. - * 로그인 아이디를 입력 받아 아이디의 사용 가능 여부를 반환합니다. - """) + ## [회원 가입] 아이디 사용 가능 여부 확인 API입니다. + * 로그인 아이디를 입력 받아 아이디의 사용 가능 여부를 반환합니다. + """) @GetMapping("/check/login-id") public ApiResponse checkLoginIdAvailability( @RequestParam String loginId) { @@ -186,9 +188,9 @@ public ApiResponse checkLoginIdAvailability( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[회원 가입] 이메일 사용 가능 여부 확인 API", description = """ - ## [회원 가입] 이메일 사용 가능 여부 확인 API입니다. - * 이메일을 입력 받아 이메일의 사용 가능 여부를 반환합니다. - """) + ## [회원 가입] 이메일 사용 가능 여부 확인 API입니다. + * 이메일을 입력 받아 이메일의 사용 가능 여부를 반환합니다. + """) @GetMapping("/check/email") public ApiResponse checkEmailAvailability( @RequestParam String email) { @@ -199,15 +201,15 @@ public ApiResponse checkEmailAvailability( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[회원 가입] 일반 회원 가입 API", - description = """ - ## [회원 가입] 일반 회원 가입 API입니다. - * 아이디(이메일)과 비밀번호 등을 포함하여 회원 가입을 진행합니다. - * 주민번호 앞자리(frontRID)와 뒷자리(backRID)는 모두 String 타입입니다. - * <비밀번호>와 <비밀번호 확인>은 반드시 RSA Public Key로 암호화하여 전송해야 합니다. - * 회원 가입에 성공하면, 액세스 토큰과 리프레시 토큰이 발급됩니다. - * 액세스 토큰은 사용자의 정보를 인증하는데 사용되며, 리프레시 토큰은 액세스 토큰이 만료된 경우, 액세스 토큰을 재발급 하는데 사용됩니다. - * 액세스 토큰이 만료된 경우, 유효한 상태의 리프레시 토큰을 통해 액세스 토큰을 재발급 받을 수 있습니다. - """) + description = """ + ## [회원 가입] 일반 회원 가입 API입니다. + * 아이디(이메일)과 비밀번호 등을 포함하여 회원 가입을 진행합니다. + * 주민번호 앞자리(frontRID)와 뒷자리(backRID)는 모두 String 타입입니다. + * <비밀번호>와 <비밀번호 확인>은 반드시 RSA Public Key로 암호화하여 전송해야 합니다. + * 회원 가입에 성공하면, 액세스 토큰과 리프레시 토큰이 발급됩니다. + * 액세스 토큰은 사용자의 정보를 인증하는데 사용되며, 리프레시 토큰은 액세스 토큰이 만료된 경우, 액세스 토큰을 재발급 하는데 사용됩니다. + * 액세스 토큰이 만료된 경우, 유효한 상태의 리프레시 토큰을 통해 액세스 토큰을 재발급 받을 수 있습니다. + """) @PostMapping("/sign-up") public ApiResponse signUp( @RequestParam Long rsaId, @@ -219,11 +221,11 @@ public ApiResponse signUp( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[아이디 찾기] 아이디 찾기 API", description = """ - ## [아이디 찾기] 이메일 인증을 통해 계정 정보를 불러옵니다. - * 인증번호 전송 API > 이메일 인증 API 호출 후 해당 API를 호출해야 합니다. - * 이메일 인증 API로부터 발급 받은 임시 토큰이 Authorization 헤더에 포함되어야 합니다. - * 임시 토큰을 발급 받은 이메일로 가입된 계정 정보를 반환합니다. - """) + ## [아이디 찾기] 이메일 인증을 통해 계정 정보를 불러옵니다. + * 인증번호 전송 API > 이메일 인증 API 호출 후 해당 API를 호출해야 합니다. + * 이메일 인증 API로부터 발급 받은 임시 토큰이 Authorization 헤더에 포함되어야 합니다. + * 임시 토큰을 발급 받은 이메일로 가입된 계정 정보를 반환합니다. + """) @PostMapping("/find-id") public ApiResponse findId() { MemberResponseDTO.FindIdDTO findIdDTO = authService.findId(); @@ -233,11 +235,11 @@ public ApiResponse findId() { @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[비밀번호 찾기] 비밀번호 찾기 API", description = """ - ## [비밀번호 찾기] 이메일 인증 & 아이디 확인을 통해 임시 비밀번호를 발급합니다. - * 인증번호 전송 API > 이메일 인증 API 호출 후 해당 API를 호출해야 합니다. - * 이메일 인증 API로부터 발급 받은 임시 토큰이 Authorization 헤더에 포함되어야 합니다. - * 이메일과 아이디 정보를 통해 비밀번호를 발급하여 반환합니다. - """) + ## [비밀번호 찾기] 이메일 인증 & 아이디 확인을 통해 임시 비밀번호를 발급합니다. + * 인증번호 전송 API > 이메일 인증 API 호출 후 해당 API를 호출해야 합니다. + * 이메일 인증 API로부터 발급 받은 임시 토큰이 Authorization 헤더에 포함되어야 합니다. + * 이메일과 아이디 정보를 통해 비밀번호를 발급하여 반환합니다. + """) @PostMapping("/find-pw") public ApiResponse findPw( @RequestParam String loginId) { @@ -248,10 +250,10 @@ public ApiResponse findPw( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[로그인] RSA Public Key 발급 API", description = """ - ## [로그인] 비밀번호 전송을 위해 RSA Public Key를 발급하는 API입니다. - * 서버에서 발급한 RSA Public Key와 해당 키의 식별자인 rsaId를 클라이언트에 전달합니다. - * 로그인 및 회원가입 시 해당 키를 통해 비밀번호를 암호화하여 전송해야 합니다. - """) + ## [로그인] 비밀번호 전송을 위해 RSA Public Key를 발급하는 API입니다. + * 서버에서 발급한 RSA Public Key와 해당 키의 식별자인 rsaId를 클라이언트에 전달합니다. + * 로그인 및 회원가입 시 해당 키를 통해 비밀번호를 암호화하여 전송해야 합니다. + """) @PostMapping("/login/rsa") public ApiResponse getRSAPublicKey() throws Exception { Rsa.RSAPublicKey rsaPublicKey = authService.getRSAPublicKey(); @@ -260,14 +262,14 @@ public ApiResponse getRSAPublicKey() throws Exception { @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[로그인] 일반 로그인 API", - description = """ - ## [로그인] 아이디(이메일)과 비밀번호를 통해 로그인 하는 API입니다. - * 로그인에 성공하면, 액세스 토큰과 리프레시 토큰이 발급됩니다. - * 액세스 토큰은 사용자의 정보를 인증하는데 사용되며, 리프레시 토큰은 액세스 토큰이 만료된 경우, 액세스 토큰을 재발급 하는데 사용됩니다. - * 액세스 토큰이 만료된 경우, 유효한 상태의 리프레시 토큰을 통해 액세스 토큰을 재발급 받을 수 있습니다. - * rsaId에는 RSA Public Key 발급 API를 통해 획득한 식별자(id)를 입력해야 합니다. - * 비밀번호는 반드시 RSA Public Key로 암호화하여 전송해야 합니다. - """) + description = """ + ## [로그인] 아이디(이메일)과 비밀번호를 통해 로그인 하는 API입니다. + * 로그인에 성공하면, 액세스 토큰과 리프레시 토큰이 발급됩니다. + * 액세스 토큰은 사용자의 정보를 인증하는데 사용되며, 리프레시 토큰은 액세스 토큰이 만료된 경우, 액세스 토큰을 재발급 하는데 사용됩니다. + * 액세스 토큰이 만료된 경우, 유효한 상태의 리프레시 토큰을 통해 액세스 토큰을 재발급 받을 수 있습니다. + * rsaId에는 RSA Public Key 발급 API를 통해 획득한 식별자(id)를 입력해야 합니다. + * 비밀번호는 반드시 RSA Public Key로 암호화하여 전송해야 합니다. + """) @PostMapping("/login") public ApiResponse login( @RequestParam Long rsaId, @@ -279,26 +281,27 @@ public ApiResponse login( @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") @Operation(summary = "[가입 유무 확인] 서비스 가입 유무 확인 API", description = """ - ## [가입 유무 확인] 서비스 가입 유무 확인 API입니다. - 해당 유저가 가입 후 체크리스트를 작성 했는지 확인합니다. - * 가입 후 체크리스트를 작성한 경우 true, 작성하지 않은 경우 false를 반환합니다. - """) + ## [가입 유무 확인] 서비스 가입 유무 확인 API입니다. + 해당 유저가 가입 후 체크리스트를 작성 했는지 확인합니다. + * 가입 후 체크리스트를 작성한 경우 true, 작성하지 않은 경우 false를 반환합니다. + """) @GetMapping("/check") public ApiResponse checkIsSpotMember() { - MemberResponseDTO.CheckMemberDTO checkMemberDTO = authService.checkIsSpotMember(SecurityUtils.getCurrentUserId()); + MemberResponseDTO.CheckMemberDTO checkMemberDTO = authService.checkIsSpotMember( + SecurityUtils.getCurrentUserId()); return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNUP_CHECK_COMPLETED, checkMemberDTO); } -/* ----------------------------- 로그아웃 API ------------------------------------- */ + /* ----------------------------- 로그아웃 API ------------------------------------- */ @Tag(name = "회원 관리 API - 개발 중", description = "회원 관리 API") @Operation(summary = "[로그아웃] 로그아웃 API", - description = """ - ## [로그아웃] 로그아웃 API입니다. - 로그아웃을 진행합니다. - 로그아웃 시, 사용 하던 액세스 토큰과 리프레시 토큰은 더 이상 사용이 불가능합니다. - 다시 서비스를 이용하기 위해서는 로그인을 다시 진행해야 합니다. - """) + description = """ + ## [로그아웃] 로그아웃 API입니다. + 로그아웃을 진행합니다. + 로그아웃 시, 사용 하던 액세스 토큰과 리프레시 토큰은 더 이상 사용이 불가능합니다. + 다시 서비스를 이용하기 위해서는 로그인을 다시 진행해야 합니다. + """) @PostMapping("/logout") public ApiResponse logout() { return null; From f051a6b5cd806b09d8ba06d7a48d08d53f8a043d Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:29:23 +0900 Subject: [PATCH 20/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B2=84=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=99=B8=EB=B6=80=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20Feign=20Client=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/naver/NaverApiClient.java | 14 +++++++++++++ .../client/naver/NaverAuthClient.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java create mode 100644 src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java new file mode 100644 index 00000000..05ebbe3b --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java @@ -0,0 +1,14 @@ +package com.example.spot.auth.infrastructure.client.naver; + +import com.example.spot.auth.presentation.dto.oauth.naver.NaverUser; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient(name = "naverApiClient", url = "https://openapi.naver.com") +public interface NaverApiClient { + + @GetMapping("/v1/nid/me") + NaverUser getNaverUserInfo( + @RequestHeader("Authorization") String accessToken); +} diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java new file mode 100644 index 00000000..b3f0cc20 --- /dev/null +++ b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java @@ -0,0 +1,21 @@ +package com.example.spot.auth.infrastructure.client.naver; + +import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthToken.NaverOAuthTokenDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "naverAuthClient", url = "https://nid.naver.com/oauth2.0") +public interface NaverAuthClient { + + @GetMapping("/token") + NaverOAuthTokenDTO getNaverAccessToken( + @RequestParam("grant_type") String grantType, + @RequestParam("client_id") String clientId, + @RequestParam("client_secret") String clientSecret, + @RequestParam String code, + @RequestParam String state + ); + + +} From bba69ccb6f21930b630cddffc8b1e3e4e74a5cbd Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:32:08 +0900 Subject: [PATCH 21/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B2=84=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20DTO=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/naver/NaverAuthClient.java | 2 +- .../dto/oauth/naver/NaverOAuthTokenDTO.java | 11 +++++++++++ .../presentation/dto/oauth/naver/NaverUser.java | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverOAuthTokenDTO.java create mode 100644 src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverUser.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java index b3f0cc20..bc0807d5 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java @@ -1,6 +1,6 @@ package com.example.spot.auth.infrastructure.client.naver; -import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthToken.NaverOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthTokenDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; diff --git a/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverOAuthTokenDTO.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverOAuthTokenDTO.java new file mode 100644 index 00000000..ac43f188 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverOAuthTokenDTO.java @@ -0,0 +1,11 @@ +package com.example.spot.auth.presentation.dto.oauth.naver; + +public record NaverOAuthTokenDTO( + String access_token, + String token_type, + String refresh_token, + String expires_in, + String error, + String error_description +) { +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverUser.java b/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverUser.java new file mode 100644 index 00000000..7ab717c9 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/dto/oauth/naver/NaverUser.java @@ -0,0 +1,15 @@ +package com.example.spot.auth.presentation.dto.oauth.naver; + +public record NaverUser( + String resultcode, + String message, + NaverPropertiesDTO response +) { + public record NaverPropertiesDTO( + String id, + String name, + String email, + String thumbnail_image + ) { + } +} \ No newline at end of file From ec3217e33eb05d26c770d4d31d5ab14630a6c42e Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:32:27 +0900 Subject: [PATCH 22/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B2=84=20=EA=B4=80=EB=A0=A8=20=EC=A0=84=EB=9E=B5=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/impl/oauth/NaverOauth.java | 73 +++++++++++++++++++ .../impl/straegy/NaverOAuthStrategy.java | 36 +++++++++ .../constants/AuthConstants.java | 2 + 3 files changed, 111 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java create mode 100644 src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java new file mode 100644 index 00000000..eff1ed89 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java @@ -0,0 +1,73 @@ +package com.example.spot.auth.application.refactor.impl.oauth; + +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.REDIRECT_URI; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE_CODE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.STATE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.STATE_STRING; +import static com.example.spot.auth.infrastructure.constants.JwtConstants.BEARER_PREFIX; + +import com.example.spot.auth.infrastructure.client.naver.NaverApiClient; +import com.example.spot.auth.infrastructure.client.naver.NaverAuthClient; +import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.naver.NaverUser; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NaverOauth { + + @Value("${spring.oauth2.naver.client-id}") + private String NAVER_CLIENT_ID; + + @Value("${spring.oauth2.naver.client-secret}") + private String NAVER_CLIENT_SECRET; + + @Value("${spring.oauth2.naver.callback-url}") + private String NAVER_CALLBACK_LOGIN_URL; + + @Value("${spring.oauth2.naver.url}") + private String NAVER_SNS_URL; + + private final NaverAuthClient naverAuthClient; + private final NaverApiClient naverApiClient; + + public String getOauthRedirectURL() { + Map params = new HashMap<>(); + params.put(CLIENT_ID, NAVER_CLIENT_ID); + params.put(REDIRECT_URI, NAVER_CALLBACK_LOGIN_URL); + params.put(RESPONSE_TYPE, RESPONSE_TYPE_CODE); + params.put(STATE, STATE_STRING); + + String parameterString = params.entrySet().stream() + .map(x -> x.getKey() + KEY_VALUE_DELIMITER + x.getValue()) + .collect(Collectors.joining(QUERY_DELIMITER)); + + return NAVER_SNS_URL + QUERY_PREFIX + parameterString; + } + + public NaverOAuthTokenDTO requestAccessToken(String code) { + return naverAuthClient.getNaverAccessToken( + GRANT_TYPE, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, code, STATE); + } + + + public NaverUser requestUserInfo(NaverOAuthTokenDTO naverOAuthTokenDTO) { + return naverApiClient.getNaverUserInfo( + getAccessToken(naverOAuthTokenDTO)); + } + + private static String getAccessToken(NaverOAuthTokenDTO naverOAuthTokenDTO) { + return BEARER_PREFIX + naverOAuthTokenDTO.access_token(); + } +} diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java new file mode 100644 index 00000000..5e556c83 --- /dev/null +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java @@ -0,0 +1,36 @@ +package com.example.spot.auth.application.refactor.impl.straegy; + +import com.example.spot.auth.application.refactor.OAuthStrategy; +import com.example.spot.auth.application.refactor.impl.oauth.NaverOauth; +import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthTokenDTO; +import com.example.spot.auth.presentation.dto.oauth.naver.NaverUser; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.enums.LoginType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NaverOAuthStrategy implements OAuthStrategy { + + private final NaverOauth naverOauth; + + @Override + public LoginType getType() { + return LoginType.NAVER; + } + + @Override + public String getOauthRedirectURL() { + return naverOauth.getOauthRedirectURL(); + } + + @Override + public Member toMember(String code) { + NaverOAuthTokenDTO token = naverOauth.requestAccessToken(code); + NaverUser user = naverOauth.requestUserInfo(token); + return Member.toMember( + getType(), user.response().name(), user.response().email(), + user.response().thumbnail_image()); + } +} diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java index 947f4b59..470858f4 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java @@ -14,6 +14,8 @@ public class AuthConstants { public static final String KEY_VALUE_DELIMITER = "="; public static final String QUERY_PREFIX = "?"; public static final String SCOPE = "scope"; + public static final String STATE = "state"; + public static final String STATE_STRING = "STATE_STRING"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String KAKAO_AUTHORIZATION = "KakaoAK "; From e586445d77e7a6434a0db42d4ef1a701008319d5 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:33:02 +0900 Subject: [PATCH 23/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A0=88=EA=B1=B0=EC=8B=9C=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?API=20=ED=98=B8=EC=B6=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakao/KakaoOAuthClient.java | 155 ------------------ 1 file changed, 155 deletions(-) delete mode 100644 src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java deleted file mode 100644 index 1f0ff591..00000000 --- a/src/main/java/com/example/spot/auth/infrastructure/kakao/KakaoOAuthClient.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.example.spot.auth.infrastructure.kakao; - -import com.example.spot.auth.presentation.dto.kakao.KaKaoOAuthToken; -import com.example.spot.auth.presentation.dto.kakao.KaKaoOAuthToken.KaKaoOAuthTokenDTO; -import com.example.spot.auth.presentation.dto.kakao.KaKaoUser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; - -@Service -@Slf4j -@RequiredArgsConstructor -public class KakaoOAuthClient { - - @Value("${spring.oauth2.kakao.url}") - private String KAKAO_SNS_URL; - - @Value("${spring.oauth2.kakao.client-id}") - private String KAKAO_SNS_CLIENT_ID; - - @Value("${spring.oauth2.kakao.callback-login-url}") - private String KAKAO_SNS_CALLBACK_LOGIN_URL; - - - private final ObjectMapper objectMapper; - private final RestTemplate restTemplate; - - /** - * 카카오 로그인 요청 URL을 생성합니다. - * - * @return 카카오 로그인 요청 URL - */ - public String getOauthRedirectURL() { - // 카카오 로그인 요청 URL 생성 - Map params = new HashMap<>(); - params.put("client_id", KAKAO_SNS_CLIENT_ID); - params.put("redirect_uri", KAKAO_SNS_CALLBACK_LOGIN_URL); - params.put("response_type", "code"); - - // URL 파라미터 생성 - String parameterString = params.entrySet().stream() - .map(x -> x.getKey() + "=" + x.getValue()) - .collect(Collectors.joining("&")); - - // 카카오 로그인 요청 URL - return KAKAO_SNS_URL + "?" + parameterString; - } - - /** - * 카카오 로그인 요청을 합니다. - * - * @param code 카카오 로그인 요청 시 발급받은 코드 - * @return 카카오 로그인 요청 결과 - */ - public ResponseEntity requestAccessToken(String code) { - String KAKAO_TOKEN_REQUEST_URL = "https://kauth.kakao.com/oauth/token"; - // 카카오 로그인 요청 - RestTemplate restTemplate = new RestTemplate(); - // 요청 파라미터 - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("grant_type", "authorization_code"); - params.add("client_id", KAKAO_SNS_CLIENT_ID); - params.add("redirect_uri", KAKAO_SNS_CALLBACK_LOGIN_URL); - params.add("code", code); - - // 헤더에 Content-Type 추가 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED); - HttpEntity> request = new HttpEntity(params, headers); - - // 카카오 로그인 요청 - ResponseEntity responseEntity = restTemplate.postForEntity(KAKAO_TOKEN_REQUEST_URL, request, - String.class); - - // 요청 결과 반환 - if (responseEntity.getStatusCode() == HttpStatus.OK) { - return responseEntity; - } - - // 요청 실패 시 null 반환 - return null; - } - - /** - * 카카오 로그인 요청 결과에서 AccessToken을 추출합니다. - * - * @param response 카카오 로그인 요청 결과 - * @return 카카오 로그인 성공 시 반환되는 AccessToken - * @throws JsonProcessingException JSON 파싱 예외 - */ - public KaKaoOAuthTokenDTO getAccessToken(ResponseEntity response) - throws JsonProcessingException { - - // 카카오 로그인 요청 결과에서 AccessToken 추출 - KaKaoOAuthToken.KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO = objectMapper.readValue(response.getBody(), - KaKaoOAuthTokenDTO.class); - return kaKaoOAuthTokenDTO; - } - - - /** - * 카카오 로그인 시 발급된 accessToken을 이용하여 사용자 정보를 요청합니다. - * - * @param accessToken 카카오 로그인 시 발급된 accessToken - * @return 사용자 정보 요청 결과 - */ - public ResponseEntity requestUserInfo(String accessToken) { - log.info("accessToken = {}", accessToken); - // 사용자 정보 요청 URL - String KAKAO_USER_INFO_REQUEST_URL = "https://kapi.kakao.com/v2/user/me"; - - // 헤더에 accessToken 추가 - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + accessToken); - - // HttpEntity에 헤더 추가 - HttpEntity> request = new HttpEntity(headers); - - // 사용자 정보 요청 - ResponseEntity responseEntity = restTemplate.exchange(KAKAO_USER_INFO_REQUEST_URL, HttpMethod.GET, - request, String.class); - - return responseEntity; - } - - /** - * 카카오 로그인 요청 결과에서 사용자 정보를 추출합니다. - * - * @param userInfoRes 카카오 로그인 요청 결과 - * @return 카카오 로그인 성공 시 반환되는 사용자 정보 - * @throws JsonProcessingException JSON 파싱 예외 - */ - public KaKaoUser getUserInfo(ResponseEntity userInfoRes) - throws JsonProcessingException { - // 사용자 정보 파싱 - KaKaoUser kaKaoUser = objectMapper.readValue(userInfoRes.getBody(), KaKaoUser.class); - return kaKaoUser; - } - - -} From bd0c03d8dc53e02b16bfc045c1df859b199f23bf Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:34:55 +0900 Subject: [PATCH 24/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Token=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=AA=85=EC=97=90?= =?UTF-8?q?=EC=84=9C=20JWT=EC=99=80=20=EA=B0=99=EC=9D=80=20=EA=B8=B0?= =?UTF-8?q?=EC=88=A0=20=EA=B5=AC=EC=B2=B4=EC=A0=81=EC=9D=B8=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...JwtTokenService.java => TokenService.java} | 2 +- ...nServiceImpl.java => JwtTokenService.java} | 4 +- .../controller/legacy/AuthController.java | 52 ------------------- .../refactor/JwtTokenController.java | 44 ---------------- .../controller/refactor/TokenController.java | 41 +++++++++++++++ 5 files changed, 44 insertions(+), 99 deletions(-) rename src/main/java/com/example/spot/auth/application/refactor/{JwtTokenService.java => TokenService.java} (88%) rename src/main/java/com/example/spot/auth/application/refactor/impl/{JwtTokenServiceImpl.java => JwtTokenService.java} (95%) delete mode 100644 src/main/java/com/example/spot/auth/presentation/controller/refactor/JwtTokenController.java create mode 100644 src/main/java/com/example/spot/auth/presentation/controller/refactor/TokenController.java diff --git a/src/main/java/com/example/spot/auth/application/refactor/JwtTokenService.java b/src/main/java/com/example/spot/auth/application/refactor/TokenService.java similarity index 88% rename from src/main/java/com/example/spot/auth/application/refactor/JwtTokenService.java rename to src/main/java/com/example/spot/auth/application/refactor/TokenService.java index e87b5c32..6924e1d0 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/JwtTokenService.java +++ b/src/main/java/com/example/spot/auth/application/refactor/TokenService.java @@ -2,7 +2,7 @@ import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; -public interface JwtTokenService { +public interface TokenService { // 리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급 TokenResponseDTO.TokenDTO reissueToken(String refreshToken); diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenServiceImpl.java b/src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenService.java similarity index 95% rename from src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenServiceImpl.java rename to src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenService.java index 06fc8076..254ee01d 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenServiceImpl.java +++ b/src/main/java/com/example/spot/auth/application/refactor/impl/JwtTokenService.java @@ -1,6 +1,6 @@ package com.example.spot.auth.application.refactor.impl; -import com.example.spot.auth.application.refactor.JwtTokenService; +import com.example.spot.auth.application.refactor.TokenService; import com.example.spot.auth.domain.RefreshToken; import com.example.spot.auth.domain.RefreshTokenRepository; import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; @@ -17,7 +17,7 @@ @Service @Transactional @RequiredArgsConstructor -public class JwtTokenServiceImpl implements JwtTokenService { +public class JwtTokenService implements TokenService { private final MemberRepository memberRepository; private final JwtTokenProvider jwtTokenProvider; diff --git a/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java b/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java index 14c5faed..f52f1041 100644 --- a/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java +++ b/src/main/java/com/example/spot/auth/presentation/controller/legacy/AuthController.java @@ -28,7 +28,6 @@ @Slf4j -@Deprecated @RestController @RequestMapping("/spot") @RequiredArgsConstructor @@ -89,57 +88,6 @@ public ApiResponse withdraw() { return ApiResponse.onSuccess(SuccessStatus._MEMBER_DELETED, inactiveMemberDTO); } - /* ----------------------------- 네이버 소셜로그인 API ------------------------------------- */ - -// @Tag(name = "테스트 용 API", description = "테스트 용 API") -// @Operation(summary = "!서버용! [네이버 로그인] 테스트용 인증코드 발급 API", -// description = """ -// ## [네이버 로그인] 네이버 액세스 토큰 발급에 필요한 인증코드를 발급 받는 API입니다. -// * API를 호출하면 네이버 로그인 페이지로 리디렉션됩니다. -// * 로그인을 완료하면 <네이버 회원 조회> 콜백 함수가 실행됩니다. -// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 -// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 -// ** 테스트 시 API URL을 복사하여 웹 브라우저에서 실행해주세요!!** -// """) -// @GetMapping("/members/sign-in/naver/authorize/test") -// public void authorizeWithNaver(HttpServletRequest request, HttpServletResponse response) { -// authService.authorizeWithNaver(request, response); -// } -// -// @Tag(name = "테스트 용 API", description = "테스트 용 API") -// @Operation(summary = "!서버용! [네이버 로그인] 테스트용 네이버 로그인/회원가입 API", -// description = """ -// ## [네이버 로그인] 네이버 액세스 토큰을 발급하여 로그인/회원가입을 수행하는 콜백 함수입니다 -// ### (직접 호출하는 API가 아닙니다) -// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 -// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 -// * 콜백 함수의 결과로 토큰 정보가 반환됩니다. -// """) -// @GetMapping("/members/sign-in/naver/redirect") -// public ApiResponse signInWithNaver( -// HttpServletRequest request, HttpServletResponse response, NaverCallback naverCallback) throws Exception { -// SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverCallback); -// return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); -// } -// -// @Tag(name = "네이버 로그인 API - 개발 완료", description = "네이버 로그인 API") -// @Operation(summary = "[네이버 로그인] 네이버 로그인/회원가입 API", -// description = """ -// ## [네이버 로그인] 클라이언트로부터 네이버 액세스 토큰을 받아 로그인/회원가입을 수행하는 함수입니다 -// * 회원가입이 되어있는 경우 -> 로그인 & 토큰 정보 반환 -// * 회원가입이 되어있지 않은 경우 -> 회원가입 & 로그인 & 토큰 정보 반환 -// """) -// @PostMapping(value = "/members/sign-in/naver", produces = MediaType.APPLICATION_JSON_VALUE) -// public ApiResponse signInWithNaver( -// HttpServletRequest request, -// HttpServletResponse response, -// @RequestBody NaverOAuthToken.NaverTokenIssuanceDTO naverTokenDTO -// ) throws Exception { -// SocialLoginSignInDTO socialLoginSignInDTO = authService.signInWithNaver(request, response, naverTokenDTO); -// return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, socialLoginSignInDTO); -// } - - /* ----------------------------- 일반 로그인/회원가입 API ------------------------------------- */ diff --git a/src/main/java/com/example/spot/auth/presentation/controller/refactor/JwtTokenController.java b/src/main/java/com/example/spot/auth/presentation/controller/refactor/JwtTokenController.java deleted file mode 100644 index 51fd4447..00000000 --- a/src/main/java/com/example/spot/auth/presentation/controller/refactor/JwtTokenController.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.spot.auth.presentation.controller.refactor; - -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.example.spot.auth.application.refactor.JwtTokenService; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; -import com.example.spot.common.api.ApiResponse; -import com.example.spot.common.api.code.status.SuccessStatus; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RestController -@RequestMapping("/spot") -@RequiredArgsConstructor -public class JwtTokenController { - - // Newly added - private final JwtTokenService jwtTokenService; - - /* ----------------------------- JWT 토큰 관리 API ------------------------------------- */ - - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @Operation(summary = "[세션 유지] 액세스 토큰 재발급 API", - description = """ - ## [세션 유지] 액세스 토큰을 재발급 하는 API입니다. - 리프레시 토큰을 통해 액세스 토큰을 재발급 합니다. - 리프레시 토큰의 만료 기간 이전인 경우에만 재발급이 가능합니다. - 액세스 토큰을 재발급 하는 경우, 리프레시 토큰도 재발급 됩니다. - """) - @PostMapping("/reissue") - public ApiResponse reissueToken(HttpServletRequest request, - @RequestHeader("refreshToken") String refreshToken){ - return ApiResponse.onSuccess(SuccessStatus._CREATED, jwtTokenService.reissueToken(refreshToken)); - } - -} diff --git a/src/main/java/com/example/spot/auth/presentation/controller/refactor/TokenController.java b/src/main/java/com/example/spot/auth/presentation/controller/refactor/TokenController.java new file mode 100644 index 00000000..6e6f2d76 --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/controller/refactor/TokenController.java @@ -0,0 +1,41 @@ +package com.example.spot.auth.presentation.controller.refactor; + +import com.example.spot.auth.application.refactor.TokenService; +import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; +import com.example.spot.common.api.ApiResponse; +import com.example.spot.common.api.code.status.SuccessStatus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/spot") +@RequiredArgsConstructor +public class TokenController { + + private final TokenService tokenService; + + /* ----------------------------- JWT 토큰 관리 API ------------------------------------- */ + + @Tag(name = "회원 관리 API", description = "회원 관리 API") + @Operation(summary = "[세션 유지] 액세스 토큰 재발급 API", + description = """ + ## [세션 유지] 액세스 토큰을 재발급 하는 API입니다. + 리프레시 토큰을 통해 액세스 토큰을 재발급 합니다. + 리프레시 토큰의 만료 기간 이전인 경우에만 재발급이 가능합니다. + 액세스 토큰을 재발급 하는 경우, 리프레시 토큰도 재발급 됩니다. + """) + @PostMapping("/reissue") + public ApiResponse reissueToken(HttpServletRequest request, + @RequestHeader("refreshToken") String refreshToken) { + return ApiResponse.onSuccess(SuccessStatus._CREATED, tokenService.reissueToken(refreshToken)); + } + +} From 8060f9feb91b2f7b298cebee65a64530de26ed9e Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:48:58 +0900 Subject: [PATCH 25/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20OAuth=20=EA=B4=80=EB=A0=A8=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EB=B0=8F=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 - .../spot/common/config/WebSecurity.java | 25 +-- .../filters/JwtAuthenticationFilter.java | 19 +- .../oauth/CustomOAuth2UserService.java | 91 -------- .../oauth/CustomOAuthSuccessHandler.java | 74 ------- .../security/oauth/OAuthUserInfoFactory.java | 27 --- .../oauth/adpter/CustomOAuth2User.java | 50 ----- .../security/oauth/adpter/OAuth2UserInfo.java | 14 -- .../oauth/adpter/google/GoogleUserInfo.java | 36 --- .../{refactor => }/MemberInfoService.java | 4 +- .../application/MemberPreferenceService.java | 22 ++ .../application/MemberTestSupportService.java | 13 ++ .../impl/MemberInfoServiceImpl.java | 4 +- .../impl/MemberPreferenceServiceImpl.java | 4 +- .../impl/MemberTestSupportServiceImpl.java | 10 +- .../application/legacy/MemberService.java | 23 -- .../application/legacy/MemberServiceImpl.java | 80 ------- .../refactor/MemberPreferenceService.java | 15 -- .../refactor/MemberTestSupportService.java | 13 -- .../controller/legacy/MemberController.java | 81 ------- .../refactor/MemberInfoController.java | 45 ++-- .../refactor/MemberPreferenceController.java | 206 +++++++++--------- .../refactor/MemberTestSupportController.java | 68 +++--- 23 files changed, 222 insertions(+), 705 deletions(-) delete mode 100644 src/main/java/com/example/spot/common/security/oauth/CustomOAuth2UserService.java delete mode 100644 src/main/java/com/example/spot/common/security/oauth/CustomOAuthSuccessHandler.java delete mode 100644 src/main/java/com/example/spot/common/security/oauth/OAuthUserInfoFactory.java delete mode 100644 src/main/java/com/example/spot/common/security/oauth/adpter/CustomOAuth2User.java delete mode 100644 src/main/java/com/example/spot/common/security/oauth/adpter/OAuth2UserInfo.java delete mode 100644 src/main/java/com/example/spot/common/security/oauth/adpter/google/GoogleUserInfo.java rename src/main/java/com/example/spot/member/application/{refactor => }/MemberInfoService.java (51%) create mode 100644 src/main/java/com/example/spot/member/application/MemberPreferenceService.java create mode 100644 src/main/java/com/example/spot/member/application/MemberTestSupportService.java rename src/main/java/com/example/spot/member/application/{refactor => }/impl/MemberInfoServiceImpl.java (93%) rename src/main/java/com/example/spot/member/application/{refactor => }/impl/MemberPreferenceServiceImpl.java (98%) rename src/main/java/com/example/spot/member/application/{refactor => }/impl/MemberTestSupportServiceImpl.java (94%) delete mode 100644 src/main/java/com/example/spot/member/application/legacy/MemberService.java delete mode 100644 src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java delete mode 100644 src/main/java/com/example/spot/member/application/refactor/MemberPreferenceService.java delete mode 100644 src/main/java/com/example/spot/member/application/refactor/MemberTestSupportService.java delete mode 100644 src/main/java/com/example/spot/member/presentation/controller/legacy/MemberController.java diff --git a/build.gradle b/build.gradle index 1043faa3..59106f76 100644 --- a/build.gradle +++ b/build.gradle @@ -42,9 +42,6 @@ dependencies { // apache common csv implementation 'org.apache.commons:commons-csv:1.8' - // Oauth2 - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - // Spring Security implementation 'org.springframework.boot:spring-boot-starter-security' diff --git a/src/main/java/com/example/spot/common/config/WebSecurity.java b/src/main/java/com/example/spot/common/config/WebSecurity.java index 1824375b..802189ff 100644 --- a/src/main/java/com/example/spot/common/config/WebSecurity.java +++ b/src/main/java/com/example/spot/common/config/WebSecurity.java @@ -1,14 +1,10 @@ package com.example.spot.common.config; +import com.example.spot.auth.application.refactor.UserDetailsServiceCustom; import com.example.spot.common.security.filters.JwtAuthenticationFilter; -import com.example.spot.common.security.oauth.CustomOAuth2UserService; -import com.example.spot.common.security.oauth.CustomOAuthSuccessHandler; import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.member.application.legacy.MemberService; -import com.example.spot.auth.application.refactor.UserDetailsServiceCustom; import lombok.RequiredArgsConstructor; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -18,6 +14,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + @Configuration @EnableWebSecurity @RequiredArgsConstructor @@ -25,16 +22,11 @@ public class WebSecurity { // JWT 토큰을 생성하고 유효성을 검사하는 JwtTokenProvider private final JwtTokenProvider jwtTokenProvider; - // 회원 정보를 처리하는 MemberService - private final MemberService memberService; // 사용자 정보를 처리하는 UserDetailsServiceCustom private final UserDetailsServiceCustom userDetailsService; - private final CustomOAuth2UserService customOAuth2UserService; - private final CustomOAuthSuccessHandler customOAuthSuccessHandler; /** - * * @param http * @return * @throws Exception @@ -43,7 +35,7 @@ public class WebSecurity { protected SecurityFilterChain configure(HttpSecurity http) throws Exception { // CSRF 보안 설정을 비활성화합니다. - http.csrf( (csrf) -> csrf.disable()); + http.csrf((csrf) -> csrf.disable()); // HttpSecurity 설정을 구성합니다. JWT 토큰을 통한 검증을 거치지 않는 요청은 permitAll()로 설정합니다. http.authorizeHttpRequests((authz) -> authz @@ -58,7 +50,8 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/spot/login/rsa", "POST")).permitAll() .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver", "POST")).permitAll() .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver/redirect", "GET")).permitAll() - .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver/authorize/test", "GET")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/naver/authorize/test", "GET")) + .permitAll() .requestMatchers(new AntPathRequestMatcher("/spot/login/kakao", "GET")).permitAll() .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/kakao", "GET")).permitAll() .requestMatchers(new AntPathRequestMatcher("/spot/members/sign-in/kakao/redirect", "GET")).permitAll() @@ -69,11 +62,6 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**", "GET")).permitAll() .anyRequest().authenticated() ) - .oauth2Login(oauth2 -> oauth2 - .authorizationEndpoint(authorization -> authorization.baseUri("/oauth/authorize")) - .redirectionEndpoint(redirection -> redirection.baseUri("/spot/members/sign-in/google/redirect")) - .successHandler(customOAuthSuccessHandler) - ) // JWT 토큰을 검증하는 필터를 UsernamePasswordAuthenticationFilter 앞에 추가합니다. .addFilterBefore(getJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 세션을 사용하지 않도록 설정합니다. @@ -87,10 +75,11 @@ protected SecurityFilterChain configure(HttpSecurity http) throws Exception { /** * JWT 토큰을 검증하는 필터를 생성합니다. + * * @return JwtAuthenticationFilter */ private JwtAuthenticationFilter getJwtAuthenticationFilter() { - return new JwtAuthenticationFilter(jwtTokenProvider, memberService, userDetailsService); + return new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService); } @Bean diff --git a/src/main/java/com/example/spot/common/security/filters/JwtAuthenticationFilter.java b/src/main/java/com/example/spot/common/security/filters/JwtAuthenticationFilter.java index 33fd2766..ac86a4b1 100644 --- a/src/main/java/com/example/spot/common/security/filters/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/spot/common/security/filters/JwtAuthenticationFilter.java @@ -1,14 +1,14 @@ package com.example.spot.common.security.filters; -import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.auth.application.refactor.UserDetailsServiceCustom; import com.example.spot.auth.domain.TempUserDetails; -import com.example.spot.member.application.legacy.MemberService; +import com.example.spot.common.api.exception.GeneralException; import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.auth.application.refactor.UserDetailsServiceCustom; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import java.io.PrintWriter; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -17,28 +17,26 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; - @Slf4j @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; // JwtTokenProvider 주입 추가 - private final MemberService memberService; // MemberService 주입 추가 private final UserDetailsServiceCustom userDetailsService; // UserDetailsServiceCustom 주입 추가 /** * JWT 토큰을 검증하는 필터를 생성합니다. - * @param request HTTP 요청 - * @param response HTTP 응답 + * + * @param request HTTP 요청 + * @param response HTTP 응답 * @param filterChain 필터 체인 * @throws ServletException * @throws IOException */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { + FilterChain filterChain) throws ServletException, IOException { try { // 임시 토큰 인증 요청 별도 처리 if (isTempRequest(request)) { @@ -62,8 +60,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String token = jwtTokenProvider.resolveToken(request); // 토큰이 유효한 경우 사용자 인증 - if (isValidToken(token)) + if (isValidToken(token)) { authenticateUser(token); + } filterChain.doFilter(request, response); } catch (GeneralException e) { diff --git a/src/main/java/com/example/spot/common/security/oauth/CustomOAuth2UserService.java b/src/main/java/com/example/spot/common/security/oauth/CustomOAuth2UserService.java deleted file mode 100644 index 969d65ec..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/CustomOAuth2UserService.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.example.spot.common.security.oauth; - -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.GeneralException; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.common.security.oauth.adpter.CustomOAuth2User; -import com.example.spot.common.security.oauth.adpter.OAuth2UserInfo; -import com.example.spot.common.security.utils.MemberUtils; -import com.example.spot.member.application.legacy.MemberService; -import com.example.spot.member.domain.Member; -import com.example.spot.member.domain.enums.Carrier; -import com.example.spot.member.domain.enums.LoginType; -import com.example.spot.member.infrastructure.MemberRepository; -import java.time.LocalDate; -import java.util.Map; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - - -/** - * CustomOAuth2UserService는 OAuth2UserService를 확장하여, 액세스 토큰을 사용하여 사용자 정보를 가져오는 역할을 합니다. - *

- * loadUser() 메서드: 이 메서드는 액세스 토큰을 사용하여 구글 API에서 사용자 정보를 가져옵니다. 사용자 정보 처리: 가져온 사용자 정보를 사용하여 새로운 사용자를 생성하거나 기존 사용자와 - * 연동합니다. - *

- * 정리하면, accessToken으로 Oauth에게 받아온 사용자 정보를 가져오고 처리하는 역할을합니다. - */ - - -@Service -@RequiredArgsConstructor -public class CustomOAuth2UserService extends DefaultOAuth2UserService { - - private final MemberRepository memberRepository; - private final MemberService memberService; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - - // Google이 발급한 accessToken으로 요청한 후 받은 사용자에 대한 Google 정보 추출 - OAuth2User oAuth2User = super.loadUser(userRequest); - - // 로그인한 경로, 이메일 등 필요한 정보 추출 - Map attributes = oAuth2User.getAttributes(); - String provider = userRequest.getClientRegistration().getRegistrationId(); - OAuth2UserInfo oAuthUserInfo = OAuthUserInfoFactory.getOAuthUserInfo(provider, attributes); - - String oauthEmail = oAuthUserInfo.getEmail(); - if (memberRepository.existsByEmailAndLoginTypeNot(oauthEmail, LoginType.GOOGLE)) { - throw new GeneralException(ErrorStatus._MEMBER_EMAIL_EXIST); - } - - // - Optional optionalMember = memberRepository.findByEmail(oauthEmail); - if (optionalMember.isEmpty()) { - if (provider.equals("google")) { - Member newMember = generateMember(attributes, oauthEmail); - memberService.save(newMember); - - // SuccessHandler에서 principle로 추출시에 우리 회원 정보를 추출하기 위해 CustomOAuth2User로 반환 - return new CustomOAuth2User(newMember, attributes, false); - } - throw new MemberHandler(ErrorStatus._MEMBER_UNSUPPORTED_LOGIN_TYPE); - } - - // SuccessHandler에서 principle로 추출시에 우리 회원 정보를 추출하기 위해 CustomOAuth2User로 반환 - return new CustomOAuth2User(optionalMember.get(), attributes, true); - } - - private Member generateMember(Map attributes, String oauthEmail) { - return Member.builder() - .name(attributes.get("name").toString().substring(0, 10)) - .nickname(attributes.get("name").toString().substring(0, 10)) - .email(oauthEmail) - .profileImage(attributes.get("picture").toString()) - .carrier(Carrier.NONE) - .password("default") - .phone(MemberUtils.generatePhoneNumber()) - .birth(LocalDate.now()) - .personalInfo(false) - .idInfo(false) - .isAdmin(false) - .loginType(LoginType.GOOGLE) - .build(); - } -} diff --git a/src/main/java/com/example/spot/common/security/oauth/CustomOAuthSuccessHandler.java b/src/main/java/com/example/spot/common/security/oauth/CustomOAuthSuccessHandler.java deleted file mode 100644 index 0edfd18b..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/CustomOAuthSuccessHandler.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.example.spot.common.security.oauth; - -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO; -import com.example.spot.common.api.ApiResponse; -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.code.status.SuccessStatus; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.common.security.oauth.adpter.CustomOAuth2User; -import com.example.spot.common.security.oauth.adpter.google.GoogleUserInfo; -import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.member.domain.Member; -import com.example.spot.member.infrastructure.MemberRepository; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -/** - * Spring Security가 CustomOAuth2UserService.loadUser()에서 성공한 사용자 정보를 자동으로 SecurityContext Principle에 등록합니다. 따라서, OAuth로 - * 로그인 성공 후 브라우저 재 진입시 Principle이 살아있는한 다시 로그인할 필요가 없습니다. (JwtToken과는 별개) - */ - -@Component -@RequiredArgsConstructor -public class CustomOAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - private final MemberRepository memberRepository; - private final JwtTokenProvider jwtTokenProvider; - private final ObjectMapper objectMapper; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - - // CustomOAuth2UserService.loadUser()에서 저장한 Security Context에서 Principle 추출 - // 추후에 Spring Security에서 제공하는 OAuth 방식을 사용할까봐, OAuth2UserInfo로 추상화했습니다. - CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); - GoogleUserInfo googleUserInfo = new GoogleUserInfo(customOAuth2User.getAttributes()); - - String email = googleUserInfo.getEmail(); - Optional memberOptional = memberRepository.findByEmail(email); - Member member = memberOptional.orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - Long memberId = member.getId(); - - // JWT Token 발급 - TokenResponseDTO.TokenDTO token = jwtTokenProvider.createToken(memberId); - - MemberResponseDTO.MemberSignInDTO memberSignInDTO = MemberResponseDTO.MemberSignInDTO.builder() - .tokens(token) - .memberId(member.getId()) - .loginType(member.getLoginType()) - .email(member.getEmail()) - .build(); - - // OAuth 로그인 성공한 후 응답 방식으로 포멧팅 - ApiResponse apiResponse = ApiResponse.onSuccess( - SuccessStatus._OK, SocialLoginSignInDTO.toDTO( - customOAuth2User.getIsSpotMember(), memberSignInDTO)); - - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - - // 객체 직렬화하여 작성 - objectMapper.writeValue(response.getWriter(), apiResponse); - } -} diff --git a/src/main/java/com/example/spot/common/security/oauth/OAuthUserInfoFactory.java b/src/main/java/com/example/spot/common/security/oauth/OAuthUserInfoFactory.java deleted file mode 100644 index 619de3d0..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/OAuthUserInfoFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.spot.common.security.oauth; - -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.common.security.oauth.adpter.google.GoogleUserInfo; -import com.example.spot.common.security.oauth.adpter.OAuth2UserInfo; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -/** - * 추후 Spring Security OAuth2로 사용시 이곳에 구현하시면 됩니다. - */ - -@Slf4j -public class OAuthUserInfoFactory { - - public static OAuth2UserInfo getOAuthUserInfo(String provider, Map attributes) { - - if (provider.equals("google")) { - log.info("------------------ GOOGLE 로그인 요청 ------------------"); - return new GoogleUserInfo(attributes); - } - throw new MemberHandler(ErrorStatus._MEMBER_UNSUPPORTED_LOGIN_TYPE); - - } -} diff --git a/src/main/java/com/example/spot/common/security/oauth/adpter/CustomOAuth2User.java b/src/main/java/com/example/spot/common/security/oauth/adpter/CustomOAuth2User.java deleted file mode 100644 index 1d2ca8e7..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/adpter/CustomOAuth2User.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.example.spot.common.security.oauth.adpter; - -import com.example.spot.member.domain.Member; -import com.example.spot.member.domain.enums.LoginType; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.user.OAuth2User; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * OAuth 로그인 성공 시 CustomOAuth2UserService에서 생성되는 객체입니다. - * {@code CustomOAuth2UserService.loadUser()} 메서드의 반환 타입으로, {@link OAuth2User}를 구현하여 SecurityContext에 Principal을 등록하는 역할을 합니다. - * Google 정보뿐만 아니라 애플리케이션의 회원 정보도 포함하여 SuccessHandler 등에서 Principal로 활용할 수 있도록 합니다. - */ - -@RequiredArgsConstructor -public class CustomOAuth2User implements OAuth2User { - - private final Member member; - private final Map attributes; - - @Getter - private final Boolean isSpotMember; - - @Override - public Map getAttributes() { - return attributes; - } - - // Google로 로그인한 사용자를 기본 Role로 설정합니다. - @Override - public Collection getAuthorities() { - return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")); - } - - @Override - public String getName() { - if (member.getLoginType().equals(LoginType.GOOGLE)) { - return attributes.get("name").toString(); - } - - return null; - } - -} diff --git a/src/main/java/com/example/spot/common/security/oauth/adpter/OAuth2UserInfo.java b/src/main/java/com/example/spot/common/security/oauth/adpter/OAuth2UserInfo.java deleted file mode 100644 index 0ecdef21..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/adpter/OAuth2UserInfo.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.spot.common.security.oauth.adpter; - -/** - * 추후 Spring Security OAuth2로 사용시 해당 interface를 구현하시면 됩니다. - */ - -public interface OAuth2UserInfo { - String getProfile(); - - String getEmail(); - - String getName(); - -} diff --git a/src/main/java/com/example/spot/common/security/oauth/adpter/google/GoogleUserInfo.java b/src/main/java/com/example/spot/common/security/oauth/adpter/google/GoogleUserInfo.java deleted file mode 100644 index 0bb08c0f..00000000 --- a/src/main/java/com/example/spot/common/security/oauth/adpter/google/GoogleUserInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.spot.common.security.oauth.adpter.google; - -import com.example.spot.common.security.oauth.adpter.OAuth2UserInfo; -import lombok.RequiredArgsConstructor; - -import java.util.Map; - -/** - * Spring Security 내부에서 "/oauth/authorize/google" 로그인에 성공한 후, - * OAuth2LoginAuthenticationFilter가 가로채어 구글 API에 사용자 정보를 반환 받은 정보를 저장하는 객체 - * - * Notion OAuth + Spring Security의 자동화 5 ~ 6 번 참고 -**/ - - -@RequiredArgsConstructor -public class GoogleUserInfo implements OAuth2UserInfo { - - private final Map attributes; - - @Override - public String getProfile() { - return attributes.get("picture").toString(); - } - - @Override - public String getEmail() { - return attributes.get("email").toString(); - } - - @Override - public String getName() { - return attributes.get("name").toString(); - } - -} diff --git a/src/main/java/com/example/spot/member/application/refactor/MemberInfoService.java b/src/main/java/com/example/spot/member/application/MemberInfoService.java similarity index 51% rename from src/main/java/com/example/spot/member/application/refactor/MemberInfoService.java rename to src/main/java/com/example/spot/member/application/MemberInfoService.java index 6d6c5d24..3409c171 100644 --- a/src/main/java/com/example/spot/member/application/refactor/MemberInfoService.java +++ b/src/main/java/com/example/spot/member/application/MemberInfoService.java @@ -1,9 +1,9 @@ -package com.example.spot.member.application.refactor; +package com.example.spot.member.application; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; public interface MemberInfoService { - MemberResponseDTO.MemberUpdateDTO updateProfile(Long memberId, MemberRequestDTO.MemberUpdateDTO requestDTO); + MemberResponseDTO.MemberUpdateDTO updateProfile(Long memberId, MemberRequestDTO.MemberUpdateDTO requestDTO); } diff --git a/src/main/java/com/example/spot/member/application/MemberPreferenceService.java b/src/main/java/com/example/spot/member/application/MemberPreferenceService.java new file mode 100644 index 00000000..2ecfba79 --- /dev/null +++ b/src/main/java/com/example/spot/member/application/MemberPreferenceService.java @@ -0,0 +1,22 @@ +package com.example.spot.member.application; + +import static com.example.spot.member.presentation.dto.MemberResponseDTO.MemberRegionDTO; +import static com.example.spot.member.presentation.dto.MemberResponseDTO.MemberStudyReasonDTO; +import static com.example.spot.member.presentation.dto.MemberResponseDTO.MemberThemeDTO; +import static com.example.spot.member.presentation.dto.MemberResponseDTO.MemberUpdateDTO; + +import com.example.spot.member.presentation.dto.MemberRequestDTO; + +public interface MemberPreferenceService { + MemberUpdateDTO updateTheme(Long memberId, MemberRequestDTO.MemberThemeDTO dto); + + MemberUpdateDTO updateRegion(Long memberId, MemberRequestDTO.MemberRegionDTO dto); + + MemberUpdateDTO updateStudyReason(Long memberId, MemberRequestDTO.MemberReasonDTO dto); + + MemberThemeDTO getThemes(Long memberId); + + MemberRegionDTO getRegions(Long memberId); + + MemberStudyReasonDTO getStudyReasons(Long memberId); +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/member/application/MemberTestSupportService.java b/src/main/java/com/example/spot/member/application/MemberTestSupportService.java new file mode 100644 index 00000000..adc1dc85 --- /dev/null +++ b/src/main/java/com/example/spot/member/application/MemberTestSupportService.java @@ -0,0 +1,13 @@ +package com.example.spot.member.application; + +import com.example.spot.member.presentation.dto.MemberRequestDTO; +import com.example.spot.member.presentation.dto.MemberResponseDTO; + +public interface MemberTestSupportService { + + // 테스트 용 멤버 생성 + MemberResponseDTO.MemberTestDTO testMember(MemberRequestDTO.MemberInfoListDTO memberInfoListDTO); + + MemberResponseDTO.MemberUpdateDTO toAdmin(Long memberId); + +} diff --git a/src/main/java/com/example/spot/member/application/refactor/impl/MemberInfoServiceImpl.java b/src/main/java/com/example/spot/member/application/impl/MemberInfoServiceImpl.java similarity index 93% rename from src/main/java/com/example/spot/member/application/refactor/impl/MemberInfoServiceImpl.java rename to src/main/java/com/example/spot/member/application/impl/MemberInfoServiceImpl.java index 63b1dab1..eba07245 100644 --- a/src/main/java/com/example/spot/member/application/refactor/impl/MemberInfoServiceImpl.java +++ b/src/main/java/com/example/spot/member/application/impl/MemberInfoServiceImpl.java @@ -1,7 +1,7 @@ -package com.example.spot.member.application.refactor.impl; +package com.example.spot.member.application.impl; import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.member.application.refactor.MemberInfoService; +import com.example.spot.member.application.MemberInfoService; import com.example.spot.member.domain.Member; import com.example.spot.member.infrastructure.MemberRepository; import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberUpdateDTO; diff --git a/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java b/src/main/java/com/example/spot/member/application/impl/MemberPreferenceServiceImpl.java similarity index 98% rename from src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java rename to src/main/java/com/example/spot/member/application/impl/MemberPreferenceServiceImpl.java index d1b9253b..b5a3a382 100644 --- a/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java +++ b/src/main/java/com/example/spot/member/application/impl/MemberPreferenceServiceImpl.java @@ -1,11 +1,11 @@ -package com.example.spot.member.application.refactor.impl; +package com.example.spot.member.application.impl; import static com.example.spot.member.presentation.dto.MemberResponseDTO.MemberRegionDTO.RegionDTO; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.GeneralException; import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.member.application.refactor.MemberPreferenceService; +import com.example.spot.member.application.MemberPreferenceService; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.association.PreferredRegion; import com.example.spot.member.domain.association.PreferredTheme; diff --git a/src/main/java/com/example/spot/member/application/refactor/impl/MemberTestSupportServiceImpl.java b/src/main/java/com/example/spot/member/application/impl/MemberTestSupportServiceImpl.java similarity index 94% rename from src/main/java/com/example/spot/member/application/refactor/impl/MemberTestSupportServiceImpl.java rename to src/main/java/com/example/spot/member/application/impl/MemberTestSupportServiceImpl.java index 565f467b..e2e441ec 100644 --- a/src/main/java/com/example/spot/member/application/refactor/impl/MemberTestSupportServiceImpl.java +++ b/src/main/java/com/example/spot/member/application/impl/MemberTestSupportServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.member.application.refactor.impl; +package com.example.spot.member.application.impl; import com.example.spot.auth.domain.RefreshToken; import com.example.spot.auth.domain.RefreshTokenRepository; @@ -6,7 +6,7 @@ import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.security.utils.JwtTokenProvider; -import com.example.spot.member.application.refactor.MemberTestSupportService; +import com.example.spot.member.application.MemberTestSupportService; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.LoginType; @@ -116,9 +116,9 @@ public MemberResponseDTO.MemberUpdateDTO toAdmin(Long memberId) { */ private void saveRefreshToken(Member member, TokenResponseDTO.TokenDTO token) { // 기존 리프레시 토큰 삭제 - if (refreshTokenRepository.existsByMemberId(member.getId())) { - refreshTokenRepository.deleteAllByMemberId(member.getId()); - } + if (refreshTokenRepository.existsByMemberId(member.getId())) { + refreshTokenRepository.deleteAllByMemberId(member.getId()); + } // DB에 저장하기 위한 새로운 리프레시 토큰 객체 생성 RefreshToken refreshToken = RefreshToken.builder() diff --git a/src/main/java/com/example/spot/member/application/legacy/MemberService.java b/src/main/java/com/example/spot/member/application/legacy/MemberService.java deleted file mode 100644 index 87967b95..00000000 --- a/src/main/java/com/example/spot/member/application/legacy/MemberService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.spot.member.application.legacy; - - -import com.example.spot.member.domain.Member; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberInfoListDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberReasonDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberRegionDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberThemeDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberUpdateDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.fasterxml.jackson.core.JsonProcessingException; -import java.io.IOException; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.transaction.annotation.Transactional; - -// TODO 추후 삭제 예정 -> 구글 로그인 관련 로직이 남아있음 -@Deprecated -public interface MemberService extends UserDetailsService { - - @Transactional - void save(Member member); -} - diff --git a/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java b/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java deleted file mode 100644 index 02533627..00000000 --- a/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.example.spot.member.application.legacy; - -import com.example.spot.auth.domain.CustomUserDetails; -import com.example.spot.member.domain.Member; -import com.example.spot.member.infrastructure.MemberRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -// TODO 추후 삭제 예정 -> 구글 로그인 관련 로직이 남아있음 - -@Service -@Slf4j -@Transactional -@RequiredArgsConstructor -@Deprecated -public class MemberServiceImpl implements MemberService { - - private final MemberRepository memberRepository; - - /** - * 회원의 정보를 조회합니다. - * - * @param username 회원 식별자(ID) - * @return 회원 정보 - * @throws UsernameNotFoundException 회원을 찾을 수 없을 경우 - */ - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - // 회원 ID Long 타입으로 변환 - Long memberId = parseUsernameToMemberId(username); - - // 회원 조회 - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다.")); - - // 권한 설정 -> ROLE_USER 또는 ROLE_ADMIN - List authorities = List.of( - new SimpleGrantedAuthority("ROLE_" + (member.getIsAdmin() ? "ADMIN" : "USER")) - ); - - // CustomUserDetails 객체 생성 - return CustomUserDetails.builder() - .email(member.getEmail()) - .memberId(member.getId()) - .password(member.getPassword()) - .enabled(true) - .authorities(authorities) - .build(); - } - - /** - * 문자열로 입력된 회원 ID를 Long 타입으로 파싱합니다. - * - * @param username 회원 ID 문자열 - * @return 회원 ID - * @throws UsernameNotFoundException 회원 ID 형식이 잘못된 경우 - */ - private Long parseUsernameToMemberId(String username) { - try { - return Long.parseLong(username); - } catch (NumberFormatException e) { - throw new UsernameNotFoundException("Invalid user ID format"); - } - } - - - @Override - @Transactional - public void save(Member member) { - memberRepository.save(member); - } - -} diff --git a/src/main/java/com/example/spot/member/application/refactor/MemberPreferenceService.java b/src/main/java/com/example/spot/member/application/refactor/MemberPreferenceService.java deleted file mode 100644 index 2c0ca15d..00000000 --- a/src/main/java/com/example/spot/member/application/refactor/MemberPreferenceService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.example.spot.member.application.refactor; - -import static com.example.spot.member.presentation.dto.MemberResponseDTO.*; - -import com.example.spot.member.presentation.dto.MemberRequestDTO; - -public interface MemberPreferenceService { - MemberUpdateDTO updateTheme(Long memberId, MemberRequestDTO.MemberThemeDTO dto); - MemberUpdateDTO updateRegion(Long memberId, MemberRequestDTO.MemberRegionDTO dto); - MemberUpdateDTO updateStudyReason(Long memberId, MemberRequestDTO.MemberReasonDTO dto); - - MemberThemeDTO getThemes(Long memberId); - MemberRegionDTO getRegions(Long memberId); - MemberStudyReasonDTO getStudyReasons(Long memberId); -} \ No newline at end of file diff --git a/src/main/java/com/example/spot/member/application/refactor/MemberTestSupportService.java b/src/main/java/com/example/spot/member/application/refactor/MemberTestSupportService.java deleted file mode 100644 index 9e9f7cd3..00000000 --- a/src/main/java/com/example/spot/member/application/refactor/MemberTestSupportService.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.spot.member.application.refactor; - -import com.example.spot.member.presentation.dto.MemberRequestDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO; - -public interface MemberTestSupportService { - - // 테스트 용 멤버 생성 - MemberResponseDTO.MemberTestDTO testMember(MemberRequestDTO.MemberInfoListDTO memberInfoListDTO); - - MemberResponseDTO.MemberUpdateDTO toAdmin(Long memberId); - -} diff --git a/src/main/java/com/example/spot/member/presentation/controller/legacy/MemberController.java b/src/main/java/com/example/spot/member/presentation/controller/legacy/MemberController.java deleted file mode 100644 index 1547dfaa..00000000 --- a/src/main/java/com/example/spot/member/presentation/controller/legacy/MemberController.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.example.spot.member.presentation.controller.legacy; - -import com.example.spot.common.api.ApiResponse; -import com.example.spot.common.api.code.status.SuccessStatus; - -import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.member.application.legacy.MemberService; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberRegionDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberStudyReasonDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberTestDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberUpdateDTO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import jakarta.validation.Valid; -import org.springframework.validation.annotation.Validated; -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 static com.example.spot.auth.presentation.dto.google.GoogleExampleResponse.EXAMPLE_RESPONSE; - -// TODO : 이 클래스는 구글 로그인 API를 위한 레거시 컨트롤러입니다. 추후 리팩토링 예정입니다. - -@Deprecated -@RestController -@RequestMapping("/spot") -@RequiredArgsConstructor -@Validated -public class MemberController { - - private final MemberService memberService; - - @Tag(name = "구글 로그인 API", description = "구글 OAuth2 로그인 API") - @Operation(summary = "[구글 로그인] 구글 로그인/회원가입 API", - description = """ - 구글 로그인 인증 페이지로 이동합니다. - 사용자가 로그인 후, 설정된 리디렉션 URL로 돌아옵니다. - 브라우저에서 직접 요청해 주세요. - ## http://localhost:8080/oauth2/authorization/google - ## www.teamspot.site/oauth2/authorization/google - """) - @GetMapping("/oauth/authorize") - public void redirectToGoogleLogin(HttpServletRequest request, HttpServletResponse response) { - response.setStatus(HttpServletResponse.SC_FOUND); - response.setHeader("Location", "/oauth/authorize"); - } - - @Tag(name = "구글 로그인 API", description = "구글 OAuth2 로그인 API") - @Operation(summary = "[구글 로그인] 구글 로그인/회원가입 리다이렉트용 API", - description = """ - 구글 로그인 인증 완료 후 호출되는 콜백 URL입니다. - 클라이언트가 직접 호출하지 않습니다. - 로그인 성공 시 회원의 이메일과 토큰 정보를 반환합니다. - """) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "google OAuth 로그인에 성공하면 SPOT 서버에 접근할 수 있는 SPOT JWT Token을 반환합니다.", - content = @Content( - mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = @Schema(implementation = ApiResponses.class), - examples = @ExampleObject( - value = EXAMPLE_RESPONSE - )))}) - @GetMapping("/members/sign-in/google/redirect") - public void handleGoogleCallback() { - } - -} diff --git a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberInfoController.java b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberInfoController.java index 28a3364d..dda25447 100644 --- a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberInfoController.java +++ b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberInfoController.java @@ -1,23 +1,21 @@ package com.example.spot.member.presentation.controller.refactor; -import org.springframework.validation.annotation.Validated; -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 com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.member.application.refactor.MemberInfoService; +import com.example.spot.member.application.MemberInfoService; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +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; @RestController @RequestMapping("/spot") @@ -25,21 +23,22 @@ @Validated public class MemberInfoController { - private final MemberInfoService memberInfoService; + private final MemberInfoService memberInfoService; - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @PostMapping("/members/user-info") - @Operation(summary = "[회원 정보 업데이트] 개인 정보 입력 및 수정", - description = """ - ## [회원 정보 업데이트] 해당하는 회원의 개인 정보를 입력 및 수정 합니다. - 업데이트 할 회원의 정보를 입력 받습니다. - 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse updateMemberInfo( - @RequestBody @Valid MemberRequestDTO.MemberUpdateDTO requestDTO){ - MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberInfoService.updateProfile(SecurityUtils.getCurrentUserId(), requestDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_INFO_UPDATE, memberUpdateDTO); - } + @Tag(name = "회원 관리 API", description = "회원 관리 API") + @PostMapping("/members/user-info") + @Operation(summary = "[회원 정보 업데이트] 개인 정보 입력 및 수정", + description = """ + ## [회원 정보 업데이트] 해당하는 회원의 개인 정보를 입력 및 수정 합니다. + 업데이트 할 회원의 정보를 입력 받습니다. + 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse updateMemberInfo( + @RequestBody @Valid MemberRequestDTO.MemberUpdateDTO requestDTO) { + MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberInfoService.updateProfile( + SecurityUtils.getCurrentUserId(), requestDTO); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_INFO_UPDATE, memberUpdateDTO); + } } diff --git a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberPreferenceController.java b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberPreferenceController.java index eb937753..74f956c6 100644 --- a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberPreferenceController.java +++ b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberPreferenceController.java @@ -1,24 +1,22 @@ package com.example.spot.member.presentation.controller.refactor; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -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 com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.member.application.refactor.MemberPreferenceService; +import com.example.spot.member.application.MemberPreferenceService; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +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; @RestController @RequestMapping("/spot") @@ -26,102 +24,108 @@ @Validated public class MemberPreferenceController { - private final MemberPreferenceService memberPreferenceService; + private final MemberPreferenceService memberPreferenceService; - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @PostMapping("/members/theme") - @Operation(summary = "[회원 정보 업데이트] 관심 분야 입력 및 수정", - description = """ - ## [회원 정보 업데이트] 해당하는 회원의 관심 분야를 입력 및 수정 합니다. - 테마를 리스트 형식으로 입력 받습니다. - 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse updateThemes( - @RequestBody @Valid MemberRequestDTO.MemberThemeDTO requestDTO){ - MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateTheme(SecurityUtils.getCurrentUserId(), requestDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_THEME_UPDATE, memberUpdateDTO); - } + @Tag(name = "회원 관리 API", description = "회원 관리 API") + @PostMapping("/members/theme") + @Operation(summary = "[회원 정보 업데이트] 관심 분야 입력 및 수정", + description = """ + ## [회원 정보 업데이트] 해당하는 회원의 관심 분야를 입력 및 수정 합니다. + 테마를 리스트 형식으로 입력 받습니다. + 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse updateThemes( + @RequestBody @Valid MemberRequestDTO.MemberThemeDTO requestDTO) { + MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateTheme( + SecurityUtils.getCurrentUserId(), requestDTO); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_THEME_UPDATE, memberUpdateDTO); + } - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @PostMapping("/members/region") - @Operation(summary = "[회원 정보 업데이트] 관심 지역 입력 및 수정", - description = """ - ## [회원 정보 업데이트] 해당하는 회원의 관심 지역을 입력 및 수정 합니다. - 지역 코드를 리스트 형식으로 입력 받습니다. - 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse updateRegions( - @RequestBody @Valid MemberRequestDTO.MemberRegionDTO requestDTO){ - MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateRegion(SecurityUtils.getCurrentUserId(), requestDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_REGION_UPDATE, memberUpdateDTO); - } + @Tag(name = "회원 관리 API", description = "회원 관리 API") + @PostMapping("/members/region") + @Operation(summary = "[회원 정보 업데이트] 관심 지역 입력 및 수정", + description = """ + ## [회원 정보 업데이트] 해당하는 회원의 관심 지역을 입력 및 수정 합니다. + 지역 코드를 리스트 형식으로 입력 받습니다. + 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse updateRegions( + @RequestBody @Valid MemberRequestDTO.MemberRegionDTO requestDTO) { + MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateRegion( + SecurityUtils.getCurrentUserId(), requestDTO); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_REGION_UPDATE, memberUpdateDTO); + } - @Tag(name = "회원 관리 API", description = "회원 관리 API") - @PostMapping("/members/study-reasons") - @Operation(summary = "[회원 정보 업데이트] 스터디 이유 입력 및 수정", - description = """ - ## [회원 정보 업데이트] 해당하는 회원의 스터디 이유를 입력 및 수정 합니다. - 업데이트 할 회원의 정보를 입력 받습니다. - - 꾸준한 학습, 습관이필요해요(1) \n - 상호 피드백이 필요해요(2), \n - 네트워킹을 하고 싶어요(3), \n - 자격증을 취득하고 싶어요(4), \n - 대회에 참가하여 수상하고 싶어요(5),\n - 다양한 의견을 나누고 싶어요(6); \n - - 이유에 해당하는 숫자를 리스트 형식으로 입력 받습니다. - - 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse updateMemberStudyReason( - @RequestBody @Valid MemberRequestDTO.MemberReasonDTO requestDTO){ - MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateStudyReason(SecurityUtils.getCurrentUserId(), requestDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_INFO_UPDATE, memberUpdateDTO); - } + @Tag(name = "회원 관리 API", description = "회원 관리 API") + @PostMapping("/members/study-reasons") + @Operation(summary = "[회원 정보 업데이트] 스터디 이유 입력 및 수정", + description = """ + ## [회원 정보 업데이트] 해당하는 회원의 스터디 이유를 입력 및 수정 합니다. + 업데이트 할 회원의 정보를 입력 받습니다. + + 꾸준한 학습, 습관이필요해요(1) \n + 상호 피드백이 필요해요(2), \n + 네트워킹을 하고 싶어요(3), \n + 자격증을 취득하고 싶어요(4), \n + 대회에 참가하여 수상하고 싶어요(5),\n + 다양한 의견을 나누고 싶어요(6); \n + + 이유에 해당하는 숫자를 리스트 형식으로 입력 받습니다. + + 대상 회원의 식별 아이디와 수정 시각이 반환 됩니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse updateMemberStudyReason( + @RequestBody @Valid MemberRequestDTO.MemberReasonDTO requestDTO) { + MemberResponseDTO.MemberUpdateDTO memberUpdateDTO = memberPreferenceService.updateStudyReason( + SecurityUtils.getCurrentUserId(), requestDTO); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_INFO_UPDATE, memberUpdateDTO); + } - @Tag(name = "회원 조회 API", description = "회원 조회 API") - @GetMapping("/members/theme") - @Operation(summary = "[회원 정보 조회] 관심 분야 조회", - description = """ - ## [회원 정보 조회] 해당하는 회원의 관심 분야를 조회 합니다. - - 관심 분야를 리스트 형식으로 응답합니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse getThemes(){ - MemberResponseDTO.MemberThemeDTO memberThemeDTO = memberPreferenceService.getThemes(SecurityUtils.getCurrentUserId()); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_THEME_UPDATE, memberThemeDTO); - } + @Tag(name = "회원 조회 API", description = "회원 조회 API") + @GetMapping("/members/theme") + @Operation(summary = "[회원 정보 조회] 관심 분야 조회", + description = """ + ## [회원 정보 조회] 해당하는 회원의 관심 분야를 조회 합니다. + + 관심 분야를 리스트 형식으로 응답합니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse getThemes() { + MemberResponseDTO.MemberThemeDTO memberThemeDTO = memberPreferenceService.getThemes( + SecurityUtils.getCurrentUserId()); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_THEME_UPDATE, memberThemeDTO); + } - @Tag(name = "회원 조회 API", description = "회원 조회 API") - @GetMapping("/members/region") - @Operation(summary = "[회원 정보 조회] 관심 지역 조회", - description = """ - ## [회원 정보 조회] 해당하는 회원의 관심 지역을 조회 합니다. - - 관심 지역을 리스트 형식으로 응답합니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse getRegions(){ - MemberResponseDTO.MemberRegionDTO memberRegionDTO = memberPreferenceService.getRegions(SecurityUtils.getCurrentUserId()); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_REGION_UPDATE, memberRegionDTO); - } + @Tag(name = "회원 조회 API", description = "회원 조회 API") + @GetMapping("/members/region") + @Operation(summary = "[회원 정보 조회] 관심 지역 조회", + description = """ + ## [회원 정보 조회] 해당하는 회원의 관심 지역을 조회 합니다. + + 관심 지역을 리스트 형식으로 응답합니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse getRegions() { + MemberResponseDTO.MemberRegionDTO memberRegionDTO = memberPreferenceService.getRegions( + SecurityUtils.getCurrentUserId()); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_REGION_UPDATE, memberRegionDTO); + } - @Tag(name = "회원 조회 API", description = "회원 조회 API") - @GetMapping("/members/study-reasons") - @Operation(summary = "[회원 정보 조회] 스터디 이유 조회", - description = """ - ## [회원 정보 조회] 해당하는 회원의 스터디 이유를 조회 합니다. - - 스터디 이유를 리스트 형식으로 응답합니다. - """, - security = @SecurityRequirement(name = "accessToken")) - public ApiResponse getStudyReasons(){ - MemberResponseDTO.MemberStudyReasonDTO memberStudyReasonDTO = memberPreferenceService.getStudyReasons(SecurityUtils.getCurrentUserId()); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_FOUND, memberStudyReasonDTO); - } + @Tag(name = "회원 조회 API", description = "회원 조회 API") + @GetMapping("/members/study-reasons") + @Operation(summary = "[회원 정보 조회] 스터디 이유 조회", + description = """ + ## [회원 정보 조회] 해당하는 회원의 스터디 이유를 조회 합니다. + + 스터디 이유를 리스트 형식으로 응답합니다. + """, + security = @SecurityRequirement(name = "accessToken")) + public ApiResponse getStudyReasons() { + MemberResponseDTO.MemberStudyReasonDTO memberStudyReasonDTO = memberPreferenceService.getStudyReasons( + SecurityUtils.getCurrentUserId()); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_FOUND, memberStudyReasonDTO); + } } diff --git a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberTestSupportController.java b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberTestSupportController.java index 31da33b8..ede2dde6 100644 --- a/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberTestSupportController.java +++ b/src/main/java/com/example/spot/member/presentation/controller/refactor/MemberTestSupportController.java @@ -1,22 +1,20 @@ package com.example.spot.member.presentation.controller.refactor; -import org.springframework.validation.annotation.Validated; -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 com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.member.application.refactor.MemberTestSupportService; +import com.example.spot.member.application.MemberTestSupportService; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +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; @RestController @RequestMapping("/spot") @@ -24,33 +22,33 @@ @Validated public class MemberTestSupportController { - private final MemberTestSupportService memberTestSupportService; + private final MemberTestSupportService memberTestSupportService; - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!테스트 용! [회원 생성] 테스트 용 회원 생성 API", - description = """ - ## [테스트 용 회원 생성] 임의의 정보를 가진 회원 객체가 생성 됩니다. - 다른 API를 테스트 하기 위해 회원이 필요한 경우 사용해주세요. - 회원의 관심 분야 및 지역을 입력 받습니다. - 생성된 회원의 ID와 Email이 반환 됩니다. """) - @PostMapping("/members/test") - public ApiResponse testMember( - @RequestBody @Valid MemberRequestDTO.MemberInfoListDTO memberInfoListDTO){ - MemberResponseDTO.MemberTestDTO dto = memberTestSupportService.testMember(memberInfoListDTO); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); - } + @Tag(name = "테스트 용 API", description = "테스트 용 API") + @Operation(summary = "!테스트 용! [회원 생성] 테스트 용 회원 생성 API", + description = """ + ## [테스트 용 회원 생성] 임의의 정보를 가진 회원 객체가 생성 됩니다. + 다른 API를 테스트 하기 위해 회원이 필요한 경우 사용해주세요. + 회원의 관심 분야 및 지역을 입력 받습니다. + 생성된 회원의 ID와 Email이 반환 됩니다. """) + @PostMapping("/members/test") + public ApiResponse testMember( + @RequestBody @Valid MemberRequestDTO.MemberInfoListDTO memberInfoListDTO) { + MemberResponseDTO.MemberTestDTO dto = memberTestSupportService.testMember(memberInfoListDTO); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); + } - @Tag(name = "테스트 용 API", description = "테스트 용 API") - @Operation(summary = "!테스트 용! [회원 권한 부여] 관리자 권한 부여 API", - description = """ - ## [회원 권한 부여] 해당하는 회원에게 관리자 권한을 부여합니다. - 테스트를 위해 구현한 테스트 용 API입니다. - 회원의 ID를 입력 받아 관리자 권한을 부여합니다. - 성공 여부와 회원 ID가 반환 됩니다. - """) - @PostMapping("/members/test/admin") - public ApiResponse toAdmin(){ - MemberResponseDTO.MemberUpdateDTO dto = memberTestSupportService.toAdmin(SecurityUtils.getCurrentUserId()); - return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); - } + @Tag(name = "테스트 용 API", description = "테스트 용 API") + @Operation(summary = "!테스트 용! [회원 권한 부여] 관리자 권한 부여 API", + description = """ + ## [회원 권한 부여] 해당하는 회원에게 관리자 권한을 부여합니다. + 테스트를 위해 구현한 테스트 용 API입니다. + 회원의 ID를 입력 받아 관리자 권한을 부여합니다. + 성공 여부와 회원 ID가 반환 됩니다. + """) + @PostMapping("/members/test/admin") + public ApiResponse toAdmin() { + MemberResponseDTO.MemberUpdateDTO dto = memberTestSupportService.toAdmin(SecurityUtils.getCurrentUserId()); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_CREATED, dto); + } } From e97dd5e9ad156fe37eb16767210ce83c68a112c1 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:52:18 +0900 Subject: [PATCH 26/31] =?UTF-8?q?[SPOT-301][REFACTOR]=20OAuth=20=EC=A0=84?= =?UTF-8?q?=EB=9E=B5=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20=EC=98=A4=ED=83=80=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 --- .../spot/auth/application/refactor/OAuthService.java | 2 ++ .../application/refactor/{ => strategy}/OAuthStrategy.java | 3 ++- .../refactor/{ => strategy}/OAuthStrategyFactory.java | 2 +- .../straegy => strategy/provider}/GoogleOAuthStrategy.java | 6 +++--- .../straegy => strategy/provider}/KakaoOAuthStrategy.java | 6 +++--- .../straegy => strategy/provider}/NaverOAuthStrategy.java | 6 +++--- .../refactor/impl => infrastructure}/oauth/GoogleOauth.java | 2 +- .../refactor/impl => infrastructure}/oauth/KaKaoOauth.java | 2 +- .../refactor/impl => infrastructure}/oauth/NaverOauth.java | 2 +- 9 files changed, 17 insertions(+), 14 deletions(-) rename src/main/java/com/example/spot/auth/application/refactor/{ => strategy}/OAuthStrategy.java (80%) rename src/main/java/com/example/spot/auth/application/refactor/{ => strategy}/OAuthStrategyFactory.java (93%) rename src/main/java/com/example/spot/auth/application/refactor/{impl/straegy => strategy/provider}/GoogleOAuthStrategy.java (82%) rename src/main/java/com/example/spot/auth/application/refactor/{impl/straegy => strategy/provider}/KakaoOAuthStrategy.java (83%) rename src/main/java/com/example/spot/auth/application/refactor/{impl/straegy => strategy/provider}/NaverOAuthStrategy.java (83%) rename src/main/java/com/example/spot/auth/{application/refactor/impl => infrastructure}/oauth/GoogleOauth.java (98%) rename src/main/java/com/example/spot/auth/{application/refactor/impl => infrastructure}/oauth/KaKaoOauth.java (97%) rename src/main/java/com/example/spot/auth/{application/refactor/impl => infrastructure}/oauth/NaverOauth.java (98%) diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java b/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java index 08c58a55..fbb0ce6c 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java +++ b/src/main/java/com/example/spot/auth/application/refactor/OAuthService.java @@ -1,6 +1,8 @@ package com.example.spot.auth.application.refactor; import com.example.spot.auth.application.refactor.impl.OAuthMemberProcessor; +import com.example.spot.auth.application.refactor.strategy.OAuthStrategy; +import com.example.spot.auth.application.refactor.strategy.OAuthStrategyFactory; import com.example.spot.member.domain.enums.LoginType; import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategy.java similarity index 80% rename from src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java rename to src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategy.java index 3f447421..673ae864 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategy.java @@ -1,9 +1,10 @@ -package com.example.spot.auth.application.refactor; +package com.example.spot.auth.application.refactor.strategy; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.enums.LoginType; public interface OAuthStrategy { + LoginType getType(); String getOauthRedirectURL(); diff --git a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategyFactory.java similarity index 93% rename from src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java rename to src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategyFactory.java index 75b13e35..4a9b647a 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/OAuthStrategyFactory.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/OAuthStrategyFactory.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.application.refactor; +package com.example.spot.auth.application.refactor.strategy; import com.example.spot.auth.exception.UnsupportedSocialLoginTypeException; import com.example.spot.common.api.code.status.ErrorStatus; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java similarity index 82% rename from src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java rename to src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java index e7a7fd82..443a6270 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/GoogleOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java @@ -1,7 +1,7 @@ -package com.example.spot.auth.application.refactor.impl.straegy; +package com.example.spot.auth.application.refactor.strategy.provider; -import com.example.spot.auth.application.refactor.OAuthStrategy; -import com.example.spot.auth.application.refactor.impl.oauth.GoogleOauth; +import com.example.spot.auth.application.refactor.strategy.OAuthStrategy; +import com.example.spot.auth.infrastructure.oauth.GoogleOauth; import com.example.spot.auth.presentation.dto.oauth.google.GoogleOAuthToken; import com.example.spot.auth.presentation.dto.oauth.google.GoogleUser; import com.example.spot.member.domain.Member; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java similarity index 83% rename from src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java rename to src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java index a92bb93c..c1c1cd28 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/KakaoOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java @@ -1,7 +1,7 @@ -package com.example.spot.auth.application.refactor.impl.straegy; +package com.example.spot.auth.application.refactor.strategy.provider; -import com.example.spot.auth.application.refactor.OAuthStrategy; -import com.example.spot.auth.application.refactor.impl.oauth.KaKaoOauth; +import com.example.spot.auth.application.refactor.strategy.OAuthStrategy; +import com.example.spot.auth.infrastructure.oauth.KaKaoOauth; import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoOAuthTokenDTO; import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoUser; import com.example.spot.member.domain.Member; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java similarity index 83% rename from src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java rename to src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java index 5e556c83..e081d408 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/straegy/NaverOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java @@ -1,7 +1,7 @@ -package com.example.spot.auth.application.refactor.impl.straegy; +package com.example.spot.auth.application.refactor.strategy.provider; -import com.example.spot.auth.application.refactor.OAuthStrategy; -import com.example.spot.auth.application.refactor.impl.oauth.NaverOauth; +import com.example.spot.auth.application.refactor.strategy.OAuthStrategy; +import com.example.spot.auth.infrastructure.oauth.NaverOauth; import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthTokenDTO; import com.example.spot.auth.presentation.dto.oauth.naver.NaverUser; import com.example.spot.member.domain.Member; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java similarity index 98% rename from src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java rename to src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java index 509b4546..a09e1ba7 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/GoogleOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.application.refactor.impl.oauth; +package com.example.spot.auth.infrastructure.oauth; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java similarity index 97% rename from src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java rename to src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java index e177d47b..c0786abd 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/KaKaoOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.application.refactor.impl.oauth; +package com.example.spot.auth.infrastructure.oauth; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; diff --git a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java similarity index 98% rename from src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java rename to src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java index eff1ed89..50fb30a4 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/impl/oauth/NaverOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java @@ -1,4 +1,4 @@ -package com.example.spot.auth.application.refactor.impl.oauth; +package com.example.spot.auth.infrastructure.oauth; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; From ff4b0f030fe871b1394f3c8e38b7396a2f75d3a8 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 15:53:52 +0900 Subject: [PATCH 27/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/refactor/OAuthController.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/com/example/spot/auth/presentation/controller/refactor/OAuthController.java diff --git a/src/main/java/com/example/spot/auth/presentation/controller/refactor/OAuthController.java b/src/main/java/com/example/spot/auth/presentation/controller/refactor/OAuthController.java new file mode 100644 index 00000000..355dc59a --- /dev/null +++ b/src/main/java/com/example/spot/auth/presentation/controller/refactor/OAuthController.java @@ -0,0 +1,42 @@ +package com.example.spot.auth.presentation.controller.refactor; + +import com.example.spot.auth.application.refactor.OAuthService; +import com.example.spot.member.domain.enums.LoginType; +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/oauth") +public class OAuthController { + + private final OAuthService oAuthService; + + @Tag(name = "소셜 로그인 API") + @Operation(summary = "소셜 로그인 리다이렉트 URL 반환", + description = "소셜 로그인 타입에 따라 리다이렉트 URL을 반환합니다. " + + "예: /api/oauth/redirect-url/naver") + @GetMapping("/redirect-url/{type}") + public String getRedirectUrl(@PathVariable("type") String type) { + return oAuthService.redirectURL(LoginType.valueOf(type.toUpperCase())); + } + + @Tag(name = "소셜 로그인 API") + @Operation(summary = "소셜 로그인 콜백 처리", + description = "소셜 로그인 후 받은 code를 통해 로그인 또는 회원가입을 처리합니다. " + + "예: /api/oauth/callback/naver?code=YOUR_CODE") + @GetMapping("/callback/{type}") + public MemberResponseDTO.SocialLoginSignInDTO socialLoginCallback( + @PathVariable("type") String type, + @RequestParam("code") String code) { + + return oAuthService.loginOrSignUp(LoginType.valueOf(type.toUpperCase()), code); + } +} \ No newline at end of file From a39857edaaa29d4eff9681c87616fbf9f78135f7 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 16:06:12 +0900 Subject: [PATCH 28/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=20API=20=ED=98=B8=EC=B6=9C=20=EA=B4=80=EB=A0=A8=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/client/kakao/KaKaoAuthClient.java | 3 ++- .../infrastructure/client/naver/NaverApiClient.java | 4 +++- .../infrastructure/client/naver/NaverAuthClient.java | 10 +++++++--- .../auth/infrastructure/constants/AuthConstants.java | 4 +++- .../spot/auth/infrastructure/oauth/GoogleOauth.java | 4 ++-- .../spot/auth/infrastructure/oauth/KaKaoOauth.java | 4 ++-- .../spot/auth/infrastructure/oauth/NaverOauth.java | 4 ++-- 7 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java index 05ae8637..007f124e 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/kakao/KaKaoAuthClient.java @@ -1,5 +1,6 @@ package com.example.spot.auth.infrastructure.client.kakao; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_CONTENT_TYPE; import com.example.spot.auth.presentation.dto.oauth.kakao.KaKaoOAuthTokenDTO; @@ -17,5 +18,5 @@ KaKaoOAuthTokenDTO getKaKaoAccessToken( @RequestParam String grant_type, @RequestParam String redirectUri, @RequestParam String client_id, - @RequestParam(defaultValue = "authorization_code") String code); + @RequestParam(defaultValue = GRANT_TYPE_AUTHORIZATION_CODE) String code); } diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java index 05ebbe3b..fb0ccca8 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverApiClient.java @@ -1,5 +1,7 @@ package com.example.spot.auth.infrastructure.client.naver; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.HEADER_AUTHORIZATION; + import com.example.spot.auth.presentation.dto.oauth.naver.NaverUser; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @@ -10,5 +12,5 @@ public interface NaverApiClient { @GetMapping("/v1/nid/me") NaverUser getNaverUserInfo( - @RequestHeader("Authorization") String accessToken); + @RequestHeader(HEADER_AUTHORIZATION) String accessToken); } diff --git a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java index bc0807d5..1d7897eb 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java +++ b/src/main/java/com/example/spot/auth/infrastructure/client/naver/NaverAuthClient.java @@ -1,5 +1,9 @@ package com.example.spot.auth.infrastructure.client.naver; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_SECRET; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; + import com.example.spot.auth.presentation.dto.oauth.naver.NaverOAuthTokenDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @@ -10,9 +14,9 @@ public interface NaverAuthClient { @GetMapping("/token") NaverOAuthTokenDTO getNaverAccessToken( - @RequestParam("grant_type") String grantType, - @RequestParam("client_id") String clientId, - @RequestParam("client_secret") String clientSecret, + @RequestParam(GRANT_TYPE) String grantType, + @RequestParam(CLIENT_ID) String clientId, + @RequestParam(CLIENT_SECRET) String clientSecret, @RequestParam String code, @RequestParam String state ); diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java index 470858f4..4d39d530 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java @@ -4,7 +4,8 @@ public class AuthConstants { public static final String CONTENT_TYPE = "application/x-www-form-urlencoded;charset=utf-8"; public static final String APPLICATION_JSON = "application/json"; - public static final String GRANT_TYPE = "authorization_code"; + public static final String GRANT_TYPE = "grant_type"; + public static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"; public static final String CLIENT_ID = "client_id"; public static final String REDIRECT_URI = "redirect_uri"; @@ -16,6 +17,7 @@ public class AuthConstants { public static final String SCOPE = "scope"; public static final String STATE = "state"; public static final String STATE_STRING = "STATE_STRING"; + public static final String CLIENT_SECRET = "client_secret"; public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String KAKAO_AUTHORIZATION = "KakaoAK "; diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java index a09e1ba7..47673172 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java @@ -2,7 +2,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; -import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; @@ -60,7 +60,7 @@ public GoogleOAuthToken requestAccessToken(String code) { return googleAuthClient.getGoogleAccessToken(CONTENT_TYPE, code, GOOGLE_SNS_CLIENT_ID, GOOGLE_SNS_CLIENT_SECRET, - GOOGLE_SNS_CALLBACK_LOGIN_URL, GRANT_TYPE); + GOOGLE_SNS_CALLBACK_LOGIN_URL, GRANT_TYPE_AUTHORIZATION_CODE); } public GoogleUser requestUserInfo(GoogleOAuthToken oAuthToken) { diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java index c0786abd..1adf73a7 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java @@ -2,7 +2,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CONTENT_TYPE; -import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; @@ -53,7 +53,7 @@ public String getOauthRedirectURL() { public KaKaoOAuthTokenDTO requestAccessToken(String code) { return kaKaoAuthClient.getKaKaoAccessToken( - CONTENT_TYPE, GRANT_TYPE, KAKAO_SNS_CALLBACK_LOGIN_URL, KAKAO_SNS_CLIENT_ID, code); + CONTENT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE, KAKAO_SNS_CALLBACK_LOGIN_URL, KAKAO_SNS_CLIENT_ID, code); } diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java index 50fb30a4..ad242875 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java @@ -1,7 +1,7 @@ package com.example.spot.auth.infrastructure.oauth; import static com.example.spot.auth.infrastructure.constants.AuthConstants.CLIENT_ID; -import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE; +import static com.example.spot.auth.infrastructure.constants.AuthConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.KEY_VALUE_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_DELIMITER; import static com.example.spot.auth.infrastructure.constants.AuthConstants.QUERY_PREFIX; @@ -58,7 +58,7 @@ public String getOauthRedirectURL() { public NaverOAuthTokenDTO requestAccessToken(String code) { return naverAuthClient.getNaverAccessToken( - GRANT_TYPE, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, code, STATE); + GRANT_TYPE_AUTHORIZATION_CODE, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, code, STATE); } From e322df331aa8c05c6a5ab338a25da24c906d6e19 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 17:35:07 +0900 Subject: [PATCH 29/31] =?UTF-8?q?[SPOT-301][FEATURE]=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=83=9D=EC=84=B1=20=ED=8C=A9=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=AA=85=20=EB=82=B4=20OAuth=20?= =?UTF-8?q?=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../refactor/strategy/provider/GoogleOAuthStrategy.java | 2 +- .../refactor/strategy/provider/KakaoOAuthStrategy.java | 2 +- .../refactor/strategy/provider/NaverOAuthStrategy.java | 2 +- src/main/java/com/example/spot/member/domain/Member.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java index 443a6270..6829ce51 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/GoogleOAuthStrategy.java @@ -29,6 +29,6 @@ public String getOauthRedirectURL() { public Member toMember(String code) { GoogleOAuthToken token = googleOauth.requestAccessToken(code); GoogleUser user = googleOauth.requestUserInfo(token); - return Member.toMember(getType(), user.name(), user.email(), user.picture()); + return Member.toMemberByOAuth(getType(), user.name(), user.email(), user.picture()); } } diff --git a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java index c1c1cd28..1dbcebe7 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/KakaoOAuthStrategy.java @@ -29,7 +29,7 @@ public String getOauthRedirectURL() { public Member toMember(String code) { KaKaoOAuthTokenDTO token = kaKaoOauth.requestAccessToken(code); KaKaoUser user = kaKaoOauth.requestUserInfo(token); - return Member.toMember(getType(), user.properties().nickname(), user.properties().nickname(), + return Member.toMemberByOAuth(getType(), user.properties().nickname(), user.properties().nickname(), user.properties().profile_image()); } } diff --git a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java index e081d408..f8ed9adc 100644 --- a/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java +++ b/src/main/java/com/example/spot/auth/application/refactor/strategy/provider/NaverOAuthStrategy.java @@ -29,7 +29,7 @@ public String getOauthRedirectURL() { public Member toMember(String code) { NaverOAuthTokenDTO token = naverOauth.requestAccessToken(code); NaverUser user = naverOauth.requestUserInfo(token); - return Member.toMember( + return Member.toMemberByOAuth( getType(), user.response().name(), user.response().email(), user.response().thumbnail_image()); } diff --git a/src/main/java/com/example/spot/member/domain/Member.java b/src/main/java/com/example/spot/member/domain/Member.java index cef4139a..8bf4cdc7 100644 --- a/src/main/java/com/example/spot/member/domain/Member.java +++ b/src/main/java/com/example/spot/member/domain/Member.java @@ -96,7 +96,7 @@ public class Member extends BaseEntity { private Status status; - public static Member toMember(LoginType loginType, String name, String email, String profileImage) { + public static Member toMemberByOAuth(LoginType loginType, String name, String email, String profileImage) { return Member.builder() .name(name) .nickname(name) From 2456096bd1f615ba08cccf547a64a02b78ca13f7 Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 18:00:20 +0900 Subject: [PATCH 30/31] =?UTF-8?q?[SPOT-301][FEATURE]=20Feign=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EB=A5=BC=20=ED=86=B5=EC=9D=BC=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EB=9E=98=ED=8D=BC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/oauth/GoogleOauth.java | 9 ++++--- .../auth/infrastructure/oauth/KaKaoOauth.java | 9 ++++--- .../auth/infrastructure/oauth/NaverOauth.java | 9 ++++--- .../exception/base/ExternalApiException.java | 7 +++++ .../feign/SafeFeignExecutor.java | 26 +++++++++++++++++++ 5 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/example/spot/common/api/exception/base/ExternalApiException.java create mode 100644 src/main/java/com/example/spot/common/infrastructure/feign/SafeFeignExecutor.java diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java index 47673172..742dbeaa 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/GoogleOauth.java @@ -11,6 +11,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE_CODE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.SCOPE; import static com.example.spot.auth.infrastructure.constants.JwtConstants.BEARER_PREFIX; +import static com.example.spot.common.infrastructure.feign.SafeFeignExecutor.run; import com.example.spot.auth.infrastructure.client.google.GoogleApiClient; import com.example.spot.auth.infrastructure.client.google.GoogleAuthClient; @@ -58,12 +59,12 @@ public String getOauthRedirectURL() { public GoogleOAuthToken requestAccessToken(String code) { - return googleAuthClient.getGoogleAccessToken(CONTENT_TYPE, code, - GOOGLE_SNS_CLIENT_ID, GOOGLE_SNS_CLIENT_SECRET, - GOOGLE_SNS_CALLBACK_LOGIN_URL, GRANT_TYPE_AUTHORIZATION_CODE); + return run(() -> googleAuthClient.getGoogleAccessToken( + CONTENT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE, GOOGLE_SNS_CALLBACK_LOGIN_URL, + GOOGLE_SNS_CLIENT_ID, GOOGLE_SNS_CLIENT_SECRET, code)); } public GoogleUser requestUserInfo(GoogleOAuthToken oAuthToken) { - return googleApiClient.getGoogleUserInfo(BEARER_PREFIX + oAuthToken.access_token()); + return run(() -> googleApiClient.getGoogleUserInfo(BEARER_PREFIX + oAuthToken.access_token())); } } diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java index 1adf73a7..b821ab3e 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/KaKaoOauth.java @@ -9,6 +9,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.REDIRECT_URI; import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.RESPONSE_TYPE_CODE; +import static com.example.spot.common.infrastructure.feign.SafeFeignExecutor.run; import com.example.spot.auth.infrastructure.client.kakao.KaKaoApiClient; import com.example.spot.auth.infrastructure.client.kakao.KaKaoAuthClient; @@ -52,14 +53,14 @@ public String getOauthRedirectURL() { } public KaKaoOAuthTokenDTO requestAccessToken(String code) { - return kaKaoAuthClient.getKaKaoAccessToken( - CONTENT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE, KAKAO_SNS_CALLBACK_LOGIN_URL, KAKAO_SNS_CLIENT_ID, code); + return run(() -> kaKaoAuthClient.getKaKaoAccessToken( + CONTENT_TYPE, GRANT_TYPE_AUTHORIZATION_CODE, KAKAO_SNS_CALLBACK_LOGIN_URL, KAKAO_SNS_CLIENT_ID, code)); } public KaKaoUser requestUserInfo(KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO) { - return kaKaoApiClient.getKaKaoUserInfo( - getAccessToken(kaKaoOAuthTokenDTO), CONTENT_TYPE); + return run(() -> kaKaoApiClient.getKaKaoUserInfo( + getAccessToken(kaKaoOAuthTokenDTO), CONTENT_TYPE)); } private static String getAccessToken(KaKaoOAuthTokenDTO kaKaoOAuthTokenDTO) { diff --git a/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java index ad242875..2ddbcdc5 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java +++ b/src/main/java/com/example/spot/auth/infrastructure/oauth/NaverOauth.java @@ -11,6 +11,7 @@ import static com.example.spot.auth.infrastructure.constants.AuthConstants.STATE; import static com.example.spot.auth.infrastructure.constants.AuthConstants.STATE_STRING; import static com.example.spot.auth.infrastructure.constants.JwtConstants.BEARER_PREFIX; +import static com.example.spot.common.infrastructure.feign.SafeFeignExecutor.run; import com.example.spot.auth.infrastructure.client.naver.NaverApiClient; import com.example.spot.auth.infrastructure.client.naver.NaverAuthClient; @@ -57,14 +58,14 @@ public String getOauthRedirectURL() { } public NaverOAuthTokenDTO requestAccessToken(String code) { - return naverAuthClient.getNaverAccessToken( - GRANT_TYPE_AUTHORIZATION_CODE, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, code, STATE); + return run(() -> naverAuthClient.getNaverAccessToken( + GRANT_TYPE_AUTHORIZATION_CODE, NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, NAVER_CALLBACK_LOGIN_URL, code)); } public NaverUser requestUserInfo(NaverOAuthTokenDTO naverOAuthTokenDTO) { - return naverApiClient.getNaverUserInfo( - getAccessToken(naverOAuthTokenDTO)); + return run(() -> naverApiClient.getNaverUserInfo( + getAccessToken(naverOAuthTokenDTO))); } private static String getAccessToken(NaverOAuthTokenDTO naverOAuthTokenDTO) { diff --git a/src/main/java/com/example/spot/common/api/exception/base/ExternalApiException.java b/src/main/java/com/example/spot/common/api/exception/base/ExternalApiException.java new file mode 100644 index 00000000..e2bf31e7 --- /dev/null +++ b/src/main/java/com/example/spot/common/api/exception/base/ExternalApiException.java @@ -0,0 +1,7 @@ +package com.example.spot.common.api.exception.base; + +public class ExternalApiException extends RuntimeException { + public ExternalApiException(String s, Exception e) { + super(s, e); + } +} diff --git a/src/main/java/com/example/spot/common/infrastructure/feign/SafeFeignExecutor.java b/src/main/java/com/example/spot/common/infrastructure/feign/SafeFeignExecutor.java new file mode 100644 index 00000000..1b380242 --- /dev/null +++ b/src/main/java/com/example/spot/common/infrastructure/feign/SafeFeignExecutor.java @@ -0,0 +1,26 @@ +package com.example.spot.common.infrastructure.feign; + +import com.example.spot.common.api.exception.base.ExternalApiException; +import feign.FeignException; +import java.util.function.Supplier; + +public final class SafeFeignExecutor { + + private SafeFeignExecutor() { + } + + public static T run(Supplier call) { + try { + return call.get(); + } catch (FeignException e) { + throw new ExternalApiException( + "Feign API 호출 실패: " + extractMessage(e), e); + } + } + + private static String extractMessage(FeignException e) { + return e.responseBody() + .map(body -> new String(body.array())) // byte[] → String + .orElse(e.getMessage()); + } +} \ No newline at end of file From 86aaaf58def3513cf198876d0feaa2d8866e9d7b Mon Sep 17 00:00:00 2001 From: msk226 Date: Fri, 1 Aug 2025 18:01:06 +0900 Subject: [PATCH 31/31] =?UTF-8?q?[SPOT-301][FEATURE]=20constants=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20abstract?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/auth/infrastructure/constants/AuthConstants.java | 2 +- .../spot/auth/infrastructure/constants/JwtConstants.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java index 4d39d530..c481e311 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/AuthConstants.java @@ -1,6 +1,6 @@ package com.example.spot.auth.infrastructure.constants; -public class AuthConstants { +public abstract class AuthConstants { public static final String CONTENT_TYPE = "application/x-www-form-urlencoded;charset=utf-8"; public static final String APPLICATION_JSON = "application/json"; diff --git a/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java b/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java index 1052e0de..8da1a610 100644 --- a/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java +++ b/src/main/java/com/example/spot/auth/infrastructure/constants/JwtConstants.java @@ -1,6 +1,6 @@ package com.example.spot.auth.infrastructure.constants; -public class JwtConstants { +public abstract class JwtConstants { public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String BEARER_PREFIX = "Bearer ";