[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 摘要。