简体   繁体   English

从 Java 调用 Google Cloud Run

[英]Invoke a Google Cloud Run from java

I want to invoke a Cloud Run from an external application.我想从外部应用程序调用 Cloud Run。 The external application is written in Kotlin (java) and run on the JDK 11 JVM.外部应用程序是用 Kotlin (java) 编写的,并在 JDK 11 JVM 上运行。 It authenticates using a service account它使用服务帐户进行身份验证

ServiceAccountCredentials.fromStream(serviceAccount).createScoped("https://www.googleapis.com/auth/cloud-platform")

where the service account is is a valid JSON file.服务帐户所在的位置是有效的 JSON 文件。 I have tested the permissions to invoke a cloud run for the service account inside the google cloud console.我已经测试了在谷歌云控制台内为服务帐户调用云运行的权限。

I have not found an official API so I used google's GoogleNetHttpTransport to run an HTTP post request.我还没有找到官方 API,所以我使用谷歌的 GoogleNetHttpTransport 来运行 HTTP 发布请求。 To invoke the cloud run I have tried to use the HttpCredentialsAdapter为了调用云运行,我尝试使用HttpCredentialsAdapter

transport.createRequestFactory(HttpCredentialsAdapter(googleCredentials))
   .buildPostRequest(GenericUrl(url), JsonHttpContent(JacksonFactory(), data))

I have tried to use the access token directy我曾尝试直接使用访问令牌

val headers = HttpHeaders()
googleCredentials.refreshIfExpired()
headers.authorization = "Bearer " + googleCredentials.accessToken.tokenValue
postRequest.headers = headers

And I have tried to use a jet service account and use the jwt request metadata我尝试使用 jet 服务帐户并使用jwt 请求元数据

val headers = HttpHeaders()
jwtCredentials = ServiceAccountJwtAccessCredentials.fromStream(serviceAccount)
jwtCredentials.getRequestMetadata(URI(url)).forEach {
    headers.set(it.key, it.value)
}
postRequest.headers = headers

In all these cases it returns this error在所有这些情况下,它返回此错误

[20:35:00 WARN]: Exception in thread "TestThread" com.google.api.client.http.HttpResponseException: 401 Unauthorized
[20:35:00 WARN]:    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1113)
[20:35:00 WARN]:    at ***.CloudRun$invoke$3.invokeSuspend(CloudRun.kt:34)
[20:35:00 WARN]:    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[20:35:00 WARN]:    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[20:35:00 WARN]:    at java.base/java.lang.Thread.run(Thread.java:834)

Thanks for your support in advance提前感谢您的支持

I spent time on the OAuth2 Java library and the problem is the following.我花了一些时间在 OAuth2 Java 库上,问题如下。 The class ServiceAccountCredentials add an AccessToken as authorization, and the class ServiceAccountJwtAccessCredentials add a self-signed identity token . ServiceAccountCredentials类添加AccessToken作为授权, ServiceAccountJwtAccessCredentials类添加自签名identity token

I will go deeper into it, but for now you can set the header manually我会更深入地研究它,但现在你可以手动设置标题

        String myUri = "https://.....";

        Credentials credentials =  ServiceAccountCredentials
                .fromStream(resourceLoader.getResource("classpath:./key.json")
                        .getInputStream()).createScoped("https://www.googleapis.com/auth/cloud-platform");

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

        HttpRequestFactory factory = new NetHttpTransport().createRequestFactory();
        HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization","Bearer " + token);
        request.setHeaders(headers);
        HttpResponse httpResponse = request.execute();
        System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));

The pieces are here, but not work together.碎片在这里,但不能一起工作。

EDIT编辑

After some exchange on GitHub, a Google provide me the solution.在 GitHub 上进行了一些交流后,Google 为我提供了解决方案。 Now the header is automatically provisioned with a signed id Token现在标头会自动提供一个签名的 id 令牌

        Credentials credentials =  ServiceAccountCredentials
                .fromStream(resourceLoader.getResource("classpath:./key.json")
                        .getInputStream()).createScoped("https://www.googleapis.com/auth/cloud-platform");


        IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder()
                .setIdTokenProvider((ServiceAccountCredentials)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)));

I don't know Java SDKs well enough, but I think I spotted the error:我不太了解 Java SDK,但我想我发现了错误:

headers.authorization = "Bearer " + googleCredentials.accessToken.tokenValue
                                                      ^^^^^^^^^^^

You should not be sending a GCP "access token" to a Cloud Run application (eg https://*.run.app ).您不应向 Cloud Run 应用程序(例如https://*.run.app )发送 GCP“访问令牌”。 That token is for calling Google's APIs;该令牌用于调用 Google 的 API; and Cloud Run apps are not that.而 Cloud Run 应用程序并非如此。

Instead, you should be sending an "identity token", which basically shows that you are in possession of the service account, without giving out any permission to the called service (to use that token to call GCP APIs).相反,您应该发送一个“身份令牌”,它基本上表明您拥有服务帐户,而不向被调用的服务授予任何权限(使用该令牌调用 GCP API)。

You cannot sign an "identity token" yourself locally, you need to call an endpoint.您不能在本地自己签署“身份令牌”,您需要调用端点。

This is explained in this page: https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-jwt此页面对此进行了解释: https : //cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-jwt

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM