【问题标题】:How to generate a JWT for a Cloud Function HTTP trigger?如何为 Cloud Function HTTP 触发器生成 JWT?
【发布时间】:2021-07-20 19:34:43
【问题描述】:

我要做的是生成一个 JWT,用于在我部署的 GCP 云函数上调用 HTTP 触发器。 我已经使用“allUsers”部署了我的函数并验证了它是否有效,但我希望它更安全,因此我需要将 JWT 附加到我的 HTTP 请求中。 我正在关注this code,以下 sn-ps 主要来自于此。 我想我已经很接近了,但还不是很接近。在下面的所有示例等中,我已将项目名称更改为 PROJECT_NAME。

首先,我创建了一个名为 testharness-sa 的服务帐户并下载了它的密钥文件。当我运行测试时,我有一个 env var GOOGLE_APPLICATION_CREDENTIALS 指向该文件。然后我运行以下命令:

gcloud functions add-iam-policy-binding gopubsub \
  --member='serviceAccount:testharness-sa@PROJECT_NAME.iam.gserviceaccount.com' \
  --role='roles/cloudfunctions.invoker'

这给了我一个确认,列出了我的云函数上的所有当前绑定,包括 testharness-sa。

我的代码的核心是这样的:

    private String getSignedJwt() {
        GoogleCredentials credentials = GoogleCredentials
                .getApplicationDefault()
                .createScoped(Collections.singleton(IAM_SCOPE));
        long now = System.currentTimeMillis();
        RSAPrivateKey privateKey = (RSAPrivateKey) credentials.getPrivateKey();
        Algorithm algorithm = Algorithm.RSA256(null, privateKey);
        return JWT.create()
                .withKeyId(credentials.getPrivateKeyId())
                .withIssuer(credentials.getClientEmail())
                .withSubject(credentials.getClientEmail())
                .withAudience(OAUTH_TOKEN_AUDIENCE)
                .withIssuedAt(new Date(now))
                .withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
                .withClaim("target_audience", clientId)
                .sign(algorithm);
    }

这给了我一个签名的 JWT。据我了解,这用于调用 GCP 以获取可用于调用云函数的最终 JWT。

生成签名的 JWT 后,我会像这样使用它:

        String jwt = getSignedJwt();
        final GenericData tokenRequest = new GenericData()
                .set("grant_type", JWT_BEARER_TOKEN_GRANT_TYPE)
                .set("assertion", jwt);
        final UrlEncodedContent content = new UrlEncodedContent(tokenRequest);

        final HttpRequestFactory requestFactory = httpTransport.createRequestFactory();

        final HttpRequest request = requestFactory
                .buildPostRequest(new GenericUrl(OAUTH_TOKEN_URI), content)
                .setParser(new JsonObjectParser(JacksonFactory.getDefaultInstance()));

        HttpResponse response = request.execute();
...

所以它获取了签名的 JWT 并提出了获取最终 JWT 的请求,然后......失败了。错误是“无效的 JWT:观众检查失败” 看起来我的参数不好,所以让我们看看我的参数(它们实际上是常量,而不是参数):

    private static final String IAM_SCOPE = "https://www.googleapis.com/auth/iam";
    //"https://www.googleapis.com/auth/cloud-platform";
    private static final String OAUTH_TOKEN_URI = "https://oauth2.googleapis.com/token";
    //"https://www.googleapis.com/oauth2/v4/token";
    private static final String OAUTH_TOKEN_AUDIENCE = "https://us-central1-PROJECT_NAME.cloudfunctions.net/gopubsub";
    //"https://www.googleapis.com/token";
    private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
    private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;

我已经添加了其他变体作为针对常量的 cmets。

根据错误消息,我使用的受众价值似乎是错误的。 This page 建议受众应该是服务帐户电子邮件,我想我在其他地方看到它应该是云功能的 URL,但这些都不适合我。

我知道这可以工作,因为我可以发出这个命令:

gcloud auth print-identity-token

这给了我最终的 JWT(只要我有 GOOGLE_APPLICATION_CREDENTIALS 指向我的 json 文件)。 我可以将该 JWT 粘贴到 curl 命令中并成功调用 HTTP 触发器,如果​​我将 JWT 留在外面,它会失败,所以我知道 JWT 正在被检查。 但到目前为止,我不知道如何从我的 Java 代码中执行相当于 gcloud auth print-identity-token 的操作。 有人知道吗?

【问题讨论】:

  • 您是否尝试过在没有 IAM_SCOPE 的情况下生成 jwt,如来自 link 的示例所示?
  • 我现在有 :) 是的,它似乎没有任何区别,但是我发现问题与范围无关。请参阅下面的答案。

标签: java jwt google-cloud-functions


【解决方案1】:

尽管出现错误消息,但我发现答案与范围无关。它实际上与我传递的 clientId 值有关(在我的问题中并没有真正提到,尽管它在代码中)。我使用的是我在服务帐户 json 文件中找到的一个值,一个非常长的数字字符串。这就是问题所在。它必须是我的 HTTP 触发器的 URL。所以这些是我最终得到的常量:

    private static final String IAM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
    private static final String OAUTH_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token";
    private static final String OAUTH_TOKEN_AUDIENCE = "https://www.googleapis.com/oauth2/v4/token";
    private static final String JWT_BEARER_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
    private static final long EXPIRATION_TIME_IN_MILLIS = 3600 * 1000L;

事实上,如果我删除所有对IAM_SCOPE 的引用,它仍然有效,所以我得出结论,这并不重要。将其组合在一起的代码现在如下所示:

        return JWT.create()
                .withKeyId(credentials.getPrivateKeyId())
                .withIssuer(credentials.getClientEmail())
                .withSubject(credentials.getClientEmail())
                .withAudience(OAUTH_TOKEN_AUDIENCE)
                .withIssuedAt(new Date(now))
                .withExpiresAt(new Date(now + EXPIRATION_TIME_IN_MILLIS))
                .withClaim("target_audience", targetURL)
                .sign(algorithm);

具体的更改是 withClaim() 调用,它现在包含我要调用的 URL。有了这个,代码返回了我希望的 JWT,事实上,当我调用安全的 HTTP 触发器 URL 时,它确实有效。希望对其他人有所帮助。

【讨论】:

  • 我不太明白你的代码是如何编译的。 GoogleCredentials 缺少您正在使用的几种方法。我怀疑您在问题中从GoogleCredential(现已弃用)到GoogleCredentials 进行了查找/替换。
猜你喜欢
  • 2020-08-31
  • 2020-07-26
  • 1970-01-01
  • 2018-05-16
  • 1970-01-01
  • 2018-09-20
  • 2020-10-26
  • 1970-01-01
  • 2022-06-15
相关资源
最近更新 更多