diff --git a/build.gradle.kts b/build.gradle.kts index 46061c5..e9cca79 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,8 @@ dependencies { // android publisher implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20240319-2.0.0") + implementation("com.google.api-client:google-api-client:1.32.1") + implementation("org.apache.poi:poi-ooxml:5.2.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt index 04b47f8..4431651 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/content/series/recommend/AdminRecommendSeriesRepository.kt @@ -38,6 +38,7 @@ class AdminRecommendSeriesQueryRepositoryImpl( .and(series.isActive.isTrue) .and(recommendSeries.isFree.eq(isFree)) ) + .orderBy(recommendSeries.orders.asc()) .fetch() } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerService.kt index 224c9e8..a762b8c 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/admin/event/banner/AdminEventBannerService.kt @@ -156,8 +156,8 @@ class AdminEventBannerService( ) } - if (!link.isNullOrBlank() && event.link != link) { - event.link = link + if (event.link != link) { + event.link = if (link.isNullOrBlank()) null else link } if (!title.isNullOrBlank() && event.title != title) { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt index 3dda40d..8c4bc9f 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt @@ -70,6 +70,8 @@ class SecurityConfig( .antMatchers("/member/signup").permitAll() .antMatchers("/member/signup/v2").permitAll() .antMatchers("/member/login").permitAll() + .antMatchers("/member/login/google").permitAll() + .antMatchers("/member/login/kakao").permitAll() .antMatchers("/creator-admin/member/login").permitAll() .antMatchers("/member/forgot-password").permitAll() .antMatchers("/stplat/terms_of_service").permitAll() diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt index 42c16a4..2418514 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt @@ -202,7 +202,7 @@ class EventService( ) } - if (!link.isNullOrBlank() && event.link != link) { + if (event.link != link) { event.link = link } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt index c99c541..43921e9 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/Member.kt @@ -24,6 +24,13 @@ data class Member( var nickname: String, var profileImage: String? = null, + val kakaoId: Long? = null, + val googleId: String? = null, + val appleId: String? = null, + + @Enumerated(EnumType.STRING) + val provider: MemberProvider = MemberProvider.EMAIL, + @Enumerated(value = EnumType.STRING) var gender: Gender = Gender.NONE, @@ -140,3 +147,7 @@ enum class Gender { enum class MemberRole { ADMIN, BOT, USER, CREATOR, AGENT } + +enum class MemberProvider { + EMAIL, KAKAO, GOOGLE, APPLE +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt index 04a5d1c..0c55544 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberController.kt @@ -8,8 +8,11 @@ import kr.co.vividnext.sodalive.member.block.MemberBlockRequest import kr.co.vividnext.sodalive.member.following.CreatorFollowRequest import kr.co.vividnext.sodalive.member.login.LoginRequest import kr.co.vividnext.sodalive.member.login.LoginResponse +import kr.co.vividnext.sodalive.member.login.SocialLoginRequest import kr.co.vividnext.sodalive.member.notification.UpdateNotificationSettingRequest import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2 +import kr.co.vividnext.sodalive.member.social.google.GoogleAuthService +import kr.co.vividnext.sodalive.member.social.kakao.KakaoAuthService import org.springframework.data.domain.Pageable import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.security.core.userdetails.User @@ -29,6 +32,8 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/member") class MemberController( private val service: MemberService, + private val kakaoAuthService: KakaoAuthService, + private val googleAuthService: GoogleAuthService, private val trackingService: AdTrackingService ) { @GetMapping("/check/email") @@ -316,4 +321,50 @@ class MemberController( if (member == null) throw SodaException("로그인 정보를 확인해주세요.") ApiResponse.ok(service.getMemberProfile(memberId = id, myMemberId = member.id!!)) } + + @PostMapping("/login/google") + fun loginGoogle( + @RequestHeader("Authorization") authHeader: String, + @RequestBody request: SocialLoginRequest + ): ApiResponse { + if (!authHeader.startsWith("Bearer ")) { + throw SodaException("구글 로그인을 하지 못했습니다. 다시 시도해 주세요") + } + + val token = authHeader.substring(7) + val response = googleAuthService.authenticate(token, request.container, request.marketingPid) + + if (!response.marketingPid.isNullOrBlank()) { + trackingService.saveTrackingHistory( + pid = response.marketingPid, + type = AdTrackingHistoryType.SIGNUP, + memberId = response.memberId + ) + } + + return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) + } + + @PostMapping("/login/kakao") + fun loginKakao( + @RequestHeader("Authorization") authHeader: String, + @RequestBody request: SocialLoginRequest + ): ApiResponse { + if (!authHeader.startsWith("Bearer ")) { + throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요") + } + + val token = authHeader.substring(7) + val response = kakaoAuthService.authenticate(token, request.container, request.marketingPid) + + if (!response.marketingPid.isNullOrBlank()) { + trackingService.saveTrackingHistory( + pid = response.marketingPid, + type = AdTrackingHistoryType.SIGNUP, + memberId = response.memberId + ) + } + + return ApiResponse.ok(message = "회원가입을 축하드립니다.", data = response.loginResponse) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt index e67a033..4d7ead6 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberRepository.kt @@ -21,6 +21,8 @@ import org.springframework.stereotype.Repository interface MemberRepository : JpaRepository, MemberQueryRepository { fun findByEmail(email: String): Member? fun findByNickname(nickname: String): Member? + fun findByGoogleId(googleId: String): Member? + fun findByKakaoId(kakaoId: Long): Member? } interface MemberQueryRepository { diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt index 5e7642f..93cf16e 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/MemberService.kt @@ -32,6 +32,8 @@ import kr.co.vividnext.sodalive.member.signUp.SignUpRequest import kr.co.vividnext.sodalive.member.signUp.SignUpRequestV2 import kr.co.vividnext.sodalive.member.signUp.SignUpResponse import kr.co.vividnext.sodalive.member.signUp.SignUpValidator +import kr.co.vividnext.sodalive.member.social.google.GoogleUserInfo +import kr.co.vividnext.sodalive.member.social.kakao.KakaoUserInfo import kr.co.vividnext.sodalive.member.stipulation.Stipulation import kr.co.vividnext.sodalive.member.stipulation.StipulationAgree import kr.co.vividnext.sodalive.member.stipulation.StipulationAgreeRepository @@ -736,4 +738,70 @@ class MemberService( return member.activePid } + + @Transactional + fun findOrRegister(googleUserInfo: GoogleUserInfo, container: String, marketingPid: String?): Member { + repository.findByGoogleId(googleUserInfo.sub)?.let { return it } + + val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID) + ?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") + + val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID) + ?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") + + val nickname = nicknameGenerateService.generateUniqueNickname() + val member = Member( + googleId = googleUserInfo.sub, + email = googleUserInfo.email, + password = "", + nickname = nickname, + profileImage = "profile/default-profile.png", + gender = Gender.NONE, + provider = MemberProvider.GOOGLE, + container = container + ) + + if (!marketingPid.isNullOrBlank()) { + member.activePid = marketingPid + member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1) + } + + repository.save(member) + agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy) + + return member + } + + @Transactional + fun findOrRegister(kakaoUserInfo: KakaoUserInfo, container: String, marketingPid: String?): Member { + repository.findByKakaoId(kakaoUserInfo.id)?.let { return it } + + val stipulationTermsOfService = stipulationRepository.findByIdOrNull(StipulationIds.TERMS_OF_SERVICE_ID) + ?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") + + val stipulationPrivacyPolicy = stipulationRepository.findByIdOrNull(StipulationIds.PRIVACY_POLICY_ID) + ?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") + + val nickname = nicknameGenerateService.generateUniqueNickname() + val member = Member( + kakaoId = kakaoUserInfo.id, + email = kakaoUserInfo.email, + password = "", + nickname = nickname, + profileImage = "profile/default-profile.png", + gender = Gender.NONE, + provider = MemberProvider.KAKAO, + container = container + ) + + if (!marketingPid.isNullOrBlank()) { + member.activePid = marketingPid + member.partnerExpirationDatetime = LocalDateTime.now().plusYears(1) + } + + repository.save(member) + agreeTermsOfServiceAndPrivacyPolicy(member, stipulationTermsOfService, stipulationPrivacyPolicy) + + return member + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt index f49585f..da16dc4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/login/LoginRequest.kt @@ -6,3 +6,5 @@ data class LoginRequest( val isAdmin: Boolean = false, val isCreator: Boolean = false ) + +data class SocialLoginRequest(val container: String, val marketingPid: String? = null) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/SocialLoginResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/SocialLoginResponse.kt new file mode 100644 index 0000000..624f83a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/SocialLoginResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.member.social + +import kr.co.vividnext.sodalive.member.login.LoginResponse + +data class SocialLoginResponse( + val memberId: Long, + val marketingPid: String?, + val loginResponse: LoginResponse +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt new file mode 100644 index 0000000..495e269 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthService.kt @@ -0,0 +1,54 @@ +package kr.co.vividnext.sodalive.member.social.google + +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.jwt.TokenProvider +import kr.co.vividnext.sodalive.member.MemberAdapter +import kr.co.vividnext.sodalive.member.MemberService +import kr.co.vividnext.sodalive.member.login.LoginResponse +import kr.co.vividnext.sodalive.member.social.SocialLoginResponse +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Service + +@Service +class GoogleAuthService( + private val googleService: GoogleService, + private val memberService: MemberService, + private val tokenProvider: TokenProvider, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String +) { + fun authenticate(idToken: String, container: String, marketingPid: String?): SocialLoginResponse { + val googleUserInfo = googleService.getUserInfo(idToken) + ?: throw SodaException("구글 로그인을 하지 못했습니다. 다시 시도해 주세요") + val member = memberService.findOrRegister(googleUserInfo, container, marketingPid) + val principal = MemberAdapter(member) + val authToken = GoogleAuthenticationToken(idToken, principal.authorities) + authToken.setPrincipal(principal) + SecurityContextHolder.getContext().authentication = authToken + + val jwt = tokenProvider.createToken( + authentication = authToken, + memberId = member.id!! + ) + + val loginResponse = LoginResponse( + userId = member.id!!, + token = jwt, + nickname = member.nickname, + email = member.email, + profileImage = if (member.profileImage != null) { + "$cloudFrontHost/${member.profileImage}" + } else { + "$cloudFrontHost/profile/default-profile.png" + } + ) + + return SocialLoginResponse( + memberId = member.id!!, + marketingPid = marketingPid, + loginResponse = loginResponse + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthenticationToken.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthenticationToken.kt new file mode 100644 index 0000000..9bfa330 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleAuthenticationToken.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.member.social.google + +import org.springframework.security.authentication.AbstractAuthenticationToken +import org.springframework.security.core.GrantedAuthority + +class GoogleAuthenticationToken( + private val idToken: String, + authorities: Collection? = null +) : AbstractAuthenticationToken(authorities) { + private var principal: Any? = null + init { isAuthenticated = authorities != null } + override fun getCredentials(): Any = idToken + override fun getPrincipal(): Any? = principal + fun setPrincipal(principal: Any) { this.principal = principal } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleService.kt new file mode 100644 index 0000000..fa31395 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleService.kt @@ -0,0 +1,45 @@ +package kr.co.vividnext.sodalive.member.social.google + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport +import com.google.api.client.json.gson.GsonFactory +import kr.co.vividnext.sodalive.common.SodaException +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Service + +@Service +class GoogleService( + @Value("\${google.web-client-id}") + private val googleWebClientId: String +) { + private val transport = GoogleNetHttpTransport.newTrustedTransport() + private val jsonFactory = GsonFactory.getDefaultInstance() + private val clientIds = listOf(googleWebClientId) + + fun getUserInfo(idToken: String): GoogleUserInfo? { + val verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory) + .setAudience(clientIds) + .setIssuers(listOf("accounts.google.com", "https://accounts.google.com")) + .build() + + return try { + val token = verifier.verify(idToken) + + if (token != null) { + val payload = token.payload + val email = payload.email ?: throw SodaException("이메일 제공에 동의하셔야 서비스 이용이 가능합니다.") + + GoogleUserInfo( + sub = payload.subject, + email = email, + name = payload["name"] as? String + ) + } else { + null + } + } catch (e: Exception) { + e.printStackTrace() + null + } + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleUserInfo.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleUserInfo.kt new file mode 100644 index 0000000..e54db6a --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/google/GoogleUserInfo.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.member.social.google + +data class GoogleUserInfo( + val sub: String, + val email: String, + val name: String? +) diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt new file mode 100644 index 0000000..49525bf --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthService.kt @@ -0,0 +1,54 @@ +package kr.co.vividnext.sodalive.member.social.kakao + +import kr.co.vividnext.sodalive.common.SodaException +import kr.co.vividnext.sodalive.jwt.TokenProvider +import kr.co.vividnext.sodalive.member.MemberAdapter +import kr.co.vividnext.sodalive.member.MemberService +import kr.co.vividnext.sodalive.member.login.LoginResponse +import kr.co.vividnext.sodalive.member.social.SocialLoginResponse +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Service + +@Service +class KakaoAuthService( + private val kakaoService: KakaoService, + private val memberService: MemberService, + private val tokenProvider: TokenProvider, + + @Value("\${cloud.aws.cloud-front.host}") + private val cloudFrontHost: String +) { + fun authenticate(accessToken: String, container: String, marketingPid: String?): SocialLoginResponse { + val kakaoUserInfo = kakaoService.getUserInfo(accessToken) + ?: throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요") + val member = memberService.findOrRegister(kakaoUserInfo, container, marketingPid) + val principal = MemberAdapter(member) + val authToken = KakaoAuthenticationToken(accessToken, principal.authorities) + authToken.setPrincipal(principal) + SecurityContextHolder.getContext().authentication = authToken + + val jwt = tokenProvider.createToken( + authentication = authToken, + memberId = member.id!! + ) + + val loginResponse = LoginResponse( + userId = member.id!!, + token = jwt, + nickname = member.nickname, + email = member.email, + profileImage = if (member.profileImage != null) { + "$cloudFrontHost/${member.profileImage}" + } else { + "$cloudFrontHost/profile/default-profile.png" + } + ) + + return SocialLoginResponse( + memberId = member.id!!, + marketingPid = marketingPid, + loginResponse = loginResponse + ) + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthenticationToken.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthenticationToken.kt new file mode 100644 index 0000000..d8e5aa9 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoAuthenticationToken.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.member.social.kakao + +import org.springframework.security.authentication.AbstractAuthenticationToken +import org.springframework.security.core.GrantedAuthority + +class KakaoAuthenticationToken( + val accessToken: String, + authorities: Collection? = null +) : AbstractAuthenticationToken(authorities) { + private var principal: Any? = null + init { isAuthenticated = authorities != null } + override fun getCredentials(): Any = accessToken + override fun getPrincipal(): Any? = principal + fun setPrincipal(principal: Any) { this.principal = principal } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoService.kt new file mode 100644 index 0000000..ffd1204 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoService.kt @@ -0,0 +1,53 @@ +package kr.co.vividnext.sodalive.member.social.kakao + +import com.fasterxml.jackson.databind.ObjectMapper +import kr.co.vividnext.sodalive.common.SodaException +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.web.client.RestTemplate + +@Service +class KakaoService( + private val restTemplate: RestTemplate = RestTemplate(), + private val objectMapper: ObjectMapper = ObjectMapper() +) { + fun getUserInfo(accessToken: String): KakaoUserInfo? { + val url = "https://kapi.kakao.com/v2/user/me" + val headers = HttpHeaders().apply { + set("Authorization", "Bearer $accessToken") + } + val entity = HttpEntity(headers) + + try { + val response: ResponseEntity = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + String::class.java + ) + + if (response.statusCode == HttpStatus.OK) { + // 응답 JSON을 KakaoUserInfo와 직접 매핑하려면 DTO의 구조와 JSON 구조가 일치해야 합니다. + // 실제 응답 JSON은 중첩 구조를 가지므로, 필요한 필드만 추출하는 방법을 사용합니다. + val jsonNode = objectMapper.readTree(response.body ?: return null) + val id = jsonNode.get("id").asLong() + val kakaoAccount = jsonNode.get("kakao_account") + val email = kakaoAccount?.get("email")?.asText() + ?: throw SodaException("카카오 로그인을 하지 못했습니다. 다시 시도해 주세요") + val properties = jsonNode.get("properties") + val nickname = properties?.get("nickname")?.asText() + + return KakaoUserInfo(id, email, nickname) + } else { + println("카카오 사용자 정보 요청 실패: ${response.statusCode}") + } + } catch (ex: Exception) { + ex.printStackTrace() + } + return null + } +} diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoUserInfo.kt b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoUserInfo.kt new file mode 100644 index 0000000..9ddcc19 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/member/social/kakao/KakaoUserInfo.kt @@ -0,0 +1,7 @@ +package kr.co.vividnext.sodalive.member.social.kakao + +data class KakaoUserInfo( + val id: Long, + val email: String, + val nickname: String? +) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6dc7c92..fbc0fd4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -25,6 +25,9 @@ agora: firebase: secretKeyPath: ${GOOGLE_APPLICATION_CREDENTIALS} +google: + webClientId: ${GOOGLE_WEB_CLIENT_ID} + cloud: aws: credentials: