From 8f8520cb817751ac7387903036a7dabed39d2d82 Mon Sep 17 00:00:00 2001 From: DongGeon0908 Date: Tue, 30 Jul 2024 10:30:10 +0900 Subject: [PATCH] fix: get kakao user info --- .../alignlab/client/kakao/KakaoInfoService.kt | 22 +++++++++ .../client/kakao/client/KaKaoInfoClient.kt | 7 +++ .../client/SuspendableKakaoInfoClient.kt | 14 ++++++ .../client/SuspendableKakaoOAuthClient.kt | 6 +-- .../kakao/config/KakaoInfoClientConfig.kt | 45 +++++++++++++++++ .../kakao/config/KakaoOAuthClientConfig.kt | 2 +- .../response/KakaoOAuthUserInfoResponse.kt | 49 +++++++++++++++++++ .../domain/auth/resource/OAuthResource.kt | 19 ------- .../domain/dev/application/DevOAuthService.kt | 7 +++ .../domain/dev/resource/DevOAuthResource.kt | 6 +++ src/main/resources/config/application.yml | 4 ++ 11 files changed, 156 insertions(+), 25 deletions(-) create mode 100644 src/main/kotlin/com/hero/alignlab/client/kakao/KakaoInfoService.kt create mode 100644 src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoInfoClient.kt create mode 100644 src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoInfoClient.kt create mode 100644 src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoInfoClientConfig.kt create mode 100644 src/main/kotlin/com/hero/alignlab/client/kakao/model/response/KakaoOAuthUserInfoResponse.kt diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoInfoService.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoInfoService.kt new file mode 100644 index 0000000..be16823 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoInfoService.kt @@ -0,0 +1,22 @@ +package com.hero.alignlab.client.kakao + +import com.hero.alignlab.client.kakao.client.KaKaoInfoClient +import com.hero.alignlab.client.kakao.model.response.KakaoOAuthUserInfoResponse +import com.hero.alignlab.common.extension.resolveCancellation +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Service + +@Service +class KakaoInfoService( + private val kaKaoInfoClient: KaKaoInfoClient, +) { + private val logger = KotlinLogging.logger { } + + suspend fun getUserInfo(accessToken: String): KakaoOAuthUserInfoResponse { + return runCatching { + kaKaoInfoClient.getUserInfo(accessToken) + }.onFailure { e -> + logger.resolveCancellation("getUserInfo", e) + }.getOrThrow() + } +} diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoInfoClient.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoInfoClient.kt new file mode 100644 index 0000000..b39ff0d --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoInfoClient.kt @@ -0,0 +1,7 @@ +package com.hero.alignlab.client.kakao.client + +import com.hero.alignlab.client.kakao.model.response.KakaoOAuthUserInfoResponse + +interface KaKaoInfoClient { + suspend fun getUserInfo(accessToken: String): KakaoOAuthUserInfoResponse +} diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoInfoClient.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoInfoClient.kt new file mode 100644 index 0000000..6b1a86e --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoInfoClient.kt @@ -0,0 +1,14 @@ +package com.hero.alignlab.client.kakao.client + +import com.hero.alignlab.client.kakao.SuspendableClient +import com.hero.alignlab.client.kakao.model.response.KakaoOAuthUserInfoResponse +import org.springframework.web.reactive.function.client.WebClient + +class SuspendableKakaoInfoClient(client: WebClient) : KaKaoInfoClient, SuspendableClient(client) { + override suspend fun getUserInfo(accessToken: String): KakaoOAuthUserInfoResponse { + return client.get() + .uri("/v2/user/me") + .header("Authorization", "Bearer $accessToken") + .request() + } +} diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoOAuthClient.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoOAuthClient.kt index 4c4afa0..6d1e7c3 100644 --- a/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoOAuthClient.kt +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/client/SuspendableKakaoOAuthClient.kt @@ -1,15 +1,11 @@ package com.hero.alignlab.client.kakao.client import com.hero.alignlab.client.kakao.SuspendableClient -import com.hero.alignlab.client.kakao.config.KakaoOAuthClientConfig import com.hero.alignlab.client.kakao.model.request.GenerateKakaoOAuthTokenRequest import com.hero.alignlab.client.kakao.model.response.GenerateKakaoOAuthTokenResponse import org.springframework.web.reactive.function.client.WebClient -class SuspendableKakaoOAuthClient( - client: WebClient, - private val config: KakaoOAuthClientConfig.Config, -) : KaKaoOAuthClient, SuspendableClient(client) { +class SuspendableKakaoOAuthClient(client: WebClient) : KaKaoOAuthClient, SuspendableClient(client) { override suspend fun getOAuthAuthorizeCode(clientId: String, redirectUri: String) { client .get() diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoInfoClientConfig.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoInfoClientConfig.kt new file mode 100644 index 0000000..bd39206 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoInfoClientConfig.kt @@ -0,0 +1,45 @@ +package com.hero.alignlab.client.kakao.config + +import com.hero.alignlab.client.WebClientFactory +import com.hero.alignlab.client.kakao.client.KaKaoInfoClient +import com.hero.alignlab.client.kakao.client.KaKaoOAuthClient +import com.hero.alignlab.client.kakao.client.SuspendableKakaoInfoClient +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.validation.annotation.Validated + +@Validated +@Configuration +class KakaoInfoClientConfig { + private val logger = KotlinLogging.logger { } + + @Bean + @ConditionalOnProperty(prefix = "client.kakao-info", name = ["url"]) + @ConfigurationProperties(prefix = "client.kakao-info") + fun kakaoInfoConfig() = Config() + + @Bean + @ConditionalOnBean(name = ["kakaoInfoConfig"]) + @ConditionalOnMissingBean(KaKaoOAuthClient::class) + fun kakaoInfoClient( + @Valid kakaoInfoConfig: Config + ): KaKaoInfoClient { + logger.info { "initialized KaKaoInfoClient. $kakaoInfoConfig" } + + val webclient = WebClientFactory.generate(kakaoInfoConfig.url) + + return SuspendableKakaoInfoClient(webclient) + } + + data class Config( + @field:NotBlank + var url: String = "", + ) +} diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoOAuthClientConfig.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoOAuthClientConfig.kt index 0d37170..fad6b84 100644 --- a/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoOAuthClientConfig.kt +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/config/KakaoOAuthClientConfig.kt @@ -34,7 +34,7 @@ class KakaoOAuthClientConfig { val webclient = WebClientFactory.generate(kakaoOAuthConfig.url) - return SuspendableKakaoOAuthClient(webclient, kakaoOAuthConfig) + return SuspendableKakaoOAuthClient(webclient) } data class Config( diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/KakaoOAuthUserInfoResponse.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/KakaoOAuthUserInfoResponse.kt new file mode 100644 index 0000000..4a48e2b --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/KakaoOAuthUserInfoResponse.kt @@ -0,0 +1,49 @@ +package com.hero.alignlab.client.kakao.model.response + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming +import java.time.LocalDateTime + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) +data class KakaoOAuthUserInfoResponse( + val id: String, + val hasSignedUp: Boolean?, + val connectedAt: LocalDateTime?, + val synchedAt: LocalDateTime?, + val kakaoAccount: KakaoAccount?, +) { + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) + data class KakaoAccount( + val profileNeedsAgreement: Boolean?, + val profile: Profile?, + val nameNeedsAgreement: Boolean?, + val name: String?, + val emailNeedsAgreement: Boolean?, + val isEmailValid: Boolean?, + val isEmailVerified: Boolean?, + val email: String?, + val ageRangeNeedsAgreement: Boolean?, + val ageRange: String?, + val birthyearNeedsAgreement: Boolean?, + val birthyear: String?, + val birthdayNeedsAgreement: Boolean?, + val birthday: String?, + val birthdayType: String?, + val genderNeedsAgreement: Boolean?, + val gender: String?, + val phoneNumberNeedsAgreement: Boolean?, + val phoneNumber: String?, + val ciNeedsAgreement: Boolean?, + val ci: String?, + val ciAuthenticatedAt: LocalDateTime?, + ) + + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) + data class Profile( + val nickname: String?, + val thumbnailImageUrl: String?, + val profileImageUrl: String?, + val isDefaultImage: Boolean?, + val isDefaultNickname: Boolean?, + ) +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/auth/resource/OAuthResource.kt b/src/main/kotlin/com/hero/alignlab/domain/auth/resource/OAuthResource.kt index eb7cf0d..d75862d 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/auth/resource/OAuthResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/auth/resource/OAuthResource.kt @@ -1,10 +1,6 @@ package com.hero.alignlab.domain.auth.resource -import com.hero.alignlab.common.extension.wrapOk import com.hero.alignlab.domain.auth.application.OAuthFacade -import com.hero.alignlab.domain.auth.model.OAuthProvider -import com.hero.alignlab.domain.auth.model.request.OAuthAuthorizedRequest -import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.MediaType import org.springframework.web.bind.annotation.* @@ -18,20 +14,5 @@ import org.springframework.web.bind.annotation.* class OAuthResource( private val oAuthFacade: OAuthFacade, ) { - /** - * OAuth로 인가코드를 부여받고, 이를 Redirect Url로 반환. - * - redirectUrl은 Client의 주소값, 만약 변경시 yml 및 각 클라이언트 구조 변경 필요. - */ - @Operation(summary = "인가 코드 받기") - @PostMapping("/api/v1/oauth/{provider}/authorize") - suspend fun getOAuthAuthorizeCode( - @PathVariable provider: OAuthProvider, - ) = oAuthFacade.getOAuthAuthorizeCode(provider).wrapOk() - @Operation(summary = "회원가입 또는 로그인 진행") - @PostMapping("/api/v1/oauth/{provider}/authorized") - suspend fun resolveOAuth( - @PathVariable provider: OAuthProvider, - @RequestBody request: OAuthAuthorizedRequest, - ) = oAuthFacade.resolveOAuth(provider, request).wrapOk() } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/application/DevOAuthService.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/application/DevOAuthService.kt index ca27b0b..970d2bc 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/application/DevOAuthService.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/application/DevOAuthService.kt @@ -1,7 +1,9 @@ package com.hero.alignlab.domain.dev.application +import com.hero.alignlab.client.kakao.KakaoInfoService import com.hero.alignlab.client.kakao.KakaoOAuthService import com.hero.alignlab.client.kakao.model.response.GenerateKakaoOAuthTokenResponse +import com.hero.alignlab.client.kakao.model.response.KakaoOAuthUserInfoResponse import com.hero.alignlab.domain.auth.model.OAuthProvider import com.hero.alignlab.domain.dev.model.response.DevOAuthCodeResponse import io.github.oshai.kotlinlogging.KotlinLogging @@ -10,6 +12,7 @@ import org.springframework.stereotype.Service @Service class DevOAuthService( private val kakaoOAuthService: KakaoOAuthService, + private val kakaoInfoService: KakaoInfoService ) { private val logger = KotlinLogging.logger { } @@ -26,4 +29,8 @@ class DevOAuthService( OAuthProvider.kakao -> kakaoOAuthService.generateOAuthToken(code) } } + + suspend fun getUserInfo(accessToken: String): KakaoOAuthUserInfoResponse { + return kakaoInfoService.getUserInfo(accessToken) + } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevOAuthResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevOAuthResource.kt index f0ff982..058c57e 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevOAuthResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevOAuthResource.kt @@ -26,4 +26,10 @@ class DevOAuthResource( @PathVariable provider: OAuthProvider, @RequestParam code: String, ) = devOAuthService.resolveOAuth(provider, code).wrapOk() + + @Operation(summary = "사용자 정보 조회") + @GetMapping("/api/dev/v1/oauth/user") + suspend fun getOAuthUserInfos( + @RequestParam accessToken: String + ) = devOAuthService.getUserInfo(accessToken).wrapOk() } diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index 1342e15..bce5838 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -56,6 +56,10 @@ oauth: authorized-url: /authorize?client_id=%s&redirect_uri=%s&response_type=code redirect-url: https://api.alignlab.site/api/dev/v1/oauth/kakao/token +client: + kakao-info: + url: https://kapi.kakao.com + encrypt: key: algorithm: