我知道有Cloud SQL JDBC Socket Factory。但是,这需要Application Default Credentials。我想要做的是使用服务帐户(这是JSON机密文件)来验证Cloud SQL。有人可以指点我正确的方向吗?谢谢!
GettingStarted的代码(https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory):
public class GettingStarted2{
private static final ImmutableSet<String> SYSTEM_DATABASES =
ImmutableSet.of(
// MySQL.
"mysql", "information_schema", "performance_schema",
// Postgres.
"cloudsqladmin", "postgres");
@Parameter(names = "-v", description = "Verbose logging.")
private boolean verbose = false;
private void run() throws IOException, SQLException {
System.out.println("Checking API credentials.");
GoogleCredential apiCredentials;
try {
// apiCredentials = GoogleCredential.getApplicationDefault();
apiCredentials = GoogleCredential.fromStream(new FileInputStream("<keypath.json>"))
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
} catch (IOException e) {
System.err.println(
"Unable to find API credentials. \nPlease run "
+ "'gcloud auth application-default login' to make credentials available to "
+ "this application.");
if (verbose) {
e.printStackTrace();
}
System.exit(-1);
return;
}
SQLAdmin adminApiClient = createAdminApiClient(apiCredentials);
Optional<List<DatabaseInstance>> instances = askForProject(adminApiClient);
if (!instances.isPresent()) {
return;
}
if (instances.get().isEmpty()) {
System.out.println(
"This project does not contain any Cloud SQL instances. "
+ "Please create one using the Cloud Console.");
return;
}
Optional<DatabaseInstance> optionalInstance = askForInstance(instances.get());
if (!optionalInstance.isPresent()) {
return;
}
String instanceConnectionName = optionalInstance.get().getConnectionName();
Optional<DatabaseCredentials> optionalDatabaseCredentials =
askForDatabaseCredentials(optionalInstance.get());
if (!optionalDatabaseCredentials.isPresent()) {
return;
}
Connection connection = optionalDatabaseCredentials.get().getConnection();
List<String> databases = listDatabases(optionalInstance.get(), connection);
connection.close();
if (databases.isEmpty()) {
printConnectionDetails(
optionalInstance.get(), Optional.empty(), optionalDatabaseCredentials.get());
return;
}
Optional<String> database = askForDatabase(databases);
if (!database.isPresent()) {
return;
}
printConnectionDetails(optionalInstance.get(), database, optionalDatabaseCredentials.get());
}
private List<String> listDatabases(
DatabaseInstance databaseInstance, Connection connection) throws SQLException {
String listDatabasesQuery;
switch (getDatabaseType(databaseInstance)) {
case MYSQL:
listDatabasesQuery = "SHOW DATABASES";
break;
case POSTGRES:
listDatabasesQuery =
"SELECT datname AS database FROM pg_database WHERE datistemplate = false";
break;
default:
throw new IllegalStateException();
}
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(listDatabasesQuery);
List<String> databases = new ArrayList<>();
while (resultSet.next()) {
String database = resultSet.getString("database");
if (SYSTEM_DATABASES.contains(database)) {
continue;
}
databases.add(database);
}
statement.close();
databases.sort(String::compareTo);
return databases;
}
private Optional<DatabaseCredentials> askForDatabaseCredentials(DatabaseInstance databaseInstance)
throws SQLException {
String defaultUser;
String displayDatabaseType;
String defaultDatabase;
switch (getDatabaseType(databaseInstance)) {
case MYSQL:
defaultUser = "root";
displayDatabaseType = "MySQL";
defaultDatabase = "mysql";
break;
case POSTGRES:
defaultUser = "postgres";
displayDatabaseType = "Postgres";
defaultDatabase = "postgres";
break;
default:
return Optional.empty();
}
Console console = System.console();
String user;
String lastUser = defaultUser;
for (; ; ) {
char[] password={'r','o','o','t'};
System.out.printf("Please enter %s username [%s]: ", displayDatabaseType, lastUser);
// user = console.readLine();
user="root";
if (user == null) {
return Optional.empty();
}
if (user.trim().isEmpty()) {
user = lastUser;
} else {
lastUser = user;
}
System.out.printf("Please enter %s password: ", displayDatabaseType);
//password = console.readPassword();
if (password == null) {
return Optional.empty();
}
try {
return Optional.of(
new DatabaseCredentials(
user,
password,
DriverManager.getConnection(
constructJdbcUrl(databaseInstance, defaultDatabase),
user,
new String(password))));
} catch (SQLException e) {
if (e.getErrorCode() == 1045) {
System.out.println("Invalid username/password. Please try again.");
continue;
}
// Too bad Postgres doesn't set the error code...
if (e instanceof PSQLException
&& e.getMessage().contains("password authentication failed")) {
System.out.println("Invalid username/password. Please try again.");
continue;
}
throw e;
}
}
}
private static String constructJdbcUrl(DatabaseInstance databaseInstance, String database) {
switch (getDatabaseType(databaseInstance)) {
case MYSQL:
return String.format(
"jdbc:mysql://google/%s?socketFactory=com.google.cloud.sql.mysql.SocketFactory" +
"&cloudSqlInstance=%s",
database,
databaseInstance.getConnectionName());
case POSTGRES:
return String.format(
"jdbc:postgresql://google/%s?socketFactory=com.google.cloud.sql.postgres.SocketFactory" +
"&socketFactoryArg=%s",
database,
databaseInstance.getConnectionName());
default:
throw new IllegalStateException();
}
}
private Optional<DatabaseInstance> askForInstance(List<DatabaseInstance> instances) {
Optional<Integer> instanceChoice =
chooseFromList(
"Please enter the number of the instance you want to use [1]: ",
instances.stream()
.map(
inst ->
String.format(
"%s [%s] (%s)",
inst.getName(),
inst.getDatabaseVersion(),
inst.getConnectionName()))
.collect(Collectors.toList()));
if (!instanceChoice.isPresent()) {
return Optional.empty();
}
return Optional.of(instances.get(instanceChoice.get()));
}
private Optional<String> askForDatabase(List<String> databases) {
Optional<Integer> databaseIndex =
chooseFromList("Please enter the number of the database you want to use [1]: ", databases);
if (!databaseIndex.isPresent()) {
return Optional.empty();
}
return Optional.of(databases.get(databaseIndex.get()));
}
private Optional<Integer> chooseFromList(String prompt, List<String> options) {
Console console = System.console();
for (int i = 0; i < options.size(); i++) {
System.out.println(String.format("%d: %s", i + 1, options.get(i)));
}
int choice;
for (;;) {
System.out.print(prompt);
// String line = console.readLine();
String line="1";
if (line == null) {
return Optional.empty();
}
if (line.trim().isEmpty()) {
return Optional.of(0);
} else {
try {
choice = Integer.parseInt(line);
} catch (NumberFormatException e) {
System.out.println("Invalid choice.");
continue;
}
if (choice < 1 || choice > options.size()) {
System.out.println("Invalid choice.");
continue;
}
return Optional.of(choice - 1);
}
}
}
private Optional<List<DatabaseInstance>> askForProject(SQLAdmin adminApiClient)
throws IOException {
Console console = System.console();
InstancesListResponse instancesListResponse = null;
while (instancesListResponse == null) {
String project = "";
while (project.isEmpty()) {
System.out.print("Enter the name of your Cloud project: ");
//project = console.readLine();
project="<projectname>";
if (project == null) {
return Optional.empty();
}
project = project.trim();
}
System.out.println("Listing Cloud SQL instances.");
try {
instancesListResponse = adminApiClient.instances().list(project).execute();
} catch (GoogleJsonResponseException e) {
if (e.getStatusCode() >= 400 && e.getStatusCode() < 500) {
System.out.println("That doesn't appear to be a valid project, try again.");
continue;
}
throw e;
}
}
ArrayList<DatabaseInstance> instances = new ArrayList<>(instancesListResponse.getItems());
instances.sort(Comparator.comparing(DatabaseInstance::getName));
return Optional.of(instances);
}
private void printConnectionDetails(
DatabaseInstance databaseInstance,
Optional<String> database,
DatabaseCredentials databaseCredentials) {
String databaseName = database.orElse("<database_name>");
System.out.println("\n\n");
System.out.printf(
"Use the following JDBC URL%s:\n\n %s\n",
!database.isPresent() ? " after creating a database" : "",
constructJdbcUrl(databaseInstance, databaseName));
System.out.println();
System.out.println(" Username: " + databaseCredentials.getUsername());
System.out.println(
" Password: " + (databaseCredentials.getPassword().length > 0 ? "<yes>" : "<empty>"));
System.out.println("\n\n");
}
private static SQLAdmin createAdminApiClient(Credential credential) {
HttpTransport httpTransport;
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException("Unable to initialize HTTP transport", e);
}
return new SQLAdmin.Builder(httpTransport, JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Cloud SQL Example")
.build();
}
private static final class DatabaseCredentials {
private final String username;
private final char[] password;
private final Connection connection;
public DatabaseCredentials(String username, char[] password, Connection connection) {
this.username = username;
this.password = password;
this.connection = connection;
}
public String getUsername() {
return username;
}
public char[] getPassword() {
return password;
}
public Connection getConnection() {
return connection;
}
}
private static DatabaseType getDatabaseType(DatabaseInstance databaseInstance) {
if (databaseInstance.getDatabaseVersion().startsWith("MYSQL_")) {
return DatabaseType.MYSQL;
} else if (databaseInstance.getDatabaseVersion().startsWith("POSTGRES_")) {
return DatabaseType.POSTGRES;
} else {
System.err.println("Unsupported database type: " + databaseInstance.getDatabaseVersion());
System.exit(-1);
return null;
}
}
private enum DatabaseType {
MYSQL,
POSTGRES
}
public static void main(String[] args) throws IOException, SQLException {
GettingStarted2 gettingStarted2 = new GettingStarted2();
new JCommander(gettingStarted2, args);
gettingStarted2.run();
}
}
堆栈跟踪: https://pastebin.com/PMJsAFaK 2nd StackTrace(使用Github的代码):https://pastebin.com/aH5vkLYF
答案 0 :(得分:0)
您可以在进行身份验证时manually provide the credentials。
如果您按照the JDBC Socket Factory的示例,特别是this example,您可以看到几乎所有内容都在run()
中设置。记住我的第一个链接,您必须将其更改为:
GoogleCredential apiCredentials;
try {
apiCredentials = GoogleCredential.fromStream(new FileInputStream("yourkey.json"))
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
如果您按照我已关联的示例进行操作,请不要忘记添加导入java.io.FileInputStream
和com.google.common.collect.Lists
。
答案 1 :(得分:-1)
更新(Google工程师的答复): 在对是否可以使用JSON文件进行身份验证进行了更多研究之后,这就是我的发现。他们已经声明,它目前不允许外部GoogleCredentials身份验证。
当前加载ServiceAccount的方式如下: 如您所述,工厂使用ApplicationDefaultCredentials进行身份验证。您可以将它们设置为服务帐户(这是该页面上的第一个示例)。
$ gcloud iam service-accounts create my-account
$ gcloud iam service-accounts keys create key.json --iam-account=my-account@my-project.iam.gserviceaccount.com
$ export GOOGLE_APPLICATION_CREDENTIALS=key.json
$ ./my_application.sh
当前未解决的问题(他们将来可能会使用GoogleCredentials添加身份验证): https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory/issues/66