Android应用连接Google Cloud SQL时的认证错误求助
问题:Android应用连接Google Cloud MySQL数据库时的认证错误
我正在开发Android应用以连接Google Cloud MySQL数据库,遵循官方指南。已将IP地址添加到数据库实例的允许网络列表,并启用了Cloud SQL API。以示例代码为基础开发,在模拟器运行应用时出现如下错误:
2023-03-25 15:58:37.727 13755-13755/com.example.connectiontest E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.connectiontest, PID: 13755 com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Could not create connection to database server. at com.zaxxer.hikari.pool.HikariPool.throwPoolInitializationException(HikariPool.java:595) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:581) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115) at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81) at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53) at android.view.View.performClick(View.java:7506) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202) at android.view.View.performClickInternal(View.java:7483) at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0) at android.view.View$PerformClick.run(View.java:29334) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: java.sql.SQLNonTransientConnectionException: Could not create connection to database server. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63) at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:997) at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:814) at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:444) at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:237) at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:121) at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358) at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206) at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:477) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:560) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115) at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81) at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53) at android.view.View.performClick(View.java:7506) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202) at android.view.View.performClickInternal(View.java:7483) at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0) at android.view.View$PerformClick.run(View.java:29334) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: java.lang.RuntimeException: Unable to obtain credentials to communicate with the Cloud SQL API at com.google.cloud.sql.core.CoreSocketFactory$ApplicationDefaultCredentialFactory.create(CoreSocketFactory.java:392) at com.google.cloud.sql.core.CoreSocketFactory.getInstance(CoreSocketFactory.java:140) at com.google.cloud.sql.core.CoreSocketFactory.connect(CoreSocketFactory.java:256) at com.google.cloud.sql.core.CoreSocketFactory.connect(CoreSocketFactory.java:215) at com.google.cloud.sql.mysql.SocketFactory.connect(SocketFactory.java:57) at com.google.cloud.sql.mysql.SocketFactory.connect(SocketFactory.java:43) at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:63) at com.mysql.cj.NativeSession.connect(NativeSession.java:120) at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:944) at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:814) at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:444) at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:237) at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:121) at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:358) at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206) at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:477) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:560) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115) at com.zaxxer.hikari.HikariDataSource.<init>(HikariDataSource.java:81) at com.example.connectiontest.MainActivity$1.onClick(MainActivity.java:53) at android.view.View.performClick(View.java:7506) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1202) at android.view.View.performClickInternal(View.java:7483) at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0) at android.view.View$PerformClick.run(View.java:29334) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information. at com.google.auth.oauth2.DefaultCredentialsProvider.getDefaultCredentials(DefaultCredentialsProvider.java:125) 2023-03-25 15:58:37.728 13755-13755/com.example.connectiontest E/AndroidRuntime: at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:125) at com.google.auth.oauth2.GoogleCredentials.getApplicationDefault(GoogleCredentials.java:97) at com.google.cloud.sql.core.CoreSocketFactory$ApplicationDefaultCredentialFactory.create(CoreSocketFactory.java:390) ... 33 more
我了解了Application Default Credentials相关内容,但移动设备无法设置环境变量,且未在Google文档中找到替代认证方式。以下是简化后的应用代码:
MainActivity代码
import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; public class MainActivity extends AppCompatActivity { private static final String CONNECTION_NAME = "***"; private static final String DB_NAME = "***"; private static final String DB_USER = "***"; private static final String DB_PASSWORD = "***"; Button testConn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); testConn = (Button) findViewById(R.id.button); testConn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String jdbcURL = String.format("jdbc:mysql:///%s", DB_NAME); Properties connProps = new Properties(); connProps.setProperty("user", DB_USER); connProps.setProperty("password", DB_PASSWORD); connProps.setProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory"); connProps.setProperty("cloudSqlInstance", CONNECTION_NAME); HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcURL); config.setDataSourceProperties(connProps); config.setConnectionTimeout(10000); HikariDataSource connectionPool = new HikariDataSource(config);//代码在此处崩溃 try { Connection conn = connectionPool.getConnection(); String stmt = "SELECT test_field FROM test_table limit 1;"; PreparedStatement selectStmt = conn.prepareStatement(stmt); selectStmt.setQueryTimeout(10); ResultSet rs = selectStmt.executeQuery(); while (rs.next()) { Toast.makeText(getApplicationContext(), rs.getString("test_field"), Toast.LENGTH_LONG).show(); } } catch (SQLException e) { e.printStackTrace(); } } }); } }
build.gradle代码
plugins { id 'com.android.application' } android { namespace 'com.example.connectiontest' compileSdk 33 defaultConfig { applicationId "com.example.connectiontest" minSdk 26 targetSdk 33 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } packagingOptions { resources.excludes.add("META-INF/*") } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation files('libs/mysql-connector-j-8.0.32.jar') implementation 'com.zaxxer:HikariCP:3.4.5' implementation 'com.google.cloud.sql:mysql-socket-factory-connector-j-8:1.11.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' }
解决方案
核心问题
你使用的Cloud SQL Socket Factory默认依赖Application Default Credentials(ADC),但Android设备无法设置环境变量,因此需要更换认证方式。
方案1:使用服务账号密钥文件
- 在Google Cloud控制台创建服务账号,授予Cloud SQL Client角色,下载JSON格式的密钥文件。
- 将密钥文件放入Android项目的
assets目录。 - 修改连接属性,添加
credentialsFile指定密钥文件路径:
connProps.setProperty("credentialsFile", "file:///android_asset/your-service-account-key.json");
注意:发布版本中请勿直接暴露密钥文件,建议通过加密存储等安全方式管理。
方案2:直接使用公网IP连接(跳过Socket Factory)
既然你已经将模拟器/设备IP加入Cloud SQL白名单,可以直接用标准JDBC URL连接,无需Socket Factory:
// 替换为你的Cloud SQL实例公网IP String jdbcURL = String.format("jdbc:mysql://<INSTANCE_PUBLIC_IP>/%s?useSSL=false", DB_NAME); // 移除以下两行Socket Factory相关配置 // connProps.setProperty("socketFactory", "com.google.cloud.sql.mysql.SocketFactory"); // connProps.setProperty("cloudSqlInstance", CONNECTION_NAME);
这种方式更适配移动设备,避免复杂认证流程,但需确保IP白名单配置正确,生产环境建议启用SSL加密。
方案3:通过中间层API连接(生产环境推荐)
直接在Android应用中连接数据库存在凭证泄露风险,生产环境建议搭建后端API(如Cloud Functions、App Engine),Android应用通过调用API操作数据库,既能避免暴露数据库凭证,也便于管控访问权限。
内容的提问来源于stack exchange,提问作者CurtisF
相关产品推荐
相关产品推荐

