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

And*_*nin 5 java google-cloud-storage

我们希望从我们的应用服务器中的Google Storage下载文件.对单个存储桶进行只读限制访问非常重要.

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

Storage storage = StorageOptions.getDefaultInstance().getService();
Bucket b = storage.get( "mybucketname" );
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

gcloud --project myprojectname auth activate-service-account files-viewer2@myprojectname.iam.gserviceaccount.com --key-file=/.../keyfilename.json
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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" );
Run Code Online (Sandbox Code Playgroud)

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

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

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

Tux*_*ude 8

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

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

  • 按原样使用原始代码

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

    export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service_account_credentials.json
    
    Run Code Online (Sandbox Code Playgroud)
  • 再次运行您的Java应用程序以验证它是否按预期工作.

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

您发布的用于初始化的代码示例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");
Run Code Online (Sandbox Code Playgroud)

指定服务帐户凭据时,将从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 permissionsIAM roles存储云端存储.这些提供项目和桶级别的控制.此外,您可以使用ACL来控制存储桶中对象级别的权限.

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

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


And*_*nin 5

感谢@Taxdude的详尽解释,我了解我的Java代码应该没问题,并开始寻找导致该问题的其他可能原因。

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

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

因此,您必须:

  • 创建一个没有权限的“空白”服务帐户,并且
  • 从存储桶配置中配置权限

为此:

  • 打开Goog​​le Cloud Web控制台
  • 打开存储浏览器
  • 选择你的水桶
  • 使用权限打开信息面板
  • 添加具有“ 存储对象查看器”权限的服务帐户,但还有名为“ 存储旧版对象读取器”和“ 存储旧版存储桶读取器”的权限。

因为有“ Legacy”一词,所以我认为不应使用它们-它们看起来像是为了向后兼容而保留的。在尝试并添加了这些“旧版”权限之后,突然之间,我一直尝试的同一代码开始正常工作。

我仍然不确定要分配给服务帐户的最小权限是什么,但是至少现在它可以与存储桶上的所有三个“读取”权限一起使用-两个“旧”权限和一个“普通”权限。