diff --git a/Dockerfile b/Dockerfile
index dbf4550..2340f1c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,8 @@ RUN mvn clean package -DskipTests -B
FROM eclipse-temurin:21-jre
+ENV TZ=Asia/Seoul
+
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
diff --git a/pom.xml b/pom.xml
index 4c418f3..d8b1cf5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,10 +59,7 @@
lombok
true
-
- org.springframework.cloud
- spring-cloud-netflix-eureka-client
-
+
org.springframework.boot
spring-boot-starter-test
@@ -126,15 +123,43 @@
spring-boot-starter-mail
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-loadbalancer
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-circuitbreaker-resilience4j
+
+
io.micrometer
micrometer-registry-prometheus
+
- org.springframework.cloud
- spring-cloud-config-client
+ io.micrometer
+ micrometer-tracing-bridge-brave
+
+
+ io.zipkin.reporter2
+ zipkin-reporter-brave
+
+
+
+
+ org.redisson
+ redisson-spring-boot-starter
+ 3.52.0
diff --git a/src/main/java/com/nhnacademy/insightonauth/InsightonAuthApplication.java b/src/main/java/com/nhnacademy/insightonauth/InsightonAuthApplication.java
index 8820c31..e9bc098 100644
--- a/src/main/java/com/nhnacademy/insightonauth/InsightonAuthApplication.java
+++ b/src/main/java/com/nhnacademy/insightonauth/InsightonAuthApplication.java
@@ -3,9 +3,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableDiscoveryClient
+@EnableFeignClients
+@EnableScheduling
public class InsightonAuthApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/nhnacademy/insightonauth/client/CoreClient.java b/src/main/java/com/nhnacademy/insightonauth/client/CoreClient.java
new file mode 100644
index 0000000..b0d05ef
--- /dev/null
+++ b/src/main/java/com/nhnacademy/insightonauth/client/CoreClient.java
@@ -0,0 +1,13 @@
+package com.nhnacademy.insightonauth.client;
+
+import com.nhnacademy.insightonauth.dto.core.ManagerGroupExistsResponse;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@FeignClient(name = "insighton-core")
+public interface CoreClient {
+
+ @GetMapping("/internal/v1/users/{userId}/manager-groups/exists")
+ ManagerGroupExistsResponse existsManagerGroup(@PathVariable Long userId);
+}
diff --git a/src/main/java/com/nhnacademy/insightonauth/client/OauthClient.java b/src/main/java/com/nhnacademy/insightonauth/client/OauthClient.java
index 123875f..7ae7851 100644
--- a/src/main/java/com/nhnacademy/insightonauth/client/OauthClient.java
+++ b/src/main/java/com/nhnacademy/insightonauth/client/OauthClient.java
@@ -1,65 +1,8 @@
package com.nhnacademy.insightonauth.client;
import com.nhnacademy.insightonauth.dto.oauth.OauthUserInfo;
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.MediaType;
-import org.springframework.stereotype.Component;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestClient;
-import java.util.Map;
+public interface OauthClient {
-@Component
-@RequiredArgsConstructor
-public class OauthClient {
-
- @Value("${oauth.google.client-id}")
- private String clientId;
-
- @Value("${oauth.google.client-secret}")
- private String clientSecret;
-
- @Value("${oauth.google.redirect-uri}")
- private String redirectUri;
-
- private final RestClient restClient = RestClient.create();
-
- public OauthUserInfo getUserInfo(String provider, String code) {
- String accessToken = requestAccessToken(code);
- return requestUserInfo(accessToken);
- }
-
- private String requestAccessToken(String code) {
- MultiValueMap body = new LinkedMultiValueMap<>();
- body.add("code", code);
- body.add("client_id", clientId);
- body.add("client_secret", clientSecret);
- body.add("redirect_uri", redirectUri);
- body.add("grant_type", "authorization_code");
-
- Map response = restClient.post()
- .uri("https://oauth2.googleapis.com/token")
- .contentType(MediaType.APPLICATION_FORM_URLENCODED)
- .body(body)
- .retrieve()
- .body(Map.class);
-
- return (String) response.get("access_token");
- }
-
- private OauthUserInfo requestUserInfo(String accessToken) {
- Map userInfo = restClient.get()
- .uri("https://www.googleapis.com/oauth2/v3/userinfo")
- .header("Authorization", "Bearer " + accessToken)
- .retrieve()
- .body(Map.class);
-
- return new OauthUserInfo(
- (String) userInfo.get("email"),
- (String) userInfo.get("name"),
- (String) userInfo.get("sub")
- );
- }
+ OauthUserInfo getUserInfo(String code);
}
diff --git a/src/main/java/com/nhnacademy/insightonauth/client/OauthClientResolver.java b/src/main/java/com/nhnacademy/insightonauth/client/OauthClientResolver.java
new file mode 100644
index 0000000..8e880bc
--- /dev/null
+++ b/src/main/java/com/nhnacademy/insightonauth/client/OauthClientResolver.java
@@ -0,0 +1,22 @@
+package com.nhnacademy.insightonauth.client;
+
+import com.nhnacademy.insightonauth.exception.UnsupportedOAuthProviderException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class OauthClientResolver {
+ // 스프링이 bean 객체를 가져다줌
+ private final Map clients;
+
+ public OauthClient resolve(String provider) {
+ OauthClient client = clients.get(provider + "OauthClient");
+ if (client == null) {
+ throw new UnsupportedOAuthProviderException(provider);
+ }
+ return client;
+ }
+}
diff --git a/src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java b/src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java
new file mode 100644
index 0000000..882b302
--- /dev/null
+++ b/src/main/java/com/nhnacademy/insightonauth/client/impl/GithubOauthClient.java
@@ -0,0 +1,103 @@
+package com.nhnacademy.insightonauth.client.impl;
+
+import com.nhnacademy.insightonauth.client.OauthClient;
+import com.nhnacademy.insightonauth.dto.oauth.OauthUserInfo;
+import com.nhnacademy.insightonauth.exception.EmailNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
+import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestClient;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+@Component("githubOauthClient")
+@RequiredArgsConstructor
+public class GithubOauthClient implements OauthClient {
+
+ @Value("${oauth.github.client-id}")
+ private String clientId;
+
+ @Value("${oauth.github.client-secret}")
+ private String clientSecret;
+
+ @Value("${oauth.redirect-uri}")
+ private String redirectUri;
+
+ private final RestClient restClient = RestClient.builder()
+ .requestFactory(ClientHttpRequestFactoryBuilder.detect()
+ .build(ClientHttpRequestFactorySettings.defaults()
+ .withConnectTimeout(Duration.ofSeconds(3))
+ .withReadTimeout(Duration.ofSeconds(5))))
+ .build();
+
+ @Override
+ public OauthUserInfo getUserInfo(String code) {
+ String accessToken = requestAccessToken(code);
+ return requestUserInfo(accessToken);
+ }
+
+ private String requestAccessToken(String code) {
+ MultiValueMap body = new LinkedMultiValueMap<>();
+ body.add("code", code);
+ body.add("client_id", clientId);
+ body.add("client_secret", clientSecret);
+ body.add("redirect_uri", redirectUri);
+
+ Map response = restClient.post()
+ .uri("https://github.com/login/oauth/access_token") // GitHub URL로 수정
+ .header("Accept", "application/json") // JSON 응답 요청 (필수)
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .body(body)
+ .retrieve()
+ .body(Map.class);
+
+ return (String) response.get("access_token");
+ }
+
+ private OauthUserInfo requestUserInfo(String accessToken) {
+ Map userInfo = restClient.get()
+ .uri("https://api.github.com/user") // GitHub URL로 수정
+ .header("Authorization", "Bearer " + accessToken)
+ .retrieve()
+ .body(Map.class);
+
+ String email = requestPrimaryEmail(accessToken);
+
+ String name = (String) userInfo.get("name");
+ if (name == null || name.isBlank()) {
+ name = (String) userInfo.get("login"); // name이 없으면 login(아이디)으로 대체
+ }
+
+ Object id = userInfo.get("id"); // GitHub은 sub가 아니라 id (숫자)
+
+ return new OauthUserInfo(
+ email,
+ name,
+ String.valueOf(id)
+ );
+ }
+
+ // 이메일 열람 불가의 경우 api를 통해서 가져와야함
+ private String requestPrimaryEmail(String accessToken) {
+ List