Skip to content

Commit

Permalink
[YS-32] feat: Naver OAuth 로그인 추가 (#20)
Browse files Browse the repository at this point in the history
* feat: add feignclient for naver

* feat: add FetchNaverUserInfoUseCase logic

* test: add FetchNaverUserInfoUseCaseTest

* chore: add naver variable to template-application-local.yml

* refact: delete unused import and move testcode to appropriate package

* refact: refactor NaverUserInfoFeignClient interface

* refact: move class file to appropriate package
  • Loading branch information
Ji-soo708 authored Jan 6, 2025
1 parent 01ae718 commit 48c983c
Show file tree
Hide file tree
Showing 32 changed files with 348 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import com.dobby.backend.infrastructure.database.entity.MemberEntity
import com.dobby.backend.infrastructure.database.entity.ParticipantEntity
import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus
import com.dobby.backend.infrastructure.database.entity.enum.RoleType
import com.dobby.backend.presentation.api.dto.request.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.response.MemberResponse
import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest
import com.dobby.backend.infrastructure.database.entity.AddressInfo as AddressInfo
import com.dobby.backend.presentation.api.dto.request.AddressInfo as DtoAddressInfo
import com.dobby.backend.presentation.api.dto.request.signup.AddressInfo as DtoAddressInfo

object SignupMapper {
fun toAddressInfo(dto: DtoAddressInfo): AddressInfo {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package com.dobby.backend.application.service

import com.dobby.backend.application.usecase.FetchGoogleUserInfoUseCase
import com.dobby.backend.presentation.api.dto.request.OauthLoginRequest
import com.dobby.backend.application.usecase.FetchNaverUserInfoUseCase
import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest
import com.dobby.backend.presentation.api.dto.request.auth.NaverOauthLoginRequest
import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse
import org.springframework.stereotype.Service

@Service
class OauthService(
private val fetchGoogleUserInfoUseCase: FetchGoogleUserInfoUseCase
private val fetchGoogleUserInfoUseCase: FetchGoogleUserInfoUseCase,
private val fetchNaverUserInfoUseCase: FetchNaverUserInfoUseCase,
) {
fun getGoogleUserInfo(oauthLoginRequest: OauthLoginRequest): OauthLoginResponse {
fun getGoogleUserInfo(oauthLoginRequest: GoogleOauthLoginRequest): OauthLoginResponse {
return fetchGoogleUserInfoUseCase.execute(oauthLoginRequest)
}

fun getNaverUserInfo(oauthLoginRequest: NaverOauthLoginRequest): OauthLoginResponse {
return fetchNaverUserInfoUseCase.execute(oauthLoginRequest)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.dobby.backend.application.service

import com.dobby.backend.application.usecase.ParticipantSignupUseCase
import com.dobby.backend.presentation.api.dto.request.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse
import jakarta.transaction.Transactional
import org.springframework.stereotype.Service
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.dobby.backend.application.usecase

import com.dobby.backend.application.mapper.OauthUserMapper
import com.dobby.backend.domain.exception.OAuth2EmailNotFoundException
import com.dobby.backend.domain.exception.SignInMemberException
import com.dobby.backend.infrastructure.config.properties.GoogleAuthProperties
import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus
import com.dobby.backend.infrastructure.database.entity.enum.ProviderType
import com.dobby.backend.infrastructure.database.repository.MemberRepository
import com.dobby.backend.infrastructure.feign.GoogleAuthFeignClient
import com.dobby.backend.infrastructure.feign.GoogleUserInfoFeginClient
import com.dobby.backend.infrastructure.feign.google.GoogleAuthFeignClient
import com.dobby.backend.infrastructure.feign.google.GoogleUserInfoFeginClient
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import com.dobby.backend.presentation.api.dto.request.GoogleTokenRequest
import com.dobby.backend.presentation.api.dto.request.OauthLoginRequest
import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleTokenResponse
import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest
import com.dobby.backend.presentation.api.dto.request.auth.GoogleTokenRequest
import com.dobby.backend.presentation.api.dto.response.auth.GoogleTokenResponse
import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse
import com.dobby.backend.util.AuthenticationUtils

Expand All @@ -22,9 +21,9 @@ class FetchGoogleUserInfoUseCase(
private val jwtTokenProvider: JwtTokenProvider,
private val googleAuthProperties: GoogleAuthProperties,
private val memberRepository: MemberRepository
) : UseCase<OauthLoginRequest, OauthLoginResponse> {
) : UseCase<GoogleOauthLoginRequest, OauthLoginResponse> {

override fun execute(input: OauthLoginRequest): OauthLoginResponse {
override fun execute(input: GoogleOauthLoginRequest): OauthLoginResponse {
try {
val googleTokenRequest = GoogleTokenRequest(
code = input.authorizationCode,
Expand All @@ -37,7 +36,7 @@ class FetchGoogleUserInfoUseCase(
val oauthToken = oauthRes.accessToken

val userInfo = googleUserInfoFeginClient.getUserInfo("Bearer $oauthToken")
val email = userInfo.email ?: throw OAuth2EmailNotFoundException()
val email = userInfo.email
val regMember = memberRepository.findByOauthEmailAndStatus(email, MemberStatus.ACTIVE)
?: throw SignInMemberException()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.dobby.backend.application.usecase

import com.dobby.backend.application.mapper.OauthUserMapper
import com.dobby.backend.domain.exception.SignInMemberException
import com.dobby.backend.infrastructure.config.properties.NaverAuthProperties
import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus
import com.dobby.backend.infrastructure.database.entity.enum.ProviderType
import com.dobby.backend.infrastructure.database.repository.MemberRepository
import com.dobby.backend.infrastructure.feign.naver.NaverAuthFeignClient
import com.dobby.backend.infrastructure.feign.naver.NaverUserInfoFeignClient
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import com.dobby.backend.presentation.api.dto.request.auth.NaverOauthLoginRequest
import com.dobby.backend.presentation.api.dto.request.auth.NaverTokenRequest
import com.dobby.backend.presentation.api.dto.response.auth.NaverTokenResponse
import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse
import com.dobby.backend.util.AuthenticationUtils

class FetchNaverUserInfoUseCase(
private val naverAuthFeignClient: NaverAuthFeignClient,
private val naverUserInfoFeginClient: NaverUserInfoFeignClient,
private val jwtTokenProvider: JwtTokenProvider,
private val naverAuthProperties: NaverAuthProperties,
private val memberRepository: MemberRepository
) : UseCase<NaverOauthLoginRequest, OauthLoginResponse> {

override fun execute(input: NaverOauthLoginRequest): OauthLoginResponse {
try {
val naverTokenRequest = NaverTokenRequest(
grantType = "authorization_code",
clientId = naverAuthProperties.clientId,
clientSecret = naverAuthProperties.clientSecret,
code = input.authorizationCode,
state = input.state
)

val oauthRes = fetchAccessToken(naverTokenRequest)
val oauthToken = oauthRes.accessToken

val userInfo = naverUserInfoFeginClient.getUserInfo("Bearer $oauthToken")
val email = userInfo.email
val regMember = memberRepository.findByOauthEmailAndStatus(email, MemberStatus.ACTIVE)
?: throw SignInMemberException()

val regMemberAuthentication = AuthenticationUtils.createAuthentication(regMember)
val jwtAccessToken = jwtTokenProvider.generateAccessToken(regMemberAuthentication)
val jwtRefreshToken = jwtTokenProvider.generateRefreshToken(regMemberAuthentication)

return OauthUserMapper.toDto(
isRegistered = true,
accessToken = jwtAccessToken,
refreshToken = jwtRefreshToken,
oauthEmail = regMember.oauthEmail,
oauthName = regMember.name ?: throw SignInMemberException(),
role = regMember.role ?: throw SignInMemberException(),
provider = ProviderType.NAVER
)
} catch (e: SignInMemberException) {
throw SignInMemberException()
}
}

private fun fetchAccessToken(naverTokenRequest: NaverTokenRequest): NaverTokenResponse {
return naverAuthFeignClient.getAccessToken(naverTokenRequest)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.dobby.backend.application.usecase
import com.dobby.backend.application.mapper.SignupMapper
import com.dobby.backend.infrastructure.database.repository.ParticipantRepository
import com.dobby.backend.infrastructure.token.JwtTokenProvider
import com.dobby.backend.presentation.api.dto.request.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.response.MemberResponse
import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse
import com.dobby.backend.util.AuthenticationUtils
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.dobby.backend.infrastructure.config.properties

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties(prefix = "spring.security.oauth.client.registration.naver")
data class NaverAuthProperties (
var clientId: String = "",
var clientSecret: String= "",
var redirectUri : String = ""
)
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.dobby.backend.infrastructure.feign
package com.dobby.backend.infrastructure.feign.google

import com.dobby.backend.presentation.api.dto.request.GoogleTokenRequest
import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleTokenResponse
import com.dobby.backend.presentation.api.dto.request.auth.GoogleTokenRequest
import com.dobby.backend.presentation.api.dto.response.auth.GoogleTokenResponse
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.dobby.backend.infrastructure.feign
package com.dobby.backend.infrastructure.feign.google

import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleInfoResponse
import com.dobby.backend.presentation.api.dto.response.auth.GoogleInfoResponse
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.dobby.backend.infrastructure.feign.naver

import com.dobby.backend.presentation.api.dto.request.auth.NaverTokenRequest
import com.dobby.backend.presentation.api.dto.response.auth.NaverTokenResponse
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody

@FeignClient(
name = "naver-auth-feign-client",
url = "https://nid.naver.com/oauth2.0/token"
)
interface NaverAuthFeignClient {
@PostMapping
fun getAccessToken(
@RequestBody naverTokenRequest: NaverTokenRequest
): NaverTokenResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.dobby.backend.infrastructure.feign.naver

import com.dobby.backend.presentation.api.dto.response.auth.NaverInfoResponse
import org.springframework.cloud.openfeign.FeignClient
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestHeader

@FeignClient(
name = "naver-userinfo-feign-client",
url = "https://openapi.naver.com/v1/nid/me"
)
interface NaverUserInfoFeignClient {
@PostMapping
fun getUserInfo(@RequestHeader("Authorization") accessToken: String): NaverInfoResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package com.dobby.backend.presentation.api.controller

import com.dobby.backend.application.service.OauthService
import com.dobby.backend.application.usecase.GenerateTestToken
import com.dobby.backend.presentation.api.dto.request.OauthLoginRequest
import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse
import com.dobby.backend.application.usecase.GenerateTokenWithRefreshToken
import com.dobby.backend.application.usecase.GetMemberById
import com.dobby.backend.infrastructure.database.entity.enum.RoleType
import com.dobby.backend.presentation.api.dto.request.MemberRefreshTokenRequest
import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest
import com.dobby.backend.presentation.api.dto.request.auth.MemberRefreshTokenRequest
import com.dobby.backend.presentation.api.dto.request.auth.NaverOauthLoginRequest
import com.dobby.backend.presentation.api.dto.response.MemberResponse
import com.dobby.backend.presentation.api.dto.response.TestMemberSignInResponse
import com.dobby.backend.presentation.api.dto.response.auth.TestMemberSignInResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.Valid
Expand Down Expand Up @@ -42,12 +43,21 @@ class AuthController(
@PostMapping("/login/google")
@Operation(summary = "Google OAuth 로그인 API", description = "Google OAuth 로그인 후 인증 정보를 반환합니다")
fun signInWithGoogle(
@RequestParam role : RoleType, // RESEARCHER, PARTICIPANT
@RequestBody @Valid oauthLoginRequest: OauthLoginRequest
@RequestParam role : RoleType,
@RequestBody @Valid oauthLoginRequest: GoogleOauthLoginRequest
): OauthLoginResponse {
return oauthService.getGoogleUserInfo(oauthLoginRequest)
}

@PostMapping("/login/naver")
@Operation(summary = "Naver OAuth 로그인 API", description = "Naver OAuth 로그인 후 인증 정보를 반환합니다")
fun signInWithNaver(
@RequestParam role : RoleType,
@RequestBody @Valid oauthLoginRequest: NaverOauthLoginRequest
): OauthLoginResponse {
return oauthService.getNaverUserInfo(oauthLoginRequest)
}

@Operation(summary = "토큰 갱신 요청", description = "리프레시 토큰으로 기존 토큰을 갱신합니다")
@PostMapping("/refresh")
fun signInWithRefreshToken(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.dobby.backend.presentation.api.controller.SignupController

import com.dobby.backend.application.service.SignupService
import com.dobby.backend.infrastructure.database.entity.enum.RoleType
import com.dobby.backend.infrastructure.database.entity.enum.RoleType.*
import com.dobby.backend.presentation.api.dto.request.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest
import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.dobby.backend.presentation.api.dto.request
package com.dobby.backend.presentation.api.dto.request.auth

import jakarta.validation.constraints.NotBlank

data class OauthLoginRequest(
data class GoogleOauthLoginRequest(
@NotBlank(message = "authorizationCode는 공백일 수 없습니다.")
val authorizationCode: String
)
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dobby.backend.presentation.api.dto.request
package com.dobby.backend.presentation.api.dto.request.auth

import com.fasterxml.jackson.annotation.JsonProperty

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dobby.backend.presentation.api.dto.request
package com.dobby.backend.presentation.api.dto.request.auth

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.dobby.backend.presentation.api.dto.request.auth

import jakarta.validation.constraints.NotBlank

data class NaverOauthLoginRequest(
@NotBlank(message = "authorizationCode는 공백일 수 없습니다.")
val authorizationCode: String,

@NotBlank(message = "state는 공백일 수 없습니다.")
val state: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.dobby.backend.presentation.api.dto.request.auth

import com.fasterxml.jackson.annotation.JsonProperty

data class NaverTokenRequest (
@JsonProperty("grant_type")
val grantType: String = "authorization_code",

@JsonProperty("client_id")
val clientId: String,

@JsonProperty("client_secret")
val clientSecret: String,

@JsonProperty("code")
val code: String,

@JsonProperty("state")
val state: String
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dobby.backend.presentation.api.dto.request
package com.dobby.backend.presentation.api.dto.request.signup

import com.dobby.backend.infrastructure.database.entity.enum.GenderType
import com.dobby.backend.infrastructure.database.entity.enum.MatchType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dobby.backend.presentation.api.dto.response.auth.google
package com.dobby.backend.presentation.api.dto.response.auth

import com.fasterxml.jackson.annotation.JsonProperty

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.dobby.backend.presentation.api.dto.response.auth.google
package com.dobby.backend.presentation.api.dto.response.auth

import com.fasterxml.jackson.annotation.JsonProperty

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.dobby.backend.presentation.api.dto.response.auth

import com.fasterxml.jackson.annotation.JsonProperty

data class NaverInfoResponse(
@JsonProperty("email")
val email: String,

@JsonProperty("name")
val name: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.dobby.backend.presentation.api.dto.response.auth

import com.fasterxml.jackson.annotation.JsonProperty

data class NaverTokenResponse (
@JsonProperty("access_token")
val accessToken: String?
)
Loading

0 comments on commit 48c983c

Please sign in to comment.