【问题标题】:Google Cloud Platform - cloud functions API - 401 UnauthorizedGoogle Cloud Platform - 云功能 API - 401 未经授权
【发布时间】:2023-03-28 13:18:01
【问题描述】:

我正在努力使用 Java 通过 REST API 调用 GCP 云功能。

我执行的步骤是:

  • 创建角色为“Cloud Functions Invoker”的服务帐号
  • 为新创建的服务帐号下载 JSON 密钥文件
  • 在我的代码中,使用以下方法获取访问令牌:
private String getAuthToken() {
  File credentialsPath = new File(PATH_TO_JSON_KEY_FILE);

  GoogleCredentials credentials;
  try (FileInputStream serviceAccountStream = new FileInputStream(credentialsPath)) {
    credentials = ServiceAccountCredentials.fromStream(serviceAccountStream);
    return credentials
           .createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"))
           .refreshAccessToken()
           .getTokenValue();
  } catch (IOException e) {
    throw new RuntimeException("Action could not be performed");
  }
}
  • 使用创建的令牌执行 REST 调用:
public <Payload, Response> ResponseEntity<Response> callCloudFunction(
    String endpoint,
    Payload payload,
    Class<Response> klazz
) {
  RestTemplate restTemplate = new RestTemplate();
  HttpHeaders headers = new HttpHeaders();
  headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
  headers.setContentType(MediaType.APPLICATION_JSON);
  String url = gCloudUrl + endpoint;
  String token = getAuthToken();
  String payloadString = null;

  if (payload != null) {
    try {
      ObjectMapper objectMapper = new ObjectMapper();
      payloadString = objectMapper.writeValueAsString(payload);
    } catch (JsonProcessingException e) {
      System.out.println(e.getMessage());
      throw new RuntimeException("Could not perform action");
    }
  }

  headers.add("Authorization", String.format("Bearer %s", token));
  HttpEntity<String> entity = new HttpEntity<>(payloadString, headers);

  return restTemplate.exchange(url, HttpMethod.POST, entity, klazz);
}

实现看起来不错,但作为响应,我得到 401 Unauthorized。

很遗憾,GCP 文档并没有真正的帮助。我想我已经搜索了所有可能的地方。

【问题讨论】:

标签: java rest google-cloud-platform google-cloud-functions


【解决方案1】:

首先同意,不清楚……

然后,您必须知道(现在还不清楚)您需要访问令牌来调用 Google Cloud API,但需要身份令牌来调用 IAP(例如在 App Engine 上)或私有 Cloud Function 和 Cloud Run。而且这个身份令牌需要谷歌签名。

并且,如代码中所述,您需要在计算机上拥有一个服务帐户,但我建议您在 GCP 上避免这种情况,如果您使用默认身份验证,则不需要(请参阅我的代码,在您的计算机上设置GOOGLE_APPLICATION_CREDENTIALS env var 指向服务帐户密钥文件)。最好的方法是不在您的计算机上使用服务帐户密钥文件,但目前还不可能(这是 IMO 的安全问题,我正在与 Google 讨论这个问题......)

无论如何,这里有一个代码 sn-p 可以在 Java 中运行(文档中没有...)

  String myUri = "https://path/to/url";
  // You can use here your service account key file. But, on GCP you don't require a service account key file.
  // However, on your computer, you require one because you need and identity token and you can generate it with your user account (long story... I'm still in discussion with Google about this point...)
  Credentials credentials = GoogleCredentials.getApplicationDefault().createScoped("https://www.googleapis.com/auth/cloud-platform");
  IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder()
    .setIdTokenProvider((IdTokenProvider) credentials)
    .setTargetAudience(myUri).build();

  HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(idTokenCredentials));

  HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
  HttpResponse httpResponse = request.execute();
  System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));

注意如果你想继续使用 RestTemplate 对象并手动设置你的令牌,你可以像这样生成它

  String token = ((IdTokenProvider) credentials).idTokenWithAudience(myUri, Collections.EMPTY_LIST).getTokenValue();
  System.out.println(token);

【讨论】:

  • 我不敢相信,但它就像一个魅力:D 我的 IDE 只警告IdTokenProvider 被标记为不稳定(由于@Beta 注释),但老实说,我不知道不在乎 ;) 我在这个话题上做了几十次尝试。你的帮助是无价的:)
  • @guillame,感谢您成为英雄并揭开这种 go​​ogle auth 疯狂的神秘面纱。但是,在各种 StackOverflow 帖子中,我没有看到关于在开发/测试/GCP 环境之间切换时如何使其具有互操作性和无缝性的好建议。有什么建议吗?
  • 在 Java 或其他语言中,关键是依赖于您获得的 ADC(应用程序默认凭据)GoogleCredentials.getApplicationDefault()。因此,您的应用程序依赖于环境配置。在开发环境中,您处于开发项目中。您使用专用于项目的服务帐户进行部署,应用程序会自动使用它,因为它属于环境上下文。其他环境也一样。如果您有特定的用例或阻止程序,请随时分享(在新问题中并在此处粘贴链接!)
  • 问题是gcloud auth application-default login 启动了一个浏览器,迫使您选择一个Google 用户帐户。以这种方式设置时,GoogleCredentials.getApplicationDefault() 方法返回 com.google.auth.oauth2.UserCredentials 的实例,而不是所需的 IdTokenProvider。因此,似乎需要在本地设置环境变量或服务帐户 JSON 文件的路径。我可以通过手动将服务帐户文件复制到名为application_default_credentials.json 的文件来“欺骗”它,但这似乎有点新奇。 :)
  • ...我知道...我要解决这个问题:github.com/googleapis/google-auth-library-java/pull/469 您可以投票或评论此合并请求以加快其验证!我不知道为什么默认情况下它不在 auth-library 中。尽管 gcloud SDK 可以(gcloud auth print-identity-token),任何语言都可以使用用户凭据生成有效的 ID-TOKEN
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-02
  • 2012-04-04
  • 1970-01-01
  • 2014-10-22
  • 1970-01-01
  • 2019-03-07
  • 2017-09-27
相关资源
最近更新 更多