Google Pub/Sub Java代码调用报PERMISSION_DENIED错误求助
我之前遇到过几乎一模一样的问题——命令行用gcloud操作Pub/Sub完全正常,但Java代码死活抛出权限拒绝,哪怕服务账号已经是Project Owner权限。下面是几个最值得优先排查的方向:
1. 确认IntelliJ是否正确读取了GOOGLE_APPLICATION_CREDENTIALS环境变量
OSX下有个很容易踩的坑:如果是从Launchpad/Spotlight启动IntelliJ,它不会继承终端(比如zsh/bash)里配置的环境变量。这是最常见的根源。
验证方法:在代码开头加一行打印,看看环境变量是否能被读取到:
System.out.println("GOOGLE_APPLICATION_CREDENTIALS: " + System.getenv("GOOGLE_APPLICATION_CREDENTIALS"));如果输出是
null,说明IntelliJ根本没拿到这个变量。解决方法:
打开Run → Edit Configurations,选中你的运行配置,找到Environment Variables选项,点击加号手动添加:- 变量名:
GOOGLE_APPLICATION_CREDENTIALS - 变量值:你的服务账号密钥文件的绝对路径(比如
/Users/yourname/Documents/service-account-key.json)
- 变量名:
2. 手动指定项目ID和凭证,排除自动加载问题
有时候ServiceOptions.getDefaultProjectId()可能会获取到gcloud的默认项目(而非服务账号关联的项目,虽然你说调试显示正确,但可以彻底排除这个可能性),或者自动加载凭证失败。可以直接手动指定:
修改你的代码,硬编码项目ID并手动加载凭证:
import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.pubsub.v1.TopicAdminClient; import com.google.cloud.pubsub.v1.TopicAdminSettings; import com.google.pubsub.v1.ProjectTopicName; import java.io.FileInputStream; public class CreateTopicExample { public static void main(String... args) throws Exception { // 手动指定项目ID String projectId = "your-actual-project-id"; String topicId = args[0]; ProjectTopicName topic = ProjectTopicName.of(projectId, topicId); // 手动加载服务账号凭证 GoogleCredentials credentials = GoogleCredentials.fromStream( new FileInputStream("/path/to/your/service-account-key.json") ); TopicAdminSettings settings = TopicAdminSettings.newBuilder() .setCredentialsProvider(() -> credentials) .build(); try (TopicAdminClient topicAdminClient = TopicAdminClient.create(settings)) { topicAdminClient.createTopic(topic); System.out.printf("Topic %s:%s created.%n", topic.getProject(), topic.getTopic()); } catch (Exception e) { e.printStackTrace(); } } }
如果这段代码能成功运行,说明问题出在自动加载机制上,回到第一个点解决环境变量的加载问题即可。
3. 检查Maven依赖的客户端版本
旧版本的Google Cloud客户端库可能存在认证相关的bug。确保你的pom.xml里使用的是最新稳定版的Pub/Sub依赖:
<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-pubsub</artifactId> <version>1.123.10</version> <!-- 可根据官方文档更新到最新版 --> </dependency>
执行mvn clean install刷新依赖后再测试。
4. 确认服务账号权限无组织级限制
虽然你的服务账号是Project Owner,但有些企业组织会设置组织政策限制(比如禁用Pub/Sub API,或者限制服务账号的IAM权限)。可以:
- 登录Google Cloud控制台,进入IAM页面,确认该服务账号的
Owner权限已生效(没有被删除或禁用)。 - 进入API库页面,确认Cloud Pub/Sub API已启用。
原始问题详情
我尝试按照Google Pub/Sub官方快速入门指南使用Java库开发,环境为IntelliJ+Maven+OSX+Java8。已创建拥有Project Owner权限的服务账号,安装Gcloud SDK并配置GOOGLE_APPLICATIONS_CREDENTIALS环境变量,命令行下可正常创建主题、发布消息等操作,但运行示例代码时抛出PERMISSION_DENIED异常。调试显示projectId已正确关联服务账号对应的项目,且控制台确认该服务账号拥有Project:Owner权限。
示例代码:
import com.google.api.gax.rpc.ApiException; import com.google.cloud.ServiceOptions; import com.google.cloud.pubsub.v1.TopicAdminClient; import com.google.pubsub.v1.ProjectTopicName; public class CreateTopicExample { /** * Create a topic. * * @param args topicId * @throws Exception exception thrown if operation is unsuccessful */ public static void main(String... args) throws Exception { // Your Google Cloud Platform project ID String projectId = ServiceOptions.getDefaultProjectId(); // Your topic ID, eg. "my-topic" String topicId = args[0]; // Create a new topic ProjectTopicName topic = ProjectTopicName.of(projectId, topicId); try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { topicAdminClient.createTopic(topic); } catch (ApiException e) { // example : code = ALREADY_EXISTS(409) implies topic already exists System.out.print(e.getStatusCode().getCode()); System.out.print(e.isRetryable()); } System.out.printf("Topic %s:%s created.%n", topic.getProject(), topic.getTopic()); } }
完整堆栈信息:
com.google.api.gax.rpc.PermissionDeniedException: io.grpc.StatusRuntimeException: PERMISSION_DENIED: User not authorized to perform this action. at com.google.api.gax.rpc.ApiExceptionFactory.createException(ApiExceptionFactory.java:55) at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:72) at com.google.api.gax.grpc.GrpcApiExceptionFactory.create(GrpcApiExceptionFactory.java:60) at com.google.api.gax.grpc.GrpcExceptionCallable$ExceptionTransformingFuture.onFailure(GrpcExceptionCallable.java:95) at com.google.api.core.ApiFutures$1.onFailure(ApiFutures.java:61) at com.google.common.util.concurrent.Futures$4.run(Futures.java:1123) at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:435) at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:900) at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:811) at com.google.common.util.concurrent.AbstractFuture.setException(AbstractFuture.java:675) at io.grpc.stub.ClientCalls$GrpcFuture.setException(ClientCalls.java:492) at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:467) at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:41) at io.grpc.internal.CensusStatsModule$StatsClientInterceptor$1$1.onClose(CensusStatsModule.java:684) at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:41) at io.grpc.internal.CensusTracingModule$TracingClientInterceptor$1$1.onClose(CensusTracingModule.java:391) at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:475) at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:63) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:557) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$600(ClientCallImpl.java:478) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:590) at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37) at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: io.grpc.StatusRuntimeException: PERMISSION_DENIED: User not authorized to perform this action. at io.grpc.Status.asRuntimeException(Status.java:526) ... 19 more
内容的提问来源于stack exchange,提问作者argybarg

