java - 具有 Java 服务帐户的 Google Cloud Storage - 403 Caller 没有存储桶的 storage.objects.list 访问权限

标签 java google-cloud-storage

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

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

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

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

Storage Object Viewer (3 members) Read access to GCS objects.

gsutil 命令行实用程序适用于此帐户 - 从命令行它允许访问此存储桶,但不允许访问其他存储桶。

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

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

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

"AccessDeniedException: 403 Caller does not have storage.objects.list access to bucket xxxxxxxxxx."

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

我之前使用普通用户帐户的默认身份验证停止工作 - 它报告错误:

com.google.cloud.storage.StorageException: Anonymous users does not have storage.buckets.get access to bucket 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: Caller does not have storage.buckets.get access to bucket mybucketname. Caused by: com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden

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

最佳答案

TL;DR - 如果您使用 Application Default Credentials (顺便说一句,当你执行 StorageOptions.getDefaultInstance().getService(); 时,你是哪个),如果你需要使用来自服务帐户的凭据,你可以这样做而无需更改你的代码。您需要做的就是将 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

You can get Application Default Credentials by making a single client library call. The credentials returned are determined by the environment the code is running in. Conditions are checked in the following order:

  1. The environment variable GOOGLE_APPLICATION_CREDENTIALS is checked. If this variable is specified it should point to a file that defines the credentials. The simplest way to get a credential for this purpose is to create a Service account key in the Google API Console:

    a. Go to the API Console Credentials page.

    b. From the project drop-down, select your project.

    c. On the Credentials page, select the Create credentials drop-down, then select Service account key.

    d.From the Service account drop-down, select an existing service account or create a new one.

    e. For Key type, select the JSON key option, then select Create. The file automatically downloads to your computer.

    f. Put the *.json file you just downloaded in a directory of your choosing. This directory must be private (you can't let anyone get access to this), but accessible to your web server code.

    g. Set the environment variable GOOGLE_APPLICATION_CREDENTIALS to the path of the JSON file downloaded.

  2. If you have installed the Google Cloud SDK on your machine and have run the command gcloud auth application-default login, your identity can be used as a proxy to test code calling APIs from that machine.

  3. If you are running in Google App Engine production, the built-in service account associated with the application will be used.

  4. If you are running in Google Compute Engine production, the built-in service account associated with the virtual machine instance will be used.

  5. If none of these conditions is true, an error will occur.

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 添加到服务帐户。

关于java - 具有 Java 服务帐户的 Google Cloud Storage - 403 Caller 没有存储桶的 storage.objects.list 访问权限,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45120940/

相关文章:

java - 为什么我会看到 "the temporary folder has not yet been created"消息?

java - 使用 querydsl 如何检查由一对多关系产生的一组对象中的特定对象?

java - 将 Java 代码转换为 Groovy

android - 如何将我的 SQLITE 数据库保存到 Google 云端硬盘?

google-app-engine - GAE 存储定价与 Google Cloud Storage 定价之间的差异

java - getResourcesAsStream 无法从 jUnit 加载文件

java - 阿斯普斯。对 Excel 文档中的单元格区域应用水平对齐

Firebase 存储功能权限

django - Google App Engine 文件上传的响应时间非常慢

google-cloud-storage - Google云端存储交易?