问题描述
我正在部署一个 Java API,它通过域窗口委派进行用户模拟以访问用户的日历。为此,我创建了一个服务帐户,完成了委托,并为其授予了正确的权限和对用户日历的访问权限。
在本地开发时,我一直使用 JSON 格式的服务帐户的下载密钥,并使用 GOOGLE_APPLICATION_CREDENTIALS 环境变量指向它。在我的代码中,我使用 Java 客户端库创建了这样的凭据:
@Bean
@Qualifier("userCredentials")
public GoogleCredentials impersonateCalendarOwner() throws IOException {
final List<String> scopes = Collections.singletonList(CalendarScopes.CALENDAR);
return ((ServiceAccountCredentials) GoogleCredentials.getApplicationDefault())
.toBuilder()
.setServiceAccountUser(GOOGLE_CALENDARS_OWNER)
.build()
.createScoped(scopes);
}
这在本地工作正常,但在 Cloud Run 中运行时我得到:
nested exception is java.lang.classCastException:
class com.google.auth.oauth2.ComputeEngineCredentials cannot be cast to
class com.google.auth.oauth2.ServiceAccountCredentials
经过几个小时的调试,我想我终于明白了 getApplicationDefault
是如何工作的。我认为当地发生的事情是这样的:
- getApplicationDefault 首先查看环境变量 GOOGLE_APPLICATION_CREDENTIALS,因为它已设置,所以它会从该文件中读取凭据
- 因为该文件是服务帐号密钥文件,所以它创建了一个 ServiceAccountCredentials 实例,因此“转换”成功。
在 Cloud Run 中,它是这样发生的:
- getApplicationDefault 首先查看环境变量 GOOGLE_APPLICATION_CREDENTIALS,但在 Cloud Run 上未设置
- 因此,它通过从元数据服务器获取凭据并将它们作为 ComputeEngineCredentials 的实例返回(因为元数据服务器不分发服务帐户的密钥),从而回退到修订版正在运行的服务帐户标识.
- 然后它尝试将 ComputeEngineCredentials 转换为 ServiceAccountCredentials,这显然不起作用。
所以现在我的问题仍然存在:
- 如何将某些 ComputeEngineCredentials 转换为 ServiceAccountCredentials?
- 是否有其他方法可以将凭据作为 ServiceAccountCredentials 的实例获取?
- 是否有其他不需要 ServiceAccountCredentials 实例的用户模拟方法?
我目前的想法是使用我获得的 ComputeEngineCredentials,在服务启动时从 Secret Manager 中获取服务帐户 JSON 密钥文件,然后将该文件传递给 ServiceAccountCredentials.fromStream 但感觉就像我会的 ComputeEngineCredentials 以来的额外步骤正在使用的已经是我将为其获取密钥的同一服务帐户的凭据。
解决方法
实例元数据(元数据服务器)提供的凭据不包括签署请求所需的服务帐户私钥。
因此,您尝试使用的服务帐户模拟代码将不起作用。您将需要使用包含私钥的实际服务帐户 JSON。
我的建议是使用角色设置默认服务帐户以访问 Secret Manager。将服务帐户 JSON 密钥文件作为数据存储在 Secret Manager 中。在您的程序启动时获取服务帐户 JSON 内容并继续您的模拟代码。
,您需要告诉 Cloud Run 服务run as your service account:
gcloud run deploy --image IMAGE_URL --service-account SERVICE_ACCOUNT