From 65919cd3798bc574bc51f33e1a2c5962558b097d Mon Sep 17 00:00:00 2001
From: seokjin8678 <seokjin8678@gmail.com>
Date: Tue, 7 May 2024 19:48:11 +0900
Subject: [PATCH] =?UTF-8?q?refactor:=20OpenIdUserInfoProvider=20=EC=82=AD?=
 =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20OpenIdClient=EC=97=90=20=EB=B9=84?=
 =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?=
 =?UTF-8?q?=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 불필요한 depth 제거
---
 .../auth/domain/OpenIdUserInfoProvider.java   |  6 ---
 .../openid/KakaoOpenIdClient.java             | 54 +++++++++++++++++--
 ...erTest.java => KakaoOpenIdClientTest.java} | 27 +++++-----
 3 files changed, 63 insertions(+), 24 deletions(-)
 delete mode 100644 backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java
 rename backend/src/test/java/com/festago/auth/infrastructure/{KakaoOpenIdUserInfoProviderTest.java => KakaoOpenIdClientTest.java} (86%)

diff --git a/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java b/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java
deleted file mode 100644
index f721d40d4..000000000
--- a/backend/src/main/java/com/festago/auth/domain/OpenIdUserInfoProvider.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.festago.auth.domain;
-
-public interface OpenIdUserInfoProvider {
-
-    UserInfo provide(String idToken);
-}
diff --git a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java b/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java
index 5c2fbd0be..09e1c4cd1 100644
--- a/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java
+++ b/backend/src/main/java/com/festago/auth/infrastructure/openid/KakaoOpenIdClient.java
@@ -1,20 +1,66 @@
 package com.festago.auth.infrastructure.openid;
 
 import com.festago.auth.domain.OpenIdClient;
+import com.festago.auth.domain.OpenIdNonceValidator;
 import com.festago.auth.domain.SocialType;
 import com.festago.auth.domain.UserInfo;
-import lombok.RequiredArgsConstructor;
+import com.festago.common.exception.ErrorCode;
+import com.festago.common.exception.UnauthorizedException;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import java.time.Clock;
+import java.util.Date;
+import java.util.Set;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
+@Slf4j
 @Component
-@RequiredArgsConstructor
 public class KakaoOpenIdClient implements OpenIdClient {
 
-    private final KakaoOpenIdUserInfoProvider kakaoIdTokenUserInfoProvider;
+    private static final String ISSUER = "https://kauth.kakao.com";
+    private final OpenIdNonceValidator openIdNonceValidator;
+    private final OpenIdIdTokenParser idTokenParser;
+    private final Set<String> appKeys;
+
+    public KakaoOpenIdClient(
+        @Value("${festago.oauth2.kakao.rest-api-key}") String restApiKey,
+        @Value("${festago.oauth2.kakao.native-app-key}") String nativeAppKey,
+        KakaoOpenIdPublicKeyLocator kakaoOpenIdPublicKeyLocator,
+        OpenIdNonceValidator openIdNonceValidator,
+        Clock clock
+    ) {
+        this.appKeys = Set.of(restApiKey, nativeAppKey);
+        this.openIdNonceValidator = openIdNonceValidator;
+        this.idTokenParser = new OpenIdIdTokenParser(Jwts.parser()
+            .keyLocator(kakaoOpenIdPublicKeyLocator)
+            .requireIssuer(ISSUER)
+            .clock(() -> Date.from(clock.instant()))
+            .build());
+    }
 
     @Override
     public UserInfo getUserInfo(String idToken) {
-        return kakaoIdTokenUserInfoProvider.provide(idToken);
+        Claims payload = idTokenParser.parse(idToken);
+        openIdNonceValidator.validate(payload.get("nonce", String.class), payload.getExpiration());
+        validateAudience(payload.getAudience());
+        return UserInfo.builder()
+            .socialType(SocialType.KAKAO)
+            .socialId(payload.getSubject())
+            .nickname(payload.get("nickname", String.class))
+            .profileImage(payload.get("picture", String.class))
+            .build();
+    }
+
+    private void validateAudience(Set<String> audiences) {
+        for (String audience : audiences) {
+            if (appKeys.contains(audience)) {
+                return;
+            }
+        }
+        log.info("허용되지 않는 id 토큰의 audience 값이 요청되었습니다. audiences={}", audiences);
+        throw new UnauthorizedException(ErrorCode.OPEN_ID_INVALID_TOKEN);
     }
 
     @Override
diff --git a/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java b/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java
similarity index 86%
rename from backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java
rename to backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java
index 0f03442c9..314d5bcf0 100644
--- a/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdUserInfoProviderTest.java
+++ b/backend/src/test/java/com/festago/auth/infrastructure/KakaoOpenIdClientTest.java
@@ -1,11 +1,10 @@
 package com.festago.auth.infrastructure;
 
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.mock;
-import static org.mockito.BDDMockito.spy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 
 import com.festago.auth.infrastructure.openid.KakaoOpenIdPublicKeyLocator;
 import com.festago.auth.infrastructure.openid.KakaoOpenIdUserInfoProvider;
@@ -28,9 +27,9 @@
 
 @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
 @SuppressWarnings("NonAsciiCharacters")
-class KakaoOpenIdUserInfoProviderTest {
+class KakaoOpenIdClientTest {
 
-    KakaoOpenIdUserInfoProvider kakaoOpenIdUserInfoProvider;
+    KakaoOpenIdClient kakaoOpenIdClient;
 
     KakaoOpenIdPublicKeyLocator keyLocator;
 
@@ -42,7 +41,7 @@ class KakaoOpenIdUserInfoProviderTest {
     void setUp() {
         keyLocator = mock();
         clock = spy(Clock.systemDefaultZone());
-        kakaoOpenIdUserInfoProvider = new KakaoOpenIdUserInfoProvider(
+        kakaoOpenIdClient = new KakaoOpenIdClient(
             "restApiKey",
             "nativeAppKey",
             keyLocator,
@@ -65,7 +64,7 @@ void setUp() {
             .compact();
 
         // when & then
-        assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken))
+        assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken))
             .isInstanceOf(UnauthorizedException.class)
             .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage());
     }
@@ -84,7 +83,7 @@ void setUp() {
             .compact();
 
         // when & then
-        assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken))
+        assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken))
             .isInstanceOf(UnauthorizedException.class)
             .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage());
     }
@@ -104,7 +103,7 @@ void setUp() {
             .compact();
 
         // when & then
-        assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken))
+        assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken))
             .isInstanceOf(UnauthorizedException.class)
             .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage());
     }
@@ -124,7 +123,7 @@ void setUp() {
             .compact();
 
         // when & then
-        assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken))
+        assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken))
             .isInstanceOf(UnauthorizedException.class)
             .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage());
     }
@@ -143,7 +142,7 @@ void setUp() {
             .compact();
 
         // when & then
-        assertThatThrownBy(() -> kakaoOpenIdUserInfoProvider.provide(idToken))
+        assertThatThrownBy(() -> kakaoOpenIdClient.getUserInfo(idToken))
             .isInstanceOf(UnauthorizedException.class)
             .hasMessage(ErrorCode.OPEN_ID_INVALID_TOKEN.getMessage());
     }
@@ -164,7 +163,7 @@ void setUp() {
             .compact();
 
         // when
-        var expect = kakaoOpenIdUserInfoProvider.provide(idToken);
+        var expect = kakaoOpenIdClient.getUserInfo(idToken);
 
         // then
         assertThat(expect.socialId()).isEqualTo(socialId);
@@ -187,7 +186,7 @@ void setUp() {
             .compact();
 
         // when
-        var expect = kakaoOpenIdUserInfoProvider.provide(idToken);
+        var expect = kakaoOpenIdClient.getUserInfo(idToken);
 
         // then
         assertThat(expect.socialId()).isEqualTo(socialId);