【问题标题】:How to use Service Account to authenticate with Google Cloud SQL Java如何使用服务帐户向 Google Cloud SQL Java 进行身份验证
【发布时间】:2018-11-25 04:01:38
【问题描述】:

我知道有 Cloud SQL JDBC Socket Factory。但是,这需要应用程序默认凭据。我想要做的是使用服务帐户(这是 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

【问题讨论】:

  • 您是否生成了JSON file correctly? JSON 文件是否与pom.xml 文件位于同一文件夹中?试着把它放在那里,而不是“资源包”(你是什么意思?)当你创建FileInputStream 对象时,你是否真的把括号放在字符串中?你不应该。正如我在其他评论中提到的,请在运行“入门”示例时执行此操作。
  • 关于资源包,是我项目中的一个包。至于 FileInputStream 我没有把括号放在字符串中。它更类似于这样:“src/Resources/XXX.json”
  • 添加了使用来自 Github 的代码运行的新 Stacktrace。看起来我最后得到了一个稍微不同的错误,但据我所知,他们仍在搜索应用程序默认凭据。感谢您的帮助!
  • 试着把它放在我之前多次提到的地方,至少作为概念证明,以丢弃任何用户问题。如果您仍然无法运行它,您可以将您拥有的内容发布到您自己的 Github 存储库中,当然之前清理所有可能的信息,看看我是否可以运行它。如果我能运行它,我可能会说这是你的问题,但让我们看看。另外,看看你最后的跟踪,你有没有修改过代码? (除了 JSON 位)。
  • 是的。正如您在最新测试中提到的那样,我确实将它与 pom.xml 文件一起放置。我没有对代码进行任何更改。最后,查看Github 上打开的问题,我认为目前无法使用服务帐户创建 GoogleCredential 并将其传递给工厂。如果我错了,请纠正我

标签: java google-cloud-platform google-cloud-sql cloud-sql-proxy


【解决方案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

【讨论】:

  • 为什么这被否决了?这是github上谷歌工程师的回答。
【解决方案2】:

认证时可以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。

【讨论】:

  • 嗯。感谢您的帮助,但它确实不适合我。我硬编码了其他值,例如我的项目、用户、密码等,但它仍然出现错误,因为它仍在尝试检测应用程序默认凭据。
  • 从我的调试中,即使在更改为从我的 JSON 服务帐户读取之后,在我重新验证我的应用程序默认凭据之后,代码也会运行。
  • 所以,目前它运行但不是使用您想要的凭据,而是默认使用凭据,对吗?您提到它不起作用,并出现错误。您能否编辑您的问题以包括您尝试过的内容(当然,清理任何敏感信息)和错误的堆栈跟踪?我也许可以帮助你。
  • 我已经在 pastebin 中添加了我的 GettingStarted 代码和堆栈跟踪,因为有 30,000 个字符的限制。希望你能看到堆栈跟踪。我对值进行了硬编码,因为我也不确定为什么它不提示我输入值,并且如果您尝试仅从 Github 运行代码,则会导致 nullPointerException。感谢您的帮助!
  • 我一直在再次运行这个示例,它适用于我的 MySQL 和 PostgreSQL 实例,我也一直在使用凭证位,因为错误与它有关。在继续研究之前有一个简单的问题:您的凭据所在的 JSON 文件在哪里?它应该与您的“pom.xml”文件位于同一文件夹中。另外,请检查文件是否正确生成,如果需要,您可以generate another one。
猜你喜欢
  • 2017-11-03
  • 1970-01-01
  • 2013-09-23
  • 2020-07-21
  • 1970-01-01
  • 2020-05-21
  • 1970-01-01
  • 1970-01-01
  • 2016-01-31
相关资源
最近更新 更多