带有Java服务帐户的Google云端存储 – 403 Caller没有对存储桶的storage.objects.list访问权限

我们希望从我们的应用服务器中的Google Storage下载文件。 重要的是对单个存储桶具有只读受限访问权限,而不是其他任何内容。

起初我使用了一个常规用户帐户(不是服务帐户),它有权访问我们的Google Cloud项目中的所有存储桶,一切正常 – 我的Java代码打开存储桶并下载文件没有问题。

Storage storage = StorageOptions.getDefaultInstance().getService(); Bucket b = storage.get( "mybucketname" ); 

然后我想切换到使用专门创建的服务帐户,该帐户只能访问一个存储桶。 所以我创建了一个服务帐户,授予读取单个存储桶的权限,并下载了其密钥文件。 Google Cloud Console中的权限命名为:

存储对象查看器(3个成员)读取对GCS对象的访问权限。

gsutil命令行实用程序可以正常使用此帐户 – 从命令行可以访问此存储桶但不能访问其他存储桶。

使用以下命令从命令行初始化:

 gcloud --project myprojectname auth activate-service-account files-viewer2@myprojectname.iam.gserviceaccount.com --key-file=/.../keyfilename.json 

我甚至尝试了两个可以访问不同存储桶的不同服务帐户,从命令行我可以在它们之间切换,gsutil只能访问相关的存储桶,而对于任何其他存储器,它会返回错误:

“AccessDeniedException:403 Caller没有对bucket xxxxxxxxxx的storage.objects.list访问权限。”

所以,从命令行一切正常。 但在Java中,身份validation存在一些问题。

我以前与常规用户帐户一起使用的默认身份validation停止工作 – 它报告错误:

com.google.cloud.storage.StorageException:匿名用户没有对bucket xxxxxxxxxx的storage.buckets.get访问权限。

然后我尝试了以下代码(这是最简单的变体,因为它依赖于关键的json文件,但我已经尝试了在各种论坛中找到的许多其他变体,但没有成功):

 FileInputStream fis = new FileInputStream( "/path/to/the/key-file.json" ); ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream( fis ); Storage storage = StorageOptions.newBuilder().setCredentials( credentials ) .setProjectId( "myprojectid" ).build().getService(); Bucket b = storage.get( "mybucketname" ); 

而我收到的只是这个错误:

com.google.cloud.storage.StorageException:调用者没有对bucket mybucketname的storage.buckets.get访问权限。 原因:com.google.api.client.googleapis.json.GoogleJsonResponseException:403 Forbidden

无论我尝试访问哪些存储桶(甚至不存在),都会返回相同的错误。 令我困惑的是,使用相同的JSON密钥文件初始化的同一服务帐户在命令行中运行良好。 所以我认为Java代码中缺少某些东西来确保正确的身份validation。

TL; DR – 如果您正在使用Application Default Credentials (当您执行StorageOptions.getDefaultInstance().getService();时就是BTW,并且如果您需要使用服务帐户中的凭据,则可以不使用改变你的代码 。 您需要做的就是将GOOGLE_APPLICATION_CREDENTIALS环境变量设置为服务帐户json文件的完整路径,并且您已完成设置。

使用Application Default Credentials的解决方案的更长版本

  • 按原样使用原始代码

     Storage storage = StorageOptions.getDefaultInstance().getService(); Bucket b = storage.get( "mybucketname" ); 
  • 将环境变量GOOGLE_APPLICATION_CREDENTIALS设置为包含服务帐户凭据的json文件的完整路径。

     export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account_credentials.json 
  • 再次运行您的Java应用程序以validation它是否按预期工作。

使用硬编码服务帐户凭据的备用解决方案

您发布的用于初始化ServiceAccountCredentials的代码示例对我来说很快就会对我有效。 我尝试了以下代码片段,它按预期正在为我工​​作。

 String SERVICE_ACCOUNT_JSON_PATH = "/path/to/service_account_credentials.json"; Storage storage = StorageOptions.newBuilder() .setCredentials( ServiceAccountCredentials.fromStream( new FileInputStream(SERVICE_ACCOUNT_JSON_PATH))) .build() .getService(); Bucket b = storage.get("mybucketname"); 

指定服务帐户凭据时, 将从json文件中的信息中自动获取项目ID 。 所以你不必再次指定它。 我不完全确定这是否与您正在观察的问题有关。

应用程序默认凭据

以下是有关Application Default Credentials 的完整文档 ,说明根据您的环境选择哪些凭据。

应用程序默认凭据的工作原理

您可以通过单个客户端库调用来获取应用程序默认凭据。 返回的凭据由运行代码的环境确定。条件按以下顺序检查:

  1. 检查环境变量GOOGLE_APPLICATION_CREDENTIALS 。 如果指定了此变量,则应指向定义凭据的文件。 获取凭据的最简单方法是在Google API控制台中创建服务帐户密钥:

    一个。 转到API控制台凭据页面 。

    湾 从项目下拉列表中,选择您的项目。

    C。 在“凭据”页面上,选择“创建凭据”下拉列表,然后选择“服务帐户密钥”。

    d。从“服务帐户”下拉列表中,选择现有服务帐户或创建新帐户。

    即 对于Key type,选择JSON键选项,然后选择Create。 该文件会自动下载到您的计算机。

    F。 将刚刚下载的* .json文件放在您选择的目录中。 此目录必须是私有的(您不能让任何人访问此目录),但可以访问您的Web服务器代码。

    G。 将环境变量GOOGLE_APPLICATION_CREDENTIALS设置为下载的JSON文件的路径。

  2. 如果您已在计算机上安装了Google Cloud SDK并运行了命令gcloud auth application-default login ,则您的身份可用作代理来测试从该计算机调用API的代码。

  3. 如果您在Google App Engine生产中运行,则将使用与该应用程序关联的内置服务帐户。

  4. 如果您在Google Compute Engine生产中运行,则将使用与虚拟机实例关联的内置服务帐户。

  5. 如果这些条件都不成立,则会发生错误。

IAM角色

我建议查看IAM permissions和可用于云存储的IAM roles 。 这些提供项目和桶级别的控制。 此外,您可以使用ACL来控制存储桶中对象级别的权限 。

  • 如果您的用例涉及仅调用storage.get(bucketName) 。 此操作仅需要storage.buckets.get权限,并且此权限的最佳IAM角色是roles/storage.legacyObjectReader

  • 如果您还要授予服务帐户权限get( storage.objects.get )和list( storage.objects.list )单个对象,则还要将角色roles/storage.objectViewer添加到服务帐户。

感谢@Taxdude的长解释,我明白我的Java代码应该没问题,并开始研究问题的其他可能原因。

我尝试过的其他一件事是设置为服务帐户的权限,我找到了解决方案 – 实际上是意外的。

创建服务帐户时,不得向其授予从Google存储中读取的权限,因为它将具有对所有存储桶的读取权限,并且无法更改(不确定原因),因为系统会将这些权限标记为“遗传”。

因此,我创建了一个没有权限的“空白”服务帐户,并通过打开Goog​​le云端Web控制台,打开存储浏览器,选择我的存储桶以及打开带权限的信息面板,从存储桶配置中配置了权限。 在那里,我添加了具有“存储对象查看器”权限的服务帐户 – 唯一看起来合适的服务帐户…几乎 – 还有名为“存储旧版对象读取器”和“存储旧版存储桶读取器”的权限,但由于单词“Legacy”我认为不应该使用它们 – 它们看起来像是为了向后兼容而保留的东西。

在试验并添加这些“遗留”权限之后,我突然尝试的相同代码始终正常运行。

我仍然不完全确定我应该为服务帐户分配的最小权限集是什么,但至少现在它适用于存储桶上的所有3个“读取”权限 – 2“遗留”和1“正常”。