Java Firebase认证在Eclipse中正常,导出JAR后失效问题排查
问题背景
开发了一款供内部文档使用的小型Java工具,负责向Google Firestore数据库上传数据,供另一款内部Web应用读取。因场景风险较低,采用服务账号完成Firebase登录:从云控制台下载JSON认证文件,放置在执行路径供代码读取。
异常现象
在Eclipse开发环境中运行完全正常:数据可无错误上传,且能被Web应用正常读取。但将工具编译导出为可运行JAR后,数据上传失败,栈追踪信息如下:
com.google.api.gax.rpc.UnavailableException: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to obtain metadata
[...]
Caused by: io.grpc.StatusRuntimeException: UNAVAILABLE: Credentials failed to obtain metadata
[...]
Caused by: com.google.auth.oauth2.GoogleAuthException: Error getting access token for service account: Received fatal alert: handshake_failure, iss: [我的服务账号邮箱]
已排查情况
- 排除系统时间问题,测试设备时间均正常,同一开发机器上也出现该现象
- 使用同一OpenJDK 16,IDE内运行正常,但运行导出的JAR时无法登录Firebase
- 检查Eclipse运行命令行,除大量显式依赖外无特殊配置,IDE运行配置中无缺失的环境变量或命令行参数
相关代码片段
认证代码(IDE和JAR中均无执行错误)
private static Firestore getFirestoreInstance() { if (firestoreDbInstance != null) { return firestoreDbInstance; } InputStream serviceAccountFile; try { serviceAccountFile = new FileInputStream(/*path to the credentials file*/); } catch (FileNotFoundException e) { writeExceptionToLog(e); return null; } GoogleCredentials credentials; try { credentials = GoogleCredentials.fromStream(serviceAccountFile); FirebaseOptions options = FirebaseOptions.builder().setCredentials(credentials) .setProjectId(APIConstants.projectId).build(); FirebaseApp.initializeApp(options); firestoreDbInstance = FirestoreClient.getFirestore(); return firestoreDbInstance; } catch (IOException e) { writeToLog("Error getting firestore instance"); writeExceptionToLog(e); } return null; }
数据上传方法(错误发生在onFailure回调,异常指向认证问题)
Firestore db = getFirestoreInstance(); UUID uuid = UUID.randomUUID(); DocumentReference docRef = db.collection(APIConstants.pendingRequestsCollection).document(uuid.toString()); Map<String, Object> data = new HashMap<>(); [...adding JSON data...] ApiFuture<WriteResult> result = docRef.set(data); ApiFutures.addCallback(result, new ApiFutureCallback<WriteResult>() { @Override public void onFailure(Throwable throwable) { [...showing dialog with error...] writeExceptionToLog(throwable); } @Override public void onSuccess(WriteResult writeResult) { writeToLog("Upload successful: " + uuid.toString()); } }, Runnable::run);
相关版本
- Eclipse 2023-12 (4.30.0)
- Maven依赖:com.google.firebase:firebase-admin:9.2.0
核心疑问
同一JDK、同一认证文件、同一依赖包下,为何IDE中运行正常但导出JAR后上传失败?
可能的原因及解决方案
JAR打包时依赖缺失或冲突
Eclipse运行时会自动管理依赖,但导出普通可运行JAR时,可能未包含Firebase/GRPC相关的依赖包,或出现版本冲突。- 解决办法:使用Maven的
maven-shade-plugin或maven-assembly-plugin打包成包含所有依赖的胖JAR,确保GRPC、Google Auth等必要依赖被正确包含。
- 解决办法:使用Maven的
认证文件路径问题
代码中使用FileInputStream读取文件,IDE执行路径与JAR运行路径可能不同,导致JAR运行时无法正确找到认证文件(虽未抛出FileNotFoundException,仍需确认路径有效性)。- 解决办法:
- 将认证文件放在JAR同目录下,使用相对路径读取;
- 或把认证文件打包进JAR,改用
getClass().getResourceAsStream("/credentials.json")方式读取,避免路径依赖。
- 解决办法:
TLS/SSL握手失败
栈追踪中的handshake_failure说明SSL握手失败,可能是JAR运行时的JDK缺少Google相关根证书,或GRPC的SSL配置在打包后异常。- 解决办法:
- 检查JDK的
cacerts证书库是否包含所需根证书,必要时导入缺失证书; - 添加JVM参数
-Djavax.net.debug=ssl调试SSL握手过程,定位具体失败原因。
- 检查JDK的
- 解决办法:
Firebase App重复初始化
JAR运行时的类加载机制与IDE不同,可能导致FirebaseApp.initializeApp(options)被多次调用,引发认证异常。- 解决办法:初始化前先检查是否已有实例:
if (FirebaseApp.getApps().isEmpty()) { FirebaseApp.initializeApp(options); }
- 解决办法:初始化前先检查是否已有实例:
内容的提问来源于stack exchange,提问作者il_boga

