我正在部署一个 Java API,它通过 Domain-widge 委派进行用户模拟以访问用户的日历。为此,我创建了一个服务帐户,完成了委派并为其授予了正确的权限和对用户日历的访问权限。
在本地开发时,我一直在使用 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 是已经是我要为其获取密钥的同一服务帐户的凭据。