我要做的是生成一个JWT,用于在我部署的GCP云函数上调用HTTP触发器。我已经用"allUsers"部署了我的功能,并验证了它的工作原理,但我希望它更安全,所以我需要在我的HTTP请求中附加一个JWT。我正在关注这段代码,下面的片段主要来自这段代码。我想我已经接近了,但还没有完全达到。在下面的所有示例等中,我已将项目名称更改为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;
我添加了其他变体作为对常量的注释。
根据错误信息,我使用的受众值似乎是错误的。这个页面建议受众应该是服务帐户电子邮件,我想我在其他地方看到过它应该是云功能的URL,但这两个都不适合我。
我知道这可以工作,因为我可以发出这个命令:
gcloud auth print-identity-token
这给了我最后的JWT(只要我有GOOGLE_APPLICATION_CREDENTIALS指向我的json文件(。我可以将JWT粘贴到curl命令中,并成功调用HTTP触发器,如果我不使用JWT,它就会失败,所以我知道JWT正在被检查。但到目前为止,我还不知道如何从我的Java代码中实现相当于gcloudauth-print身份令牌的功能。有人知道吗?
我发现答案与范围无关,尽管有错误消息。它实际上与我传递的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时,JWT确实有效。希望这能帮助其他人。