带有Java服务帐户的Google云端存储 - 403 Caller没有对存储桶

时间:2017-07-15 17:29:06

标签: java google-cloud-storage

我们希望从我们的应用服务器中的Google Storage下载文件。重要的是对单个存储桶进行只读限制访问,而不是其他任何操作。

起初,我使用了一个常规用户帐户(不是服务帐户),它有权访问我们Google云端项目中的所有存储桶,一切正常 - 我的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没有对存储桶xxxxxxxxxx的storage.objects.list访问权限。”

因此,从命令行开始一切正常。 但在Java中,身份验证存在一些问题。

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

  

com.google.cloud.storage.StorageException:匿名用户没有storage.buckets.get访问存储桶xxxxxxxxxx。

然后我尝试了以下代码(这是最简单的变体,因为它依赖于关键的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代码中缺少某些东西来确保正确的身份验证。

2 个答案:

答案 0 :(得分:7)

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应用程序以验证它是否按预期工作。

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

您发布的用于初始化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");

指定服务帐户凭据时,the project ID is automatically picked up from the information present in the json file。所以你不必再次指定它。我不完全确定这是否与您正在观察的问题有关。

应用程序默认凭据

Here is the full documentation关于Application Default Credentials,说明根据您的环境选择了哪些凭据。

  

How the Application Default Credentials work

     

您可以通过创建单个客户端来获取应用程序默认凭据   图书馆电话。返回的凭据由。确定   代码运行的环境。条件在中检查   以下顺序:

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

         

    一个。转到API Console Credentials page

         

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

         

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

         

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

         

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

         

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

         

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

  2.   
  3. 如果您已在计算机上安装了Google Cloud SDK并且已运行命令gcloud auth application-default login,那么您的   identity可以用作代理来测试从中调用API的代码   机。

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

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

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

  10.   

IAM角色

我建议您浏览可用于云端存储的IAM permissionsIAM roles。这些提供项目和桶级别的控制。此外,您可以使用ACLs to control permissions at the object level within the bucket

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

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

答案 1 :(得分:4)

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

我尝试过的其他一项功能是设置了服务帐户的权限,并且我找到了解决方案 - 实际上是出乎意料的。

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

因此,我创造了一个"空白"没有权限的服务帐户,以及配置桶配置的权限,打开Goog​​le云端Web控制台,打开存储浏览器,选择我的存储桶,打开带权限的信息面板。在那里,我添加了服务帐户"存储对象查看器"权限 - 唯一一个看起来合适的...几乎 - 还有一个名为"存储旧版对象读取器"和#34; Storage Legacy Bucket Reader",但由于" Legacy"我认为不应该使用它们 - 它们看起来像是向后兼容的东西。

在试验并添加这些"遗产"权限,突然之间我正在尝试的相同代码一直开始正常工作。

我还不完全确定我应该为服务帐户分配的最小权限集是什么,但至少现在它适用于所有3" read"桶上的权限 - 2"遗产"和1"正常"。