32
32
package com .google .auth .oauth2 ;
33
33
34
34
import com .google .api .client .http .GenericUrl ;
35
+ import com .google .api .client .http .HttpBackOffIOExceptionHandler ;
36
+ import com .google .api .client .http .HttpBackOffUnsuccessfulResponseHandler ;
35
37
import com .google .api .client .http .HttpRequest ;
38
+ import com .google .api .client .http .HttpRequestFactory ;
36
39
import com .google .api .client .http .HttpResponse ;
37
40
import com .google .api .client .http .HttpStatusCodes ;
38
41
import com .google .api .client .http .HttpTransport ;
39
42
import com .google .api .client .http .json .JsonHttpContent ;
40
43
import com .google .api .client .json .GenericJson ;
41
44
import com .google .api .client .json .JsonObjectParser ;
45
+ import com .google .api .client .util .ExponentialBackOff ;
42
46
import com .google .api .client .util .GenericData ;
43
47
import com .google .auth .Credentials ;
44
48
import com .google .auth .ServiceAccountSigner ;
45
49
import com .google .auth .http .HttpCredentialsAdapter ;
46
50
import com .google .common .io .BaseEncoding ;
47
51
import java .io .IOException ;
48
52
import java .io .InputStream ;
53
+ import java .util .Arrays ;
54
+ import java .util .HashSet ;
49
55
import java .util .Map ;
56
+ import java .util .Set ;
50
57
51
58
/**
52
59
* This internal class provides shared utilities for interacting with the IAM API for common
@@ -60,6 +67,11 @@ class IamUtils {
60
67
private static final String PARSE_ERROR_MESSAGE = "Error parsing error message response. " ;
61
68
private static final String PARSE_ERROR_SIGNATURE = "Error parsing signature response. " ;
62
69
70
+ // Following guidance for IAM retries:
71
+ // https://cloud.google.com/iam/docs/retry-strategy#errors-to-retry
72
+ static final Set <Integer > IAM_RETRYABLE_STATUS_CODES =
73
+ new HashSet <>(Arrays .asList (500 , 502 , 503 , 504 ));
74
+
63
75
/**
64
76
* Returns a signature for the provided bytes.
65
77
*
@@ -78,11 +90,12 @@ static byte[] sign(
78
90
byte [] toSign ,
79
91
Map <String , ?> additionalFields ) {
80
92
BaseEncoding base64 = BaseEncoding .base64 ();
93
+ HttpRequestFactory factory =
94
+ transport .createRequestFactory (new HttpCredentialsAdapter (credentials ));
81
95
String signature ;
82
96
try {
83
97
signature =
84
- getSignature (
85
- serviceAccountEmail , credentials , transport , base64 .encode (toSign ), additionalFields );
98
+ getSignature (serviceAccountEmail , base64 .encode (toSign ), additionalFields , factory );
86
99
} catch (IOException ex ) {
87
100
throw new ServiceAccountSigner .SigningException ("Failed to sign the provided bytes" , ex );
88
101
}
@@ -91,10 +104,9 @@ static byte[] sign(
91
104
92
105
private static String getSignature (
93
106
String serviceAccountEmail ,
94
- Credentials credentials ,
95
- HttpTransport transport ,
96
107
String bytes ,
97
- Map <String , ?> additionalFields )
108
+ Map <String , ?> additionalFields ,
109
+ HttpRequestFactory factory )
98
110
throws IOException {
99
111
String signBlobUrl = String .format (SIGN_BLOB_URL_FORMAT , serviceAccountEmail );
100
112
GenericUrl genericUrl = new GenericUrl (signBlobUrl );
@@ -106,13 +118,27 @@ private static String getSignature(
106
118
}
107
119
JsonHttpContent signContent = new JsonHttpContent (OAuth2Utils .JSON_FACTORY , signRequest );
108
120
109
- HttpCredentialsAdapter adapter = new HttpCredentialsAdapter (credentials );
110
- HttpRequest request =
111
- transport .createRequestFactory (adapter ).buildPostRequest (genericUrl , signContent );
121
+ HttpRequest request = factory .buildPostRequest (genericUrl , signContent );
112
122
113
123
JsonObjectParser parser = new JsonObjectParser (OAuth2Utils .JSON_FACTORY );
114
124
request .setParser (parser );
115
125
request .setThrowExceptionOnExecuteError (false );
126
+ request .setNumberOfRetries (OAuth2Utils .DEFAULT_NUMBER_OF_RETRIES );
127
+
128
+ ExponentialBackOff backoff =
129
+ new ExponentialBackOff .Builder ()
130
+ .setInitialIntervalMillis (OAuth2Utils .INITIAL_RETRY_INTERVAL_MILLIS )
131
+ .setRandomizationFactor (OAuth2Utils .RETRY_RANDOMIZATION_FACTOR )
132
+ .setMultiplier (OAuth2Utils .RETRY_MULTIPLIER )
133
+ .build ();
134
+
135
+ // Retry on 500, 502, 503, and 503 status codes
136
+ request .setUnsuccessfulResponseHandler (
137
+ new HttpBackOffUnsuccessfulResponseHandler (backoff )
138
+ .setBackOffRequired (
139
+ response ->
140
+ IamUtils .IAM_RETRYABLE_STATUS_CODES .contains (response .getStatusCode ())));
141
+ request .setIOExceptionHandler (new HttpBackOffIOExceptionHandler (backoff ));
116
142
117
143
HttpResponse response = request .execute ();
118
144
int statusCode = response .getStatusCode ();
@@ -125,6 +151,8 @@ private static String getSignature(
125
151
String .format (
126
152
"Error code %s trying to sign provided bytes: %s" , statusCode , errorMessage ));
127
153
}
154
+
155
+ // Request will have retried a 5xx error 3 times and is still receiving a 5xx error code
128
156
if (statusCode != HttpStatusCodes .STATUS_CODE_OK ) {
129
157
throw new IOException (
130
158
String .format (
@@ -152,8 +180,8 @@ private static String getSignature(
152
180
* @param additionalFields additional fields to send in the IAM call
153
181
* @return IdToken issed to the serviceAccount
154
182
* @throws IOException if the IdToken cannot be issued.
155
- * @see
156
- * https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/generateIdToken
183
+ * @see <a
184
+ * href=" https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/generateIdToken">...</a>
157
185
*/
158
186
static IdToken getIdToken (
159
187
String serviceAccountEmail ,
0 commit comments