[google-auth-library-java] OAuthException.createFromHttpResponseException throws ClassCastException on a Google API error body, losing the HTTP status and body

未關閉
#14,514 1 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
3/5
預估耗時
1-2 天
新手友好度
72/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
活躍
技術堆疊
java

研究方向

Start with OAuthException.java:90 and run the deterministic createFromHttpResponseException reproduction using the supplied Google API error body. Trace its callers in StsRequestHandler.exchangeToken and ExternalAccountAuthorizedUserCredentials.java:204, then add regression coverage showing that the response status and body are retained without the ClassCastException, including a body without an error key.

由索引模型根據 Issue 內容生成。

描述

priority: p2 type: bug

Environment details

  1. API: auth, workload identity federation (AwsCredentials, STS token exchange during Cloud Storage calls)
  2. OS type and version: Linux (AWS Lambda and AWS ECS)
  3. Java version: 25
  4. Version(s): google-auth-library-oauth2-http 1.50.0 (libraries-bom 26.86.0), google-http-client 2.2.0. createFromHttpResponseException is unchanged in 1.53.0 and on main (05a998f).

Steps to reproduce

  1. Deterministic, no credentials needed: call OAuthException.createFromHttpResponseException with an HttpResponseException whose content is a Google API error body (snippet below). It throws ClassCastException.
  2. Against the real STS: an AwsCredentials whose token_url is https://sts.googleapis.com/v1/introspect gets HTTP 400 with {"error":{"code":400,"message":"Invalid JSON payload received. ...","status":"INVALID_ARGUMENT"}} and fails with the same ClassCastException. The misconfigured URL is only a way to get that body shape on demand; in production the same exception comes from https://sts.googleapis.com/v1/token.

Code example

package com.google.auth.oauth2;

import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;

class OAuthExceptionNestedErrorRepro {
  public static void main(String[] args) throws Exception {
    HttpResponseException e =
        new HttpResponseException.Builder(400, "Bad Request", new HttpHeaders())
            .setContent(
                "{\"error\":{\"code\":400,\"message\":\"Request contains an invalid argument.\",\"status\":\"INVALID_ARGUMENT\"}}")
            .build();
    OAuthException.createFromHttpResponseException(e); // java.lang.ClassCastException
  }
}

Stack trace

Production, workload identity federation from AWS, token exchange triggered by a Cloud Storage request:

java.lang.ClassCastException: class com.google.api.client.util.ArrayMap cannot be cast to class java.lang.String
  at com.google.auth.oauth2.OAuthException.createFromHttpResponseException(OAuthException.java:90)
  at com.google.auth.oauth2.StsRequestHandler.exchangeToken(StsRequestHandler.java:129)
  at com.google.auth.oauth2.ExternalAccountCredentials.exchangeExternalCredentialForAccessToken(ExternalAccountCredentials.java:567)
  at com.google.auth.oauth2.AwsCredentials.refreshAccessToken(AwsCredentials.java:132)
  at com.google.auth.oauth2.OAuth2Credentials.getRequestMetadata(OAuth2Credentials.java:189)

External references such as API reference guides

Any additional information below

  • OAuthException.java:90 casts error to String. The request-validation errors we could trigger on /v1/token come back in the OAuth form ({"error":"invalid_request","error_description":"..."}), but some production responses from /v1/token carry error as an object (Google's API error format), which makes the cast throw. We could not capture those bodies because the library drops them.
  • The ClassCastException is thrown from inside catch (HttpResponseException e) in StsRequestHandler.exchangeToken. It is a RuntimeException, so it escapes the IOException contract of refresh() / getRequestMetadata(), and it has no cause, so the HttpResponseException holding the status and body is lost. LoggingUtils logs the response only on success, so the body is not logged either. When this happened to us intermittently in production (a burst of failures across several independent GCP projects within minutes), we could not tell whether STS was rate limiting, unavailable, or refusing the credential.
  • Because the ClassCastException is not an IOException, google-cloud-storage classifies it as unretryable (Unretryable error (attempts: 1, maxAttempts: 6)), so a transient STS failure is not retried either.
  • The same method is also called from ExternalAccountAuthorizedUserCredentials.java:204.
  • Related: a body without an error key makes the constructor's checkNotNull(errorCode) throw NullPointerException.
主要語言
Java
星號
2.1k
分支
1.2k
平均合併
2 天 14 小時
30 天內合併 PR
192

環境準備

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 閱讀貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

googleapis/google-cloud-java 的其他 Issue

查看 googleapis/google-cloud-java 的全部 Issue

相似的 Issue

更多 Java Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。