使用服务账号调用Google Cloud Function遇权限问题求助
我需要通过服务账号调用Google Cloud Function,服务账号my-project-crendential已配置Cloud Functions-Invoker角色。编写了以下Java代码调用名为my-test-function的云函数:
ServiceAccountCredentials .fromStream(new FileInputStream(PATH != null ? PATH : "my-project-crendential.json")) this.settings = CloudFunctionsServiceSettings.newBuilder() .setCredentialsProvider(FixedCredentialsProvider.create(this.credentials)) .build(); try (CloudFunctionsServiceClient client = CloudFunctionsServiceClient.create(this.settings)) { final var functionNameWithLocation = String.format( "projects/%s/locations/%s/functions/%s", "my-project-id", "europe-west4", "my-test-function" ); CallFunctionRequest request = CallFunctionRequest.newBuilder() .setName(functionNameWithLocation) .setData(body.encode()) .build(); CallFunctionResponse response = client.callFunction(request); log.atDebug().log("Function response received {} ...", response.getResult()); } catch (Exception e) { log.atError().withThrowable(e).log("Create repository failed ..."); }
该云函数的访问URL为https://europe-west4-my-project-id.cloudfunctions.net/my-test-function,但始终收到异常:
io.grpc.StatusRuntimeException: PERMISSION_DENIED: Permission denied on 'locations/europe-west4' (or it may not exist)
我确认区域存在且权限配置无误,使用的依赖为:
<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-functions</artifactId> <version>2.35.0</version> </dependency>
尝试1:获取OAuth Token调用API
根据反馈原API仅用于测试,服务账号无对应权限,于是尝试获取OAuth Token:
String serviceAccountKeyFile = path;//"/path/to/service-account-key.json"; String projectId = "my-project-id"; String location = "europe-west4"; String functionName = "my-test-function"; GoogleCredentials credentials = GoogleCredentials .fromStream(new FileInputStream(serviceAccountKeyFile)) .createScoped("https://www.googleapis.com/auth/cloud-platform"); String accessToken = credentials.refreshAccessToken().getTokenValue();
随后调用Get API:
String getApiUrl = String.format("https://cloudfunctions.googleapis.com/v2/projects/%s/locations/%s/functions/%s", projectId, location, functionName); webClient.getAbs(getApiUrl) .bearerTokenAuthentication(identityToken) .putHeader("Authorization", "Bearer " + accessToken) .send(getResponse -> { if (getResponse.succeeded()) { // getResponse does not contains HTTPStrigger.URL attribute }});
响应中不包含HTTPStrigger.URL属性,且通过响应中的URL调用云函数时仍收到相同的“区域不存在”异常。
尝试2:参考官方示例使用ID Token调用
参考官方示例编写代码:
final var serviceAccountKeyFile = "/my/abs/path/to/my-project-secret-file.json"; GoogleCredentials credentials = GoogleCredentials .fromStream(new FileInputStream(serviceAccountKeyFile)) .createScoped("https://www.googleapis.com/auth/cloud-platform"); final var audience = "https://europe-west4-my-project.cloudfunctions.net/my-function"; if (!(credentials instanceof IdTokenProvider)) { throw new IllegalArgumentException("Credentials are not an instance of IdTokenProvider."); } IdTokenCredentials tokenCredential = IdTokenCredentials.newBuilder() .setIdTokenProvider((IdTokenProvider) credentials) .setTargetAudience(audience) .build(); GenericUrl genericUrl = new GenericUrl("https://europe-west4-my-project.cloudfunctions.net/my-function"); HttpCredentialsAdapter adapter = new HttpCredentialsAdapter(tokenCredential); HttpTransport transport = new NetHttpTransport(); HttpRequest request = transport.createRequestFactory(adapter).buildGetRequest(genericUrl); final var result = request.execute(); System.out.println(result.parseAsString());
服务账号已拥有调用者角色,但仍收到异常:Your client does not have permission to get URL /my-function from this server.
解决方案
1. 明确正确的调用方式
CloudFunctionsServiceClient.callFunction是Cloud Functions v1 API的管理/测试接口,仅用于在Google Cloud内部触发函数,不适用于调用公开的HTTP触发云函数。HTTP触发的云函数需要直接调用其HTTPS端点,并携带ID Token(而非普通OAuth Access Token)进行认证。
2. 修正ID Token调用的核心错误
你的尝试2中存在两个关键错误:
- Audience不匹配:
audience必须完全与云函数的HTTPS URL一致,之前代码中使用的https://europe-west4-my-project.cloudfunctions.net/my-function与实际URLhttps://europe-west4-my-project-id.cloudfunctions.net/my-test-function存在项目ID和函数名的拼写错误。 - 请求方法不匹配:多数云函数默认接受POST请求,若你的函数是POST触发,需将
buildGetRequest改为buildPostRequest并传入请求体。
修正后的完整代码示例:
import com.google.auth.oauth2.IdTokenCredentials; import com.google.auth.oauth2.IdTokenProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpCredentialsAdapter; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.StringContent; import java.io.FileInputStream; public class CloudFunctionInvoker { public static void main(String[] args) throws Exception { // 服务账号密钥文件路径 final String serviceAccountKeyPath = "/my/abs/path/to/my-project-secret-file.json"; // 云函数的完整HTTPS URL,确保完全一致 final String functionEndpoint = "https://europe-west4-my-project-id.cloudfunctions.net/my-test-function"; // 加载服务账号凭证 GoogleCredentials credentials = GoogleCredentials .fromStream(new FileInputStream(serviceAccountKeyPath)) .createScoped("https://www.googleapis.com/auth/cloud-platform"); // 验证凭证支持ID Token生成 if (!(credentials instanceof IdTokenProvider)) { throw new IllegalArgumentException("当前凭证不支持生成ID Token"); } // 构建ID Token凭证,目标受众为云函数URL IdTokenCredentials idTokenCredential = IdTokenCredentials.newBuilder() .setIdTokenProvider((IdTokenProvider) credentials) .setTargetAudience(functionEndpoint) .build(); // 创建HTTP传输和请求 HttpTransport httpTransport = new NetHttpTransport(); // 若函数为GET触发,替换为buildGetRequest HttpRequest request = httpTransport.createRequestFactory(new HttpCredentialsAdapter(idTokenCredential)) .buildPostRequest( new GenericUrl(functionEndpoint), new StringContent("application/json", "{\"param\": \"test-data\"}") // 根据函数需求传入请求体 ); // 执行请求并输出结果 var response = request.execute(); System.out.println("函数响应:" + response.parseAsString()); } }
3. 验证服务账号权限
确保服务账号确实被授予roles/cloudfunctions.invoker角色,可通过gcloud命令重新绑定权限:
gcloud functions add-iam-policy-binding my-test-function \ --region=europe-west4 \ --member=serviceAccount:my-project-crendential@my-project-id.iam.gserviceaccount.com \ --role=roles/cloudfunctions.invoker
4. 确保依赖兼容性
添加明确的Google Auth依赖,避免版本冲突:
<dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> <version>1.20.0</version> </dependency> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-functions</artifactId> <version>2.35.0</version> </dependency>
内容的提问来源于stack exchange,提问作者Qeychon

