Skip to content

Commit

Permalink
소셜 로그인 로직 수정 및 user 파일 분리 (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
kih00 authored Jan 22, 2025
1 parent cb9b8ea commit e51173f
Show file tree
Hide file tree
Showing 23 changed files with 336 additions and 235 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ jobs:
KAKAO_REDIRECT_URL=${{ secrets.KAKAO_REDIRECT_URL }}
NAVER_CLIENT_ID=${{ secrets.NAVER_CLIENT_ID }}
NAVER_CLIENT_SECRET=${{ secrets.NAVER_CLIENT_SECRET }}
GOOGLE_CLIENT_ID=1234
GOOGLE_CLIENT_SECRET=1234
GOOGLE_REDIRECT_URI=1234" > .env
GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }}
GOOGLE_REDIRECT_URI=${{ secrets.GOOGLE_REDIRECT_URI }}" > .env

# 배포 스크립트 EC2로 전송
scp -i private_key.pem -o StrictHostKeyChecking=no deploy.sh .env ubuntu@${{ secrets.EC2_PUBLIC_IP }}:/home/ubuntu/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wafflestudio.toyproject.memoWithTags.user.controller
package com.wafflestudio.toyproject.memoWithTags.mail

import com.wafflestudio.toyproject.memoWithTags.user.persistence.EmailVerificationEntity
import com.wafflestudio.toyproject.memoWithTags.mail.persistence.EmailVerificationEntity
import java.time.LocalDateTime

class EmailVerification(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.persistence
package com.wafflestudio.toyproject.memoWithTags.mail.persistence

import jakarta.persistence.Column
import jakarta.persistence.Entity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.persistence
package com.wafflestudio.toyproject.memoWithTags.mail.persistence

import org.springframework.data.jpa.repository.JpaRepository
import java.time.LocalDateTime
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wafflestudio.toyproject.memoWithTags.mail.service

interface MailService {
/**
* 메일을 보내는 함수.
* 개발 환경에서는 로그만 출력하고, 배포 환경에서만 실제 메일을 보내기 위해 인터페이스를 선언함
*/
fun sendMail(toEmail: String, title: String, content: String)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.service
package com.wafflestudio.toyproject.memoWithTags.mail.service

import org.slf4j.Logger
import org.slf4j.LoggerFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.service
package com.wafflestudio.toyproject.memoWithTags.mail.service

import jakarta.mail.MessagingException
import jakarta.mail.internet.MimeMessage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.wafflestudio.toyproject.memoWithTags.social.controller

import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException
import com.wafflestudio.toyproject.memoWithTags.social.service.SocialLoginService
import com.wafflestudio.toyproject.memoWithTags.user.SocialType
import com.wafflestudio.toyproject.memoWithTags.user.dto.UserResponse.LoginResponse
import io.swagger.v3.oas.annotations.Operation
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
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
@RequestMapping("/api/v1")
class SocialLoginController(
private val socialLoginService: SocialLoginService
) {
@Operation(summary = "소셜 로그인 요청")
@GetMapping("/auth/code/{provider}")
fun oauthCallback(
@RequestParam(value = "code", required = false) code: String?,
@PathVariable provider: String
): ResponseEntity<Unit> {
if (code == null) throw OAuthRequestException()
val appLink = "memowithtags://oauth/$provider?code=$code"
return ResponseEntity.status(HttpStatus.FOUND)
.header("Location", appLink)
.build()
}

@Operation(summary = "소셜 로그인 처리")
@GetMapping("/auth/login/{provider}")
fun oauthLogin(
@RequestParam(value = "code") code: String,
@PathVariable provider: String
): ResponseEntity<LoginResponse> {
val socialType = SocialType.from(provider)
val loginResult = when (socialType) {
SocialType.KAKAO -> socialLoginService.kakaoLogin(code)
SocialType.NAVER -> socialLoginService.naverLogin(code)
SocialType.GOOGLE -> socialLoginService.googleLogin(code)
else -> throw OAuthRequestException()
}
return ResponseEntity.ok(LoginResponse(loginResult.second, loginResult.third))
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.dto
package com.wafflestudio.toyproject.memoWithTags.social.dto

data class GoogleOAuthToken(
val access_token: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.dto
package com.wafflestudio.toyproject.memoWithTags.social.dto

data class KakaoOAuthToken(
val token_type: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.wafflestudio.toyproject.memoWithTags.user.dto
package com.wafflestudio.toyproject.memoWithTags.social.dto

data class NaverOAuthToken(
val token_type: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.wafflestudio.toyproject.memoWithTags.social.service

import com.wafflestudio.toyproject.memoWithTags.exception.EmailAlreadyExistsException
import com.wafflestudio.toyproject.memoWithTags.social.dto.GoogleOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.GoogleProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverProfile
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 org.slf4j.LoggerFactory
import org.springframework.stereotype.Service

@Service
class SocialLoginService(
private val socialUserService: SocialUserService,
private val kakaoUtil: KakaoUtil,
private val naverUtil: NaverUtil,
private val googleUtil: GoogleUtil
) {
private val logger = LoggerFactory.getLogger(javaClass)

/**
* 네이버 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun naverLogin(accessCode: String): Triple<User, String, String> {
val oAuthToken: NaverOAuthToken = naverUtil.requestToken(accessCode)
val naverProfile: NaverProfile = naverUtil.requestProfile(oAuthToken)

val naverEmail = naverProfile.email
val userEntity = socialUserService.findUserByEmail(naverEmail)

// 기존에 등록된 이메일이 있으면서 다른 서비스로 로그인을 한 경우 예외 발생
val user: User = if (userEntity != null && userEntity.socialType == SocialType.NAVER) {
logger.info("naver user already exists: ${userEntity.id}, ${userEntity.email}")
User.fromEntity(userEntity)
} else if (userEntity == null) {
logger.info("creating naver user $naverEmail")
socialUserService.createNaverUser(naverProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(naverEmail),
JwtUtil.generateRefreshToken(naverEmail)
)
}

/**
* 카카오 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun kakaoLogin(accessCode: String): Triple<User, String, String> {
val oAuthToken: KakaoOAuthToken = kakaoUtil.requestToken(accessCode)
val kakaoProfile: KakaoProfile = kakaoUtil.requestProfile(oAuthToken)

val kakaoEmail = kakaoProfile.kakao_account.email
val userEntity = socialUserService.findUserByEmail(kakaoEmail)

// 기존에 등록된 이메일이 있으면서 다른 서비스로 로그인을 한 경우 예외 발생
val user: User = if (userEntity != null && userEntity.socialType == SocialType.KAKAO) {
logger.info("kakao user already exists: ${userEntity.id}, ${userEntity.email}")
User.fromEntity(userEntity)
} else if (userEntity == null) {
logger.info("creating kakao user $kakaoEmail")
socialUserService.createKakaoUser(kakaoProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(kakaoEmail),
JwtUtil.generateRefreshToken(kakaoEmail)
)
}

/**
* 구글 프로필 정보를 받아온 후, 로그인 또는 회원가입 후 로그인 로직을 처리하는 함수
*/
fun googleLogin(accessCode: String): Triple<User, String, String> {
val oAuthToken: GoogleOAuthToken = googleUtil.requestToken(accessCode)
val googleProfile: GoogleProfile = googleUtil.requestProfile(oAuthToken)

val googleEmail = googleProfile.email
val userEntity = socialUserService.findUserByEmail(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 if (userEntity == null) {
logger.info("creating google user $googleEmail")
socialUserService.createGoogleUser(googleProfile)
} else {
throw EmailAlreadyExistsException()
}

return Triple(
user,
JwtUtil.generateAccessToken(googleEmail),
JwtUtil.generateRefreshToken(googleEmail)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.wafflestudio.toyproject.memoWithTags.social.service

import com.wafflestudio.toyproject.memoWithTags.social.dto.GoogleProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverProfile
import com.wafflestudio.toyproject.memoWithTags.user.SocialType
import com.wafflestudio.toyproject.memoWithTags.user.controller.User
import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserEntity
import com.wafflestudio.toyproject.memoWithTags.user.persistence.UserRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Instant

@Service
class SocialUserService(
private val userRepository: UserRepository
) {
/**
* 네이버 로그인 시 저장되어 있는 User 정보가 없을 경우, DB에 User를 생성하는 함수
*/
@Transactional
fun createNaverUser(naverProfile: NaverProfile): User {
val naverEmail = naverProfile.email
val naverNickname = naverProfile.nickname
val encryptedPassword = "naver_registered_user"

val userEntity = userRepository.save(
UserEntity(
email = naverEmail,
nickname = naverNickname,
hashedPassword = encryptedPassword,
verified = true,
socialType = SocialType.NAVER,
createdAt = Instant.now()
)
)

return User.fromEntity(userEntity)
}

/**
* 카카오 로그인 시 저장되어 있는 User 정보가 없을 경우, DB에 User를 생성하는 함수
*/
@Transactional
fun createKakaoUser(kakaoProfile: KakaoProfile): User {
val kakaoEmail = kakaoProfile.kakao_account.email
val kakaoNickname = kakaoProfile.kakao_account.profile.nickname
val encryptedPassword = "kakao_registered_user"

val userEntity = userRepository.save(
UserEntity(
email = kakaoEmail,
nickname = kakaoNickname,
hashedPassword = encryptedPassword,
verified = true,
socialType = SocialType.KAKAO,
createdAt = Instant.now()
)
)

return User.fromEntity(userEntity)
}

/**
* 구글 로그인 시 저장되어 있는 User 정보가 없을 경우, DB에 User를 생성하는 함수
*/
@Transactional
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)
}

/**
* 해당 메일의 User를 찾는 함수. 없으면 null을 반환한다.
*/
@Transactional
fun findUserByEmail(email: String): UserEntity? {
return userRepository.findByEmail(email)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.wafflestudio.toyproject.memoWithTags.user

import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException
import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleOAuthToken
import com.wafflestudio.toyproject.memoWithTags.user.dto.GoogleProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.GoogleOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.GoogleProfile
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException
import com.wafflestudio.toyproject.memoWithTags.user.dto.KakaoOAuthToken
import com.wafflestudio.toyproject.memoWithTags.user.dto.KakaoProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.KakaoProfile
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.wafflestudio.toyproject.memoWithTags.exception.OAuthRequestException
import com.wafflestudio.toyproject.memoWithTags.user.dto.NaverOAuthToken
import com.wafflestudio.toyproject.memoWithTags.user.dto.NaverProfile
import com.wafflestudio.toyproject.memoWithTags.user.dto.NaverProfileResponse
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverOAuthToken
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverProfile
import com.wafflestudio.toyproject.memoWithTags.social.dto.NaverProfileResponse
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpEntity
Expand Down
Loading

0 comments on commit e51173f

Please sign in to comment.