-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ISSUE-14 애플 & 카카오 소셜 로그인 연동 로직 구현 #15
Changes from 15 commits
9fdab5e
d210aa3
f65ea15
72bfe0d
04125ff
972198d
4e35eaa
8601870
d1f552e
dc3a1d2
d5a2f9b
48edecb
ff7d0d7
15a0ee4
e446589
8e13641
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.doorip.core.auth | ||
|
||
import org.doorip.domain.entity.Token | ||
import org.doorip.domain.entity.UserId | ||
import org.doorip.domain.entity.UserInfo | ||
|
||
interface AuthUseCase { | ||
fun signIn(token: String, platform: String): UserInfo | ||
fun signUp(token: String, platform: String, name: String, intro: String): Token | ||
fun signOut(userId: UserId) | ||
fun reissue(refreshToken: String): Token | ||
fun withdraw(userId: UserId) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.doorip.gateway.oauth.apple | ||
|
||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
internal class AppleAuthService( | ||
private val keyGenerator: ApplePublicKeyGenerator, | ||
private val validator: AppleIdTokenValidator, | ||
) { | ||
|
||
internal fun getPlatformId(token: String): String { | ||
val publicKey = keyGenerator.generatePublicKey(token) | ||
|
||
return validator.validateAndGetSubject(token, publicKey) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.doorip.gateway.oauth.apple | ||
|
||
import org.doorip.gateway.oauth.apple.dto.ApplePublicKeys | ||
import org.springframework.cloud.openfeign.FeignClient | ||
import org.springframework.web.bind.annotation.GetMapping | ||
|
||
@FeignClient(value = "apple-client", url = "https://appleid.apple.com/auth/keys") | ||
internal interface AppleFeignClient { | ||
@GetMapping | ||
fun getApplePublicKeys(): ApplePublicKeys | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.doorip.gateway.oauth.apple | ||
|
||
import org.doorip.domain.UnauthenticatedException | ||
import org.doorip.support.jwt.JwtProvider | ||
import org.doorip.support.jwt.SignatureKey | ||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
internal class AppleIdTokenValidator( | ||
private val jwtProvider: JwtProvider, | ||
) { | ||
@Value("\${oauth.apple.iss}") | ||
lateinit var iss: String | ||
|
||
@Value("\${oauth.apple.client-id}") | ||
lateinit var clientId: String | ||
Comment on lines
+13
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive)
🧰 Tools🪛 detekt (1.23.7)[warning] 13-14: Usages of lateinit should be avoided. (detekt.potential-bugs.LateinitUsage) [warning] 16-17: Usages of lateinit should be avoided. (detekt.potential-bugs.LateinitUsage) |
||
|
||
internal fun validateAndGetSubject(token: String, key: SignatureKey): String { | ||
return jwtProvider.validateAndGetSubject( | ||
token = token, | ||
key = key, | ||
iss = iss, | ||
aud = clientId, | ||
) ?: throw UnauthenticatedException | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.doorip.gateway.oauth.apple | ||
|
||
import feign.FeignException | ||
import org.doorip.domain.UnauthenticatedException | ||
import org.doorip.gateway.oauth.apple.dto.ApplePublicKeys | ||
import org.doorip.support.jwt.JwtProvider | ||
import org.doorip.support.jwt.RSAPublicKey | ||
import org.doorip.support.jwt.SignatureKey | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
internal class ApplePublicKeyGenerator( | ||
private val httpClient: AppleFeignClient, | ||
private val jwtProvider: JwtProvider, | ||
) { | ||
|
||
internal fun generatePublicKey(token: String): SignatureKey { | ||
val applePublicKeys = getApplePublicKeys() | ||
|
||
val header = jwtProvider.parseHeader(token) ?: throw UnauthenticatedException | ||
|
||
val alg = header["alg"] ?: throw UnauthenticatedException | ||
val kid = header["kid"] ?: throw UnauthenticatedException | ||
val key = applePublicKeys[alg, kid] ?: throw UnauthenticatedException | ||
|
||
return RSAPublicKey(key.n, key.e, key.kty) | ||
} | ||
Comment on lines
+17
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 공개 키 생성 과정에서 토큰 헤더 정보( |
||
|
||
private fun getApplePublicKeys(): ApplePublicKeys { | ||
try { | ||
return httpClient.getApplePublicKeys() | ||
} catch (e: FeignException) { | ||
throw UnauthenticatedException | ||
} | ||
} | ||
Comment on lines
+29
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 예외를 잡은 뒤 🧰 Tools🪛 detekt (1.23.7)[warning] 32-32: The caught exception is swallowed. The original exception could be lost. (detekt.exceptions.SwallowedException) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.doorip.gateway.oauth.apple.dto | ||
|
||
internal data class ApplePublicKey( | ||
val kty: String, | ||
val kid: String, | ||
val use: String, | ||
val alg: String, | ||
val n: String, | ||
val e: String, | ||
) | ||
Comment on lines
+3
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) ApplePublicKey 클래스에 대한 간단한 점검 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.doorip.gateway.oauth.apple.dto | ||
|
||
internal data class ApplePublicKeys( | ||
val keys: List<ApplePublicKey>, | ||
) { | ||
|
||
operator fun get(alg: String, kid: String): ApplePublicKey? { | ||
return keys.firstOrNull { key -> | ||
key.alg == alg && key.kid == kid | ||
} | ||
} | ||
} | ||
Comment on lines
+3
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) keys 프로퍼티 접근 시 성능 고려 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.doorip.gateway.oauth.config | ||
|
||
import org.springframework.cloud.openfeign.EnableFeignClients | ||
import org.springframework.context.annotation.Configuration | ||
|
||
@EnableFeignClients(basePackages = ["org.doorip"]) | ||
@Configuration | ||
internal class OpenFeignConfig | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Feign 클라이언트 설정 개선을 제안드립니다 😊 현재 설정은 기본적인 기능만 제공하고 있어요. 실제 운영 환경에서는 다음과 같은 추가 설정들이 필요할 것 같습니다:
다음과 같이 개선하는 것은 어떨까요? @EnableFeignClients(basePackages = ["org.doorip"])
@Configuration
-internal class OpenFeignConfig
+internal class OpenFeignConfig {
+ @Bean
+ fun feignLoggerLevel(): Logger.Level = Logger.Level.FULL
+
+ @Bean
+ fun errorDecoder(): ErrorDecoder = CustomErrorDecoder()
+
+ @Bean
+ fun requestInterceptor(): RequestInterceptor =
+ RequestInterceptor { template ->
+ template.header("Accept", "application/json")
+ }
+}
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,25 @@ | ||||||||||||||
package org.doorip.gateway.oauth.kakao | ||||||||||||||
|
||||||||||||||
import feign.FeignException | ||||||||||||||
import org.doorip.domain.UnauthenticatedException | ||||||||||||||
import org.springframework.stereotype.Component | ||||||||||||||
|
||||||||||||||
@Component | ||||||||||||||
internal class KakaoAuthService( | ||||||||||||||
private val httpClient: KakaoFeignClient, | ||||||||||||||
) { | ||||||||||||||
|
||||||||||||||
fun getPlatformId(token: String): String { | ||||||||||||||
try { | ||||||||||||||
val kakaoAccessTokenInfo = httpClient.getKakaoAccessTokenInfo(HEADER_BEARER + token) | ||||||||||||||
|
||||||||||||||
return kakaoAccessTokenInfo.id.toString() | ||||||||||||||
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) Bearer 토큰 문자열 처리에 주의하세요. Token 문자열 앞에 |
||||||||||||||
} catch (ex: FeignException) { | ||||||||||||||
throw UnauthenticatedException | ||||||||||||||
} | ||||||||||||||
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 예외 정보를 좀 더 풍부하게 전달하면 좋겠습니다. 현재 } catch (ex: FeignException) {
- throw UnauthenticatedException
+ throw UnauthenticatedException("카카오 인증 실패", ex)
} 📝 Committable suggestion
Suggested change
🧰 Tools🪛 detekt (1.23.7)[warning] 17-17: The caught exception is swallowed. The original exception could be lost. (detekt.exceptions.SwallowedException) |
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
companion object { | ||||||||||||||
private const val HEADER_BEARER = "Bearer " | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.doorip.gateway.oauth.kakao | ||
|
||
import org.doorip.gateway.oauth.kakao.dto.KakaoAccessTokenInfo | ||
import org.springframework.cloud.openfeign.FeignClient | ||
import org.springframework.web.bind.annotation.GetMapping | ||
import org.springframework.web.bind.annotation.RequestHeader | ||
|
||
@FeignClient(name = "kakao-kakao-feign", url = "https://kapi.kakao.com/v1/user/access_token_info") | ||
internal interface KakaoFeignClient { | ||
@GetMapping | ||
fun getKakaoAccessTokenInfo(@RequestHeader("Authorization") token: String): KakaoAccessTokenInfo | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package org.doorip.gateway.oauth.kakao.dto | ||
|
||
internal data class KakaoAccessTokenInfo( | ||
val id: Long, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
oauth: | ||
apple: | ||
iss: https://appleid.apple.com | ||
client-id: ${APPLE_CLIENT_ID} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.doorip.gateway.rdb | ||
|
||
import jakarta.persistence.Column | ||
import jakarta.persistence.EntityListeners | ||
import jakarta.persistence.MappedSuperclass | ||
import java.time.LocalDateTime | ||
import org.springframework.data.annotation.CreatedDate | ||
import org.springframework.data.annotation.LastModifiedDate | ||
import org.springframework.data.jpa.domain.support.AuditingEntityListener | ||
|
||
@MappedSuperclass | ||
@EntityListeners(AuditingEntityListener::class) | ||
internal abstract class BaseJpaEntity { | ||
@CreatedDate | ||
@Column(name = "created_at", updatable = false, nullable = false) | ||
var createdDate: LocalDateTime = LocalDateTime.now() | ||
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 생성일 기본값 표현에 대한 주의사항 |
||
|
||
@LastModifiedDate | ||
@Column(name = "updated_at", nullable = false) | ||
var updatedDate: LocalDateTime = LocalDateTime.now() | ||
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 마지막 수정 시각에도 같은 고려가 필요합니다. |
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,8 @@ | ||||||||||||||||||||||||||||||||
package org.doorip.gateway.rdb.config | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import org.springframework.context.annotation.Configuration | ||||||||||||||||||||||||||||||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
@EnableJpaAuditing | ||||||||||||||||||||||||||||||||
@Configuration | ||||||||||||||||||||||||||||||||
internal class JpaConfig | ||||||||||||||||||||||||||||||||
Comment on lines
+6
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) JPA Auditing 설정에 대한 제안 JPA Auditing 기본 설정이 잘 되어있네요! 😊 하지만 더 견고한 설정을 위해 몇 가지 제안드립니다:
아래와 같이 개선해보는 건 어떨까요? @EnableJpaAuditing
@Configuration
-internal class JpaConfig
+/**
+ * JPA Auditing을 위한 설정 클래스입니다.
+ * 엔티티의 생성일시와 수정일시를 자동으로 관리합니다.
+ */
+internal class JpaConfig {
+ @Bean
+ fun auditorProvider(): AuditorAware<String> {
+ return AuditorAware { Optional.of("SYSTEM") }
+ }
+} 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import org.doorip.domain.entity.UserId | |
import org.doorip.domain.repository.RefreshTokenRepository | ||
import org.springframework.data.repository.findByIdOrNull | ||
import org.springframework.stereotype.Component | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Component | ||
internal class RefreshTokenGateway( | ||
|
@@ -48,6 +49,7 @@ internal class RefreshTokenGateway( | |
return encoder.encodeToString(savedRefreshToken.refreshToken) | ||
} | ||
|
||
@Transactional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick (assertive) 삭제 로직의 안정성 검토 |
||
override fun deleteRefreshToken(userId: UserId) { | ||
refreshTokenJpaRepository.deleteByUserId(userId) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
애플 서버 응답 실패 시 예외 처리 고려
FeignClient 호출 시 애플 서버가 응답하지 않거나 오류를 반환할 경우를 대비한 예외 처리가 필요합니다. 재시도 로직, 에러 로그, 혹은 사용자에게 알림을 주는 설계 등을 검토해 보세요.