You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Google Cloud Java客户端获取Cloud Storage访问令牌供Vaex使用

问题:使用Google Cloud Java客户端获取Cloud Storage访问令牌的scope问题

我正在尝试实现vaex官方API提供的如下功能:

df = vaex.open('gs://vaex-data/airlines/us_airline_data_1988_2019.hdf5?token=MAGIC_GOOGLE_TOKEN')

我使用Java/Scala技术栈,持有如下格式的服务账号密钥(为简洁省略了其余字段):

{
  "type": "service_account",
  "project_id": "project_id",
  "private_key_id": "XXX",
  "private_key": "YYY"
}

我的目标是通过Google Cloud Java客户端获取上述代码中需要的token。

现有实现及验证

目前已经完成如下代码:

def getOrCreateInstance(bigQueryCredentialsJson: String,
                          projectId: String): BigQueryClientService = {
    val serviceAccountCredentials: ServiceAccountCredentials = ServiceAccountCredentials.fromStream(new ByteArrayInputStream(bigQueryCredentialsJson.getBytes(StandardCharsets.UTF_8)))
    val bigQuery = BigQueryOptions.newBuilder()
      .setCredentials(serviceAccountCredentials)
      .setProjectId(projectId)
      .build()
      .getService

    new BigQueryClientService(bigQuery, projectId, serviceAccountCredentials)
  }

这段代码运行正常,已经验证了两点:

  • 生成的BigQuery实例可以成功连接BigQuery服务
  • BigQuery查询结果可以正常导出到Cloud Storage,说明当前服务账号对目标Cloud Bucket拥有读写权限

遇到的问题

我需要将存储桶路径加上accessToken提供给另一个内部系统,因此尝试用如下方法获取AccessToken:

def getAccessToken(): AccessToken = {
    // serviceAccountCredentials在上述步骤中已实例化
    serviceAccountCredentials.refreshIfExpired() // 此处抛出异常
    serviceAccountCredentials.getAccessToken
  }

调用refreshIfExpired时抛出如下异常:

Error getting access token for service account: 400 Bad Request
POST https://oauth2.googleapis.com/token
{"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}, iss:bla_bla@datadiscovery-spark-dev.iam.gserviceaccount.com
java.io.IOException: Error getting access token for service account: 400 Bad Request
POST https://oauth2.googleapis.com/token
{"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}, iss: bla_bla@datadiscovery-spark-dev.iam.gserviceaccount.com
    at com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:605)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:243)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:240)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:30)
    at com.google.auth.oauth2.OAuth2Credentials$AsyncRefreshResult.executeIfNew(OAuth2Credentials.java:567)
    at com.google.auth.oauth2.OAuth2Credentials.asyncFetch(OAuth2Credentials.java:206)
    at com.google.auth.oauth2.OAuth2Credentials.refreshIfExpired(OAuth2Credentials.java:177)

我修改代码添加了scope之后,就可以正常生成AccessToken了:

def getAccessToken(): AccessToken = {
    val scoped = serviceAccountCredentials.createScoped("https://www.googleapis.com/auth/devstorage.read_write")
    scoped.refreshIfExpired()
    scoped.getAccessToken
  }

疑问

为什么服务账号已经可以将BigQuery查询结果导出到Cloud Storage,却无法直接获取访问同一份数据的访问令牌,必须添加指定scope才能生成?

问题解答

这个差异本质上是Google Cloud的权限校验逻辑和OAuth2令牌生成规则的区别:

  1. 你用服务账号初始化BigQuery客户端的时候,BigQuery的SDK会自动给凭据加上BigQuery服务对应的默认scope,所以你调用BigQuery相关的接口都是正常的。而你做BigQuery导出到GCS的操作时,是BigQuery服务本身用服务账号的身份去操作GCS,这个过程不需要你手动生成令牌,Google Cloud内部的服务间调用会自动处理权限校验,只要服务账号本身有GCS的IAM权限就能走通。
  2. 你手动调用refreshIfExpired生成通用的访问令牌时,原始的ServiceAccountCredentials实例没有绑定任何scope,Google的OAuth2接口不知道你要拿这个令牌访问什么服务,自然就会返回invalid_scope错误。你手动加上GCS的读写scope之后,OAuth服务知道你要申请访问GCS的权限,只要服务账号本身有对应权限就能生成合法令牌。
    简单说就是:IAM权限是服务账号本身的属性,scope是你申请令牌时指定的这个令牌的权限范围,二者是独立校验的。令牌的scope不能超过服务账号本身的IAM权限,但你不指定scope的话根本无法生成可用的令牌。

内容的提问来源于stack exchange,提问作者Capacytron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 21:54:03