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

Aberta
#14,514 1 comentário 0 reações 0 responsáveis Ver no GitHub

Mantenedores costumam responder em até 1 dia

Ninguém assumiu esta issue ainda.

Avaliação

Dificuldade
3/5
Tempo estimado
1-2 dias
Facilidade para iniciantes
72/100
Tipo de issue
Bug
Clareza
Razoavelmente clara
Status de atividade
Ativa
Stack de tecnologia
java
Domínio
authentication

Direção de pesquisa

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.

Escrita pelo modelo de indexação a partir do texto da issue.

Descrição

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.
Linguagem predominante
Java
Estrelas
2.1k
Forks
1.2k
Merge médio
2d 14h
PRs com merge (30d)
192

Preparar o ambiente

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de googleapis/google-cloud-java

Todas as issues de googleapis/google-cloud-java

Issues semelhantes

Mais issues de Java

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.