diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoOAuthService.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoOAuthService.kt new file mode 100644 index 0000000..4490420 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/KakaoOAuthService.kt @@ -0,0 +1,37 @@ +package com.hero.alignlab.client.kakao + +import com.hero.alignlab.client.kakao.client.KaKaoOAuthClient +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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.springframework.stereotype.Service + +@Service +class KakaoOAuthService( + private val kaKaoOAuthClient: KaKaoOAuthClient, + private val config: KakaoOAuthClientConfig.Config +) { + suspend fun getOAuthAuthorizeCode(redirectUrl: String? = null) { + withContext(Dispatchers.IO) { + kaKaoOAuthClient.getOAuthAuthorizeCode() + } + } + + suspend fun generateOAuthToken(code: String, redirectUri: String? = null): GenerateKakaoOAuthTokenResponse { + val request = GenerateKakaoOAuthTokenRequest( + clientId = config.clientSecretCode, + redirectUri = redirectUri ?: config.redirectUrl, + code = code, + ) + + return generateOAuthToken(request) + } + + suspend fun generateOAuthToken(request: GenerateKakaoOAuthTokenRequest): GenerateKakaoOAuthTokenResponse { + return withContext(Dispatchers.IO) { + kaKaoOAuthClient.generateOAuthToken(request) + } + } +} diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoOAuthClient.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoOAuthClient.kt index 24c4f6d..0fea29d 100644 --- a/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoOAuthClient.kt +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/client/KaKaoOAuthClient.kt @@ -1,5 +1,10 @@ package com.hero.alignlab.client.kakao.client +import com.hero.alignlab.client.kakao.model.request.GenerateKakaoOAuthTokenRequest +import com.hero.alignlab.client.kakao.model.response.GenerateKakaoOAuthTokenResponse + interface KaKaoOAuthClient { suspend fun getOAuthAuthorizeCode(redirectUrl: String? = null) + + suspend fun generateOAuthToken(request: GenerateKakaoOAuthTokenRequest): GenerateKakaoOAuthTokenResponse } 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 b424b0c..d1f67d8 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 @@ -2,6 +2,9 @@ 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.http.MediaType import org.springframework.web.reactive.function.client.WebClient class SuspendableKakaoOAuthClient( @@ -19,4 +22,13 @@ class SuspendableKakaoOAuthClient( .build() }.requestOrNull() } + + override suspend fun generateOAuthToken(request: GenerateKakaoOAuthTokenRequest): GenerateKakaoOAuthTokenResponse { + return client + .post() + .uri("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .bodyValue(request) + .request() + } } diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/model/request/GenerateKakaoOAuthTokenRequest.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/model/request/GenerateKakaoOAuthTokenRequest.kt new file mode 100644 index 0000000..3083570 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/model/request/GenerateKakaoOAuthTokenRequest.kt @@ -0,0 +1,13 @@ +package com.hero.alignlab.client.kakao.model.request + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) +data class GenerateKakaoOAuthTokenRequest( + val grantType: String = "authorization_code", + val clientId: String, + val redirectUri: String, + val code: String, + val clientSecret: String? = null +) diff --git a/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/GenerateKakaoOAuthTokenResponse.kt b/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/GenerateKakaoOAuthTokenResponse.kt new file mode 100644 index 0000000..2018e3e --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/client/kakao/model/response/GenerateKakaoOAuthTokenResponse.kt @@ -0,0 +1,15 @@ +package com.hero.alignlab.client.kakao.model.response + +import com.fasterxml.jackson.databind.PropertyNamingStrategies +import com.fasterxml.jackson.databind.annotation.JsonNaming + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class) +data class GenerateKakaoOAuthTokenResponse( + val tokenType: String, + val accessToken: String, + val idToken: String?, + val expiresIn: Int, + val refreshToken: String, + val refreshTokenExpiresIn: Int, + val scope: String? +) diff --git a/src/main/kotlin/com/hero/alignlab/domain/auth/application/OAuthFacade.kt b/src/main/kotlin/com/hero/alignlab/domain/auth/application/OAuthFacade.kt index bcd2c21..64c9b52 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/auth/application/OAuthFacade.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/auth/application/OAuthFacade.kt @@ -1,22 +1,32 @@ package com.hero.alignlab.domain.auth.application -import com.hero.alignlab.client.kakao.client.KaKaoOAuthClient +import com.hero.alignlab.client.kakao.KakaoOAuthService +import com.hero.alignlab.common.extension.toJson import com.hero.alignlab.domain.auth.model.OAuthProvider +import com.hero.alignlab.domain.auth.model.request.OAuthAuthorizedRequest import com.hero.alignlab.domain.auth.model.response.OAuthAuthorizeCodeResponse import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service @Service class OAuthFacade( - private val kaKaoOAuthClient: KaKaoOAuthClient, + private val kakaoOAuthService: KakaoOAuthService, ) { private val logger = KotlinLogging.logger { } suspend fun getOAuthAuthorizeCode(provider: OAuthProvider): OAuthAuthorizeCodeResponse { when (provider) { - OAuthProvider.kakao -> kaKaoOAuthClient.getOAuthAuthorizeCode() + OAuthProvider.kakao -> kakaoOAuthService.getOAuthAuthorizeCode() } return OAuthAuthorizeCodeResponse(provider) } + + suspend fun resolveOAuth(provider: OAuthProvider, request: OAuthAuthorizedRequest) { + val response = when (provider) { + OAuthProvider.kakao -> kakaoOAuthService.generateOAuthToken(request.code) + } + + println("response > " + response.toJson()) + } } diff --git a/src/main/kotlin/com/hero/alignlab/domain/auth/model/request/OAuthAuthorizedRequest.kt b/src/main/kotlin/com/hero/alignlab/domain/auth/model/request/OAuthAuthorizedRequest.kt new file mode 100644 index 0000000..ed386ab --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/auth/model/request/OAuthAuthorizedRequest.kt @@ -0,0 +1,5 @@ +package com.hero.alignlab.domain.auth.model.request + +data class OAuthAuthorizedRequest( + val code: String, +) 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 17703ca..eb7cf0d 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 @@ -3,14 +3,15 @@ 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.GetMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +/** + * - [Kakao Rest Auth](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api) + */ @Tag(name = "OAuth 인증 및 인가 관리") @RestController @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) @@ -20,11 +21,17 @@ class OAuthResource( /** * OAuth로 인가코드를 부여받고, 이를 Redirect Url로 반환. * - redirectUrl은 Client의 주소값, 만약 변경시 yml 및 각 클라이언트 구조 변경 필요. - * - [Kakao Rest Auth](https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api) */ @Operation(summary = "인가 코드 받기") - @GetMapping("/api/v1/oauth/{provider}/authorize") + @PostMapping("/api/v1/oauth/{provider}/authorize") suspend fun getOAuthAuthorizeCode( - @RequestParam provider: OAuthProvider, + @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 03f27bf..2c435ba 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,24 +1,37 @@ package com.hero.alignlab.domain.dev.application +import com.hero.alignlab.client.kakao.KakaoOAuthService import com.hero.alignlab.client.kakao.client.KaKaoOAuthClient import com.hero.alignlab.client.kakao.config.KakaoOAuthClientConfig +import com.hero.alignlab.client.kakao.model.response.GenerateKakaoOAuthTokenResponse +import com.hero.alignlab.common.extension.toJson import com.hero.alignlab.domain.auth.model.OAuthProvider +import com.hero.alignlab.domain.auth.model.request.OAuthAuthorizedRequest import com.hero.alignlab.domain.dev.model.response.DevOAuthCodeResponse import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service @Service class DevOAuthService( - private val kaKaoOAuthClient: KaKaoOAuthClient, + private val kakaoOAuthService: KakaoOAuthService, private val config: KakaoOAuthClientConfig.Config, ) { private val logger = KotlinLogging.logger { } suspend fun getOAuthAuthorizeCode(provider: OAuthProvider): DevOAuthCodeResponse { when (provider) { - OAuthProvider.kakao -> kaKaoOAuthClient.getOAuthAuthorizeCode(config.devRedirectUrl) + OAuthProvider.kakao -> kakaoOAuthService.getOAuthAuthorizeCode(config.devRedirectUrl) } return DevOAuthCodeResponse(provider) } + + suspend fun resolveOAuth( + provider: OAuthProvider, + request: OAuthAuthorizedRequest + ): GenerateKakaoOAuthTokenResponse { + return when (provider) { + OAuthProvider.kakao -> kakaoOAuthService.generateOAuthToken(request.code) + } + } } 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 d109cdc..8033d3b 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 @@ -2,6 +2,7 @@ package com.hero.alignlab.domain.dev.resource import com.hero.alignlab.common.extension.wrapOk import com.hero.alignlab.domain.auth.model.OAuthProvider +import com.hero.alignlab.domain.auth.model.request.OAuthAuthorizedRequest import com.hero.alignlab.domain.dev.application.DevOAuthService import com.hero.alignlab.domain.dev.model.request.DevRedirectedRequest import com.hero.alignlab.domain.dev.model.response.DevRedirectedResponse @@ -9,10 +10,7 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springdoc.core.annotations.ParameterObject import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @Tag(name = "Dev OAuth 인증 및 인가 관리") @RestController @@ -21,13 +19,13 @@ class DevOAuthResource( private val devOAuthService: DevOAuthService, ) { @Operation(summary = "인가 코드 받기") - @GetMapping("/api/dev/v1/oauth/{provider}/authorize") + @PostMapping("/api/dev/v1/oauth/{provider}/authorize") suspend fun getDevOAuthAuthorizeCode( @PathVariable provider: OAuthProvider, ) = devOAuthService.getOAuthAuthorizeCode(provider).wrapOk() @Operation(summary = "OAuth Redirect Test") - @GetMapping("/api/dev/v1/oauth/{provider}/authorize/redirected") + @PostMapping("/api/dev/v1/oauth/{provider}/authorize/redirected") suspend fun redirectedDevOAuthAuthorizeCode( @PathVariable provider: OAuthProvider, @ParameterObject request: DevRedirectedRequest, @@ -35,4 +33,11 @@ class DevOAuthResource( provider = provider, requestParams = request, ).wrapOk() + + @Operation(summary = "OAuth Token Generate") + @PostMapping("/api/dev/v1/oauth/{provider}/token") + suspend fun redirectedDevOAuthAuthorizeCode( + @PathVariable provider: OAuthProvider, + @RequestBody request: OAuthAuthorizedRequest, + ) = devOAuthService.resolveOAuth(provider, request).wrapOk() }