88import  org .springframework .stereotype .Service ;
99import  org .springframework .util .LinkedMultiValueMap ;
1010import  org .springframework .util .MultiValueMap ;
11+ import  org .springframework .web .client .HttpClientErrorException ;
1112import  org .springframework .web .client .RestTemplate ;
13+ import  org .slf4j .Logger ;
14+ import  org .slf4j .LoggerFactory ;
1215
1316import  java .util .Collections ;
1417import  java .util .HashMap ;
1518import  java .util .Map ;
19+ import  java .util .concurrent .TimeUnit ;
1620
1721@ Service 
1822public  class  GoogleAuthImpl  implements  GoogleAuthService  {
1923    private  final  String  GOOGLE_TOKEN_URL  = "https://oauth2.googleapis.com/token" ;
2024    private  final  String  GOOGLE_USERINFO_URL  = "https://www.googleapis.com/oauth2/v2/userinfo" ;
2125
26+     // 재시도 설정 
27+     private  final  int  MAX_RETRY_COUNT  = 3 ;
28+     private  final  long  INITIAL_BACKOFF_MS  = 1000 ; // 1초 
29+     
30+     private  static  final  Logger  log  = LoggerFactory .getLogger (GoogleAuthImpl .class );
31+ 
2232    @ Value ("${spring.security.oauth2.client.registration.google.client-id}" )
2333    private  String  clientId ;
2434    @ Value ("${spring.security.oauth2.client.registration.google.client-secret}" )
@@ -36,74 +46,123 @@ public GoogleAuthImpl(HttpServletRequest request) {
3646
3747    @ Override 
3848    public  String  getAccessToken (String  code ) {
39-         System .out .println ("[GoogleAuthImpl] getAccessToken 시작 - code: "  + code );
40-         RestTemplate  restTemplate  = new  RestTemplate ();
41-         HttpHeaders  headers  = new  HttpHeaders ();
42-         headers .setContentType (MediaType .APPLICATION_FORM_URLENCODED );
43-         headers .setAccept (Collections .singletonList (MediaType .APPLICATION_JSON ));
44- 
45-         String  finalRedirectUrl  = redirectUrl ;
46-         System .out .println ("[GoogleAuthImpl] 선택된 redirectUrl: "  + finalRedirectUrl );
47- 
48-         MultiValueMap <String , String > params  = new  LinkedMultiValueMap <>();
49-         params .add ("grant_type" , "authorization_code" );
50-         params .add ("client_id" , clientId );
51-         params .add ("client_secret" , clientSecret );
52-         params .add ("redirect_uri" , finalRedirectUrl );
53-         params .add ("code" , code );
54- 
55-         HttpEntity <MultiValueMap <String , String >> request  = new  HttpEntity <>(params , headers );
56-         ResponseEntity <String > response  = restTemplate .exchange (GOOGLE_TOKEN_URL , HttpMethod .POST , request , String .class );
57- 
58-         try  {
59-             ObjectMapper  objectMapper  = new  ObjectMapper ();
60-             JsonNode  jsonNode  = objectMapper .readTree (response .getBody ());
61-             String  accessToken  = jsonNode .get ("access_token" ).asText ();
62-             System .out .println ("[GoogleAuthImpl] Access Token 발급 성공" );
63-             return  accessToken ;
64-         } catch  (Exception  e ) {
65-             System .out .println ("[GoogleAuthImpl] Access Token 발급 실패: "  + e .getMessage ());
66-             e .printStackTrace ();
67-             throw  new  RuntimeException ("Google Access Token 요청 실패" , e );
49+         log .info ("[GoogleAuthImpl] getAccessToken 시작 - code: {}" , code );
50+         
51+         for  (int  retryCount  = 0 ; retryCount  <= MAX_RETRY_COUNT ; retryCount ++) {
52+             try  {
53+                 if  (retryCount  > 0 ) {
54+                     long  backoffTime  = INITIAL_BACKOFF_MS  * (long ) Math .pow (2 , retryCount  - 1 );
55+                     log .info ("[GoogleAuthImpl] 재시도 #{} - {}ms 대기 후 시도합니다." , retryCount , backoffTime );
56+                     TimeUnit .MILLISECONDS .sleep (backoffTime );
57+                 }
58+                 
59+                 RestTemplate  restTemplate  = new  RestTemplate ();
60+                 HttpHeaders  headers  = new  HttpHeaders ();
61+                 headers .setContentType (MediaType .APPLICATION_FORM_URLENCODED );
62+                 headers .setAccept (Collections .singletonList (MediaType .APPLICATION_JSON ));
63+ 
64+                 String  finalRedirectUrl  = redirectUrl ;
65+                 log .info ("[GoogleAuthImpl] 선택된 redirectUrl: {}" , finalRedirectUrl );
66+ 
67+                 MultiValueMap <String , String > params  = new  LinkedMultiValueMap <>();
68+                 params .add ("grant_type" , "authorization_code" );
69+                 params .add ("client_id" , clientId );
70+                 params .add ("client_secret" , clientSecret );
71+                 params .add ("redirect_uri" , finalRedirectUrl );
72+                 params .add ("code" , code );
73+ 
74+                 HttpEntity <MultiValueMap <String , String >> request  = new  HttpEntity <>(params , headers );
75+                 ResponseEntity <String > response  = restTemplate .exchange (GOOGLE_TOKEN_URL , HttpMethod .POST , request , String .class );
76+ 
77+                 ObjectMapper  objectMapper  = new  ObjectMapper ();
78+                 JsonNode  jsonNode  = objectMapper .readTree (response .getBody ());
79+                 String  accessToken  = jsonNode .get ("access_token" ).asText ();
80+                 log .info ("[GoogleAuthImpl] Access Token 발급 성공" );
81+                 return  accessToken ;
82+             } catch  (HttpClientErrorException  e ) {
83+                 if  (e .getStatusCode ().value () == 429 ) { // Too Many Requests 
84+                     if  (retryCount  == MAX_RETRY_COUNT ) {
85+                         log .error ("[GoogleAuthImpl] 최대 재시도 횟수({})를 초과했습니다. 요청 속도 제한(429)으로 실패" , MAX_RETRY_COUNT );
86+                         throw  new  RuntimeException ("구글 API 요청 속도 제한 초과" , e );
87+                     }
88+                     log .warn ("[GoogleAuthImpl] 요청 속도 제한(429) 발생, 재시도 #{}" , retryCount  + 1 );
89+                 } else  if  (e .getStatusCode ().value () == 400 ) {
90+                     log .error ("[GoogleAuthImpl] 잘못된 요청 (400): {}" , e .getResponseBodyAsString ());
91+                     throw  new  RuntimeException ("구글 Access Token 요청 실패: 잘못된 요청 - "  + e .getResponseBodyAsString (), e );
92+                 } else  {
93+                     log .error ("[GoogleAuthImpl] Access Token 발급 실패: HTTP 오류 {}" , e .getStatusCode ());
94+                     throw  new  RuntimeException ("구글 Access Token 요청 실패: "  + e .getMessage (), e );
95+                 }
96+             } catch  (InterruptedException  e ) {
97+                 Thread .currentThread ().interrupt ();
98+                 log .error ("[GoogleAuthImpl] 재시도 대기 중 인터럽트 발생" );
99+                 throw  new  RuntimeException ("구글 Access Token 요청 중단" , e );
100+             } catch  (Exception  e ) {
101+                 log .error ("[GoogleAuthImpl] Access Token 발급 실패: {}" , e .getMessage ());
102+                 throw  new  RuntimeException ("구글 Access Token 요청 실패" , e );
103+             }
68104        }
105+         throw  new  RuntimeException ("구글 Access Token 요청 실패 - 재시도 후에도 실패" );
69106    }
70107
71108    @ Override 
72109    public  Map <String , Object > getUserInfo (String  accessToken ) {
73-         System .out .println ("[GoogleAuthImpl] getUserInfo 시작" );
74-         RestTemplate  restTemplate  = new  RestTemplate ();
75-         HttpHeaders  headers  = new  HttpHeaders ();
76-         headers .setBearerAuth (accessToken );
77-         headers .setAccept (Collections .singletonList (MediaType .APPLICATION_JSON ));
78- 
79-         HttpEntity <Void > entity  = new  HttpEntity <>(headers );
80-         ResponseEntity <String > response  = restTemplate .exchange (GOOGLE_USERINFO_URL , HttpMethod .GET , entity , String .class );
81- 
82-         try  {
83-             ObjectMapper  objectMapper  = new  ObjectMapper ();
84-             JsonNode  jsonNode  = objectMapper .readTree (response .getBody ());
85- 
86-             Map <String , Object > userInfo  = new  HashMap <>();
87-             String  socialId  = jsonNode .get ("id" ).asText ();
88-             String  email  = jsonNode .get ("email" ).asText ();
89-             String  username  = jsonNode .get ("name" ).asText ();
90-             
91-             System .out .println ("[GoogleAuthImpl] 사용자 정보 파싱 결과:" );
92-             System .out .println ("- socialId: "  + socialId );
93-             System .out .println ("- email: "  + email );
94-             System .out .println ("- username: "  + username );
95-             
96-             userInfo .put ("socialId" , socialId );
97-             userInfo .put ("email" , email );
98-             userInfo .put ("username" , username );
99- 
100-             System .out .println ("[GoogleAuthImpl] 사용자 정보 조회 성공" );
101-             return  userInfo ;
102-         } catch  (Exception  e ) {
103-             System .out .println ("[GoogleAuthImpl] 사용자 정보 조회 실패: "  + e .getMessage ());
104-             System .out .println ("[GoogleAuthImpl] 응답 내용: "  + response .getBody ());
105-             e .printStackTrace ();
106-             throw  new  RuntimeException ("Google 사용자 정보 요청 실패" , e );
110+         log .info ("[GoogleAuthImpl] getUserInfo 시작" );
111+         
112+         for  (int  retryCount  = 0 ; retryCount  <= MAX_RETRY_COUNT ; retryCount ++) {
113+             try  {
114+                 if  (retryCount  > 0 ) {
115+                     long  backoffTime  = INITIAL_BACKOFF_MS  * (long ) Math .pow (2 , retryCount  - 1 );
116+                     log .info ("[GoogleAuthImpl] 사용자 정보 조회 재시도 #{} - {}ms 대기 후 시도합니다." , retryCount , backoffTime );
117+                     TimeUnit .MILLISECONDS .sleep (backoffTime );
118+                 }
119+                 
120+                 RestTemplate  restTemplate  = new  RestTemplate ();
121+                 HttpHeaders  headers  = new  HttpHeaders ();
122+                 headers .setBearerAuth (accessToken );
123+                 headers .setAccept (Collections .singletonList (MediaType .APPLICATION_JSON ));
124+ 
125+                 HttpEntity <Void > entity  = new  HttpEntity <>(headers );
126+                 ResponseEntity <String > response  = restTemplate .exchange (GOOGLE_USERINFO_URL , HttpMethod .GET , entity , String .class );
127+ 
128+                 ObjectMapper  objectMapper  = new  ObjectMapper ();
129+                 JsonNode  jsonNode  = objectMapper .readTree (response .getBody ());
130+ 
131+                 Map <String , Object > userInfo  = new  HashMap <>();
132+                 String  socialId  = jsonNode .get ("id" ).asText ();
133+                 String  email  = jsonNode .get ("email" ).asText ();
134+                 String  username  = jsonNode .get ("name" ).asText ();
135+                 
136+                 log .info ("[GoogleAuthImpl] 사용자 정보 파싱 결과: socialId={}, email={}, username={}" , 
137+                         socialId , email , username );
138+                 
139+                 userInfo .put ("socialId" , socialId );
140+                 userInfo .put ("email" , email );
141+                 userInfo .put ("username" , username );
142+ 
143+                 log .info ("[GoogleAuthImpl] 사용자 정보 조회 성공" );
144+                 return  userInfo ;
145+             } catch  (HttpClientErrorException  e ) {
146+                 if  (e .getStatusCode ().value () == 429 ) { // Too Many Requests 
147+                     if  (retryCount  == MAX_RETRY_COUNT ) {
148+                         log .error ("[GoogleAuthImpl] 사용자 정보 조회 - 최대 재시도 횟수({})를 초과했습니다. 요청 속도 제한(429)으로 실패" , MAX_RETRY_COUNT );
149+                         throw  new  RuntimeException ("구글 API 요청 속도 제한 초과" , e );
150+                     }
151+                     log .warn ("[GoogleAuthImpl] 사용자 정보 조회 - 요청 속도 제한(429) 발생, 재시도 #{}" , retryCount  + 1 );
152+                 } else  {
153+                     log .error ("[GoogleAuthImpl] 사용자 정보 조회 실패: HTTP 오류 {}" , e .getStatusCode ());
154+                     throw  new  RuntimeException ("구글 사용자 정보 요청 실패: "  + e .getMessage (), e );
155+                 }
156+             } catch  (InterruptedException  e ) {
157+                 Thread .currentThread ().interrupt ();
158+                 log .error ("[GoogleAuthImpl] 사용자 정보 조회 - 재시도 대기 중 인터럽트 발생" );
159+                 throw  new  RuntimeException ("구글 사용자 정보 요청 중단" , e );
160+             } catch  (Exception  e ) {
161+                 log .error ("[GoogleAuthImpl] 사용자 정보 조회 실패: {}" , e .getMessage ());
162+                 log .error ("[GoogleAuthImpl] 응답 내용: {}" , e .getMessage ());
163+                 throw  new  RuntimeException ("구글 사용자 정보 요청 실패" , e );
164+             }
107165        }
166+         throw  new  RuntimeException ("구글 사용자 정보 요청 실패 - 재시도 후에도 실패" );
108167    }
109168}
0 commit comments