From 5fcfa1eaf463018e94a1a81df5008404b98c793e Mon Sep 17 00:00:00 2001 From: kih00 Date: Tue, 21 Jan 2025 22:39:17 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memoWithTags/user/GoogleUtil.kt | 88 +++++++++++++++++++ .../user/controller/SocialLoginController.kt | 6 +- .../memoWithTags/user/dto/GoogleUser.kt | 20 +++++ .../user/service/SocialLoginService.kt | 46 +++++++++- src/main/resources/application.yml | 6 ++ 5 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/GoogleUtil.kt create mode 100644 src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/dto/GoogleUser.kt diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/GoogleUtil.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/GoogleUtil.kt new file mode 100644 index 0000000..8f625c8 --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/GoogleUtil.kt @@ -0,0 +1,88 @@ +package com.wafflestudio.toyproject.memoWithTags.user + +import com.fasterxml.jackson.core.JsonProcessingException +import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException +import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleOAuthToken +import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleProfile +import org.slf4j.LoggerFactory +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.MediaType +import org.springframework.stereotype.Component +import org.springframework.util.MultiValueMap +import org.springframework.web.client.RestTemplate + +@Component +class GoogleUtil( + @Value("\${google.auth.client-id}") + private val googleClientId: String, + @Value("\${google.auth.client-secret}") + private val googleClientSecret: String, + @Value("\${google.auth.redirect}") + private val googleRedirectUri: String, +) { + private val logger = LoggerFactory.getLogger(GoogleUtil::class.java) + + fun requestToken(accessCode: String): GoogleOAuthToken { + val restTemplate = RestTemplate() + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_JSON + + val params = mapOf( + "grant_type" to "authorization_code", + "client_id" to googleClientId, + "client_secret" to googleClientSecret, + "redirect_uri" to googleRedirectUri, + "code" to accessCode + ) + + val googleTokenRequest = HttpEntity(params, headers) + logger.info("Token Request: $googleTokenRequest") + + val response = restTemplate.exchange( + "https://oauth2.googleapis.com/token", + HttpMethod.POST, + googleTokenRequest, + GoogleOAuthToken::class.java + ) + logger.info("Token Response: $response") + + return try { + val oAuthToken = response.body!! + logger.info("oAuthToken: ${oAuthToken.access_token}") + oAuthToken + } catch (e: NullPointerException) { + logger.info("Token processing error: ${e.message}") + throw OAuthRequestException() + } + } + + fun requestProfile(oAuthToken: GoogleOAuthToken): GoogleProfile { + val restTemplate = RestTemplate() + val headers = HttpHeaders() + headers.setBearerAuth(oAuthToken.access_token) + headers.contentType = MediaType.APPLICATION_JSON + + val kakaoProfileRequest: HttpEntity> = HttpEntity(headers) + logger.info("Profile Request: $kakaoProfileRequest") + + val response = restTemplate.exchange( + "https://www.googleapis.com/userinfo/v2/me", + HttpMethod.GET, + kakaoProfileRequest, + GoogleProfile::class.java + ) + logger.info("Profile Response: $response") + + return try { + val googleProfile = response.body!! + logger.info("google email: ${googleProfile.email}") + googleProfile + } catch (e: NullPointerException) { + logger.info("Profile processing error: ${e.message}") + throw OAuthRequestException() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/controller/SocialLoginController.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/controller/SocialLoginController.kt index 2408db5..a7cc837 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/controller/SocialLoginController.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/controller/SocialLoginController.kt @@ -38,6 +38,10 @@ class SocialLoginController( } @GetMapping("/oauth/google") - fun googleCallback() { + fun googleCallback( + @RequestParam("code") code: String + ): ResponseEntity { + val (_, accessToken, refreshToken) = socialLoginService.googleCallback(code) + return ResponseEntity.ok(LoginResponse(accessToken, refreshToken)) } } diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/dto/GoogleUser.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/dto/GoogleUser.kt new file mode 100644 index 0000000..4cf14ef --- /dev/null +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/dto/GoogleUser.kt @@ -0,0 +1,20 @@ +package com.wafflestudio.toyproject.memoWithTags.user.dto + +data class GoogleOAuthToken( + val access_token: String, + val expires_in: Int, + val scope: String, + val token_type: String, + val id_token: String +) + +data class GoogleProfile( + val id: String, + val email: String, + val verified_email: Boolean, + val name: String, + val given_name: String, + val family_name: String, + val picture: String?, + val locale: String? +) \ No newline at end of file diff --git a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/SocialLoginService.kt b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/SocialLoginService.kt index 53bc847..4f9a5f4 100644 --- a/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/SocialLoginService.kt +++ b/src/main/kotlin/com/wafflestudio/toyproject/memoWithTags/user/service/SocialLoginService.kt @@ -1,10 +1,13 @@ package com.wafflestudio.toyproject.memoWithTags.user.service +import com.wafflestudio.toyproject.memoWithTags.user.GoogleUtil import com.wafflestudio.toyproject.memoWithTags.user.JwtUtil import com.wafflestudio.toyproject.memoWithTags.user.KakaoUtil import com.wafflestudio.toyproject.memoWithTags.user.NaverUtil import com.wafflestudio.toyproject.memoWithTags.user.SocialType import com.wafflestudio.toyproject.memoWithTags.user.controller.User +import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleOAuthToken +import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleProfile import com.wafflestudio.toyproject.memoWithTags.user.dto.KakaoOAuthToken import com.wafflestudio.toyproject.memoWithTags.user.dto.KakaoProfile import com.wafflestudio.toyproject.memoWithTags.user.dto.NaverOAuthToken @@ -19,7 +22,8 @@ import java.time.Instant class SocialLoginService( private val userRepository: UserRepository, private val kakaoUtil: KakaoUtil, - private val naverUtil: NaverUtil + private val naverUtil: NaverUtil, + private val googleUtil: GoogleUtil, ) { private val logger = LoggerFactory.getLogger(javaClass) @@ -102,4 +106,44 @@ class SocialLoginService( return User.fromEntity(userEntity) } + + fun googleCallback(accessCode: String): Triple { + val oAuthToken: GoogleOAuthToken = googleUtil.requestToken(accessCode) + val googleProfile: GoogleProfile = googleUtil.requestProfile(oAuthToken) + + val googleEmail = googleProfile.email + val userEntity = userRepository.findByEmail(googleEmail) + val user: User = if (userEntity != null && userEntity.socialType == SocialType.GOOGLE) { + logger.info("google user already exists: ${userEntity.id}, ${userEntity.email}") + User.fromEntity(userEntity) + } else { + logger.info("creating google user $googleEmail") + createGoogleUser(googleProfile) + } + + return Triple( + user, + JwtUtil.generateAccessToken(googleEmail), + JwtUtil.generateRefreshToken(googleEmail) + ) + } + + fun createGoogleUser(profile: GoogleProfile): User { + val googleEmail = profile.email + val googleNickname = profile.name + val encryptedPassword = "google_registered_user" + + val userEntity = userRepository.save( + UserEntity( + email = googleEmail, + nickname = googleNickname, + hashedPassword = encryptedPassword, + verified = true, + socialType = SocialType.GOOGLE, + createdAt = Instant.now() + ) + ) + + return User.fromEntity(userEntity) + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index a8036c7..4713b36 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,12 @@ naver: client-id: ${NAVER_CLIENT_ID} client-secret: ${NAVER_CLIENT_SECRET} +google: + auth: + client-id: ${GOOGLE_CLIENT_ID} + client-secret: ${GOOGLE_CLIENT_SECRET} + redirect: ${GOOGLE_REDIRECT_URI} + springdoc: override-with-generic-response: false # 이후 jwt도 yml에 넣어서 관리 예정 \ No newline at end of file