3131
3232package com .google .auth .oauth2 ;
3333
34+ import com .google .api .client .http .HttpContent ;
3435import com .google .api .client .http .HttpRequest ;
3536import com .google .api .client .http .HttpResponse ;
3637import com .google .api .client .http .UrlEncodedContent ;
3738import com .google .api .client .http .json .JsonHttpContent ;
39+ import com .google .api .client .util .Data ;
3840import com .google .api .client .util .GenericData ;
3941import com .google .gson .Gson ;
4042import java .nio .charset .StandardCharsets ;
@@ -59,10 +61,15 @@ class Slf4jLoggingHelpers {
5961 "token" ,
6062 "assertion" ,
6163 "access_token" ,
64+ // IAM Credentials getAccessTokenCall returns `accessToken` and not `access_token`:
65+ // https://docs.cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
66+ "accessToken" ,
6267 "client_secret" ,
6368 "refresh_token" ,
6469 "signedBlob" ,
65- "authorization" ));
70+ "authorization" ,
71+ "subject_token" ,
72+ "id_token" ));
6673 }
6774
6875 static void logRequest (HttpRequest request , LoggerProvider loggerProvider , String message ) {
@@ -73,35 +80,24 @@ static void logRequest(HttpRequest request, LoggerProvider loggerProvider, Strin
7380 loggingDataMap .put ("request.method" , request .getRequestMethod ());
7481 loggingDataMap .put ("request.url" , request .getUrl ().toString ());
7582
76- Map <String , Object > headers = new HashMap <>();
77- request
78- .getHeaders ()
79- .forEach (
80- (key , val ) -> {
81- if (SENSITIVE_KEYS .contains (key )) {
82- String hashedVal = calculateSHA256Hash (String .valueOf (val ));
83- headers .put (key , hashedVal );
84- } else {
85- headers .put (key , val );
86- }
87- });
83+ Map <String , Object > headers = parseGenericData (request .getHeaders ());
8884 loggingDataMap .put ("request.headers" , gson .toJson (headers ));
8985
9086 if (request .getContent () != null && logger .isDebugEnabled ()) {
91- // are payload always GenericData? If so, can parse and store in json
92- if (request .getContent () instanceof UrlEncodedContent ) {
93- // this is parsed to GenericData because that is how it is constructed.
94- GenericData data = (GenericData ) ((UrlEncodedContent ) request .getContent ()).getData ();
87+ Map <String , Object > data = null ;
88+ HttpContent content = request .getContent ();
89+ if (content instanceof UrlEncodedContent ) {
90+ data = Data .mapOf (((UrlEncodedContent ) content ).getData ());
91+ } else if (content instanceof JsonHttpContent ) {
92+ data = Data .mapOf (((JsonHttpContent ) content ).getData ());
93+ }
94+ if (data != null ) {
95+ // this call will mask the sensitive keys in the payload
9596 Map <String , Object > contextMap = parseGenericData (data );
9697 loggingDataMap .put ("request.payload" , gson .toJson (contextMap ));
97- } else if (request .getContent () instanceof JsonHttpContent ) {
98- String jsonData = gson .toJson (((JsonHttpContent ) request .getContent ()).getData ());
99- loggingDataMap .put ("request.payload" , jsonData );
10098 }
101-
10299 Slf4jUtils .log (logger , org .slf4j .event .Level .DEBUG , loggingDataMap , message );
103100 } else {
104-
105101 Slf4jUtils .log (logger , org .slf4j .event .Level .INFO , loggingDataMap , message );
106102 }
107103 }
@@ -132,7 +128,6 @@ static void logResponse(HttpResponse response, LoggerProvider loggerProvider, St
132128 static void logResponsePayload (
133129 GenericData genericData , LoggerProvider loggerProvider , String message ) {
134130 try {
135-
136131 Logger logger = loggerProvider .getLogger ();
137132 if (logger .isDebugEnabled ()) {
138133 Map <String , Object > contextMap = parseGenericData (genericData );
@@ -168,22 +163,43 @@ static org.slf4j.event.Level matchUtilLevelToSLF4JLevel(Level level) {
168163 }
169164 }
170165
171- private static Map <String , Object > parseGenericData (GenericData genericData ) {
166+ /**
167+ * Parses standard {@link GenericData} key-value payloads into a loggable format.
168+ *
169+ * <p>Any key that exists within the {@code SENSITIVE_KEYS} set (e.g. access tokens, refresh
170+ * tokens) will have its corresponding value masked via a SHA-256 hash. This guarantees that
171+ * sensitive secrets are never logged in plain text while keeping the hash signature consistent
172+ * for debugging purposes. Non-sensitive keys are passed through directly as strings.
173+ *
174+ * @param data the payload data to parse
175+ * @return a map containing the safely parsed and optionally masked context
176+ */
177+ @ SuppressWarnings ("unchecked" )
178+ private static Map <String , Object > parseGenericData (Map <String , Object > data ) {
172179 Map <String , Object > contextMap = new HashMap <>();
173- genericData .forEach (
180+ data .forEach (
174181 (key , val ) -> {
175182 if (SENSITIVE_KEYS .contains (key )) {
176183 String secretString = String .valueOf (val );
177184 String hashedVal = calculateSHA256Hash (secretString );
178185 contextMap .put (key , hashedVal );
179186 } else {
180- contextMap .put (key , val . toString ( ));
187+ contextMap .put (key , String . valueOf ( val ));
181188 }
182189 });
183190 return contextMap ;
184191 }
185192
186- // calculate SHA256Hash so we do not print secrets directly to log
193+ /**
194+ * Applies a SHA-256 one-way cryptographic hash to securely mask sensitive data.
195+ *
196+ * <p>Logging must be completely fail-safe. If the SHA-256 algorithm is unsupported on the system,
197+ * this will gracefully downgrade to a static error fallback message to ensure the underlying auth
198+ * request does not crash.
199+ *
200+ * @param data the sensitive secret string to hash
201+ * @return the resulting 64-character hex string representing the hashed secret
202+ */
187203 private static String calculateSHA256Hash (String data ) {
188204 try {
189205 MessageDigest digest = MessageDigest .getInstance ("SHA-256" );
@@ -195,6 +211,12 @@ private static String calculateSHA256Hash(String data) {
195211 }
196212 }
197213
214+ /**
215+ * Converts a raw cryptographic byte array digest into a padded, readable hexadecimal String.
216+ *
217+ * @param hash the digested byte array
218+ * @return a formatted hexadecimal string representation
219+ */
198220 private static String bytesToHex (byte [] hash ) {
199221 StringBuilder hexString = new StringBuilder (2 * hash .length );
200222 for (byte b : hash ) {
0 commit comments