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

