SpringBoot中 SSL 连接 PostgreSQL 数据库

在本文中,我们通过 SSL 配置并安全地与 PostgreSQL 服务器建立了数据库连接。

在数据库管理领域,确保应用程序和数据库之间的安全通信非常重要。在本教程中,我们将介绍如何从 JDBC 和 Spring Boot 通过 SSL 连接到 PostgreSQL。

PostgreSQL 配置
我们需要更新 PostgreSQL 服务器以允许通过 SSL 进行连接。为此,我们需要准备好或创建我们的根 (CA) 证书、服务器证书和私钥。让我们修改 PostgreSQL 服务器配置文件postgresql.conf  并提供证书文件的路径:

...
ssl = on
ssl_ca_file = '/opt/homebrew/var/postgresql@14/rootCA.crt'
ssl_cert_file = '/opt/homebrew/var/postgresql@14/localhost.crt'
#ssl_crl_file = ''
#ssl_crl_dir = ''
ssl_key_file = '/opt/homebrew/var/postgresql@14/localhost.key'
...

现在,让我们修改 PostgreSQL客户端配置文件 pg_hba.conf,并在 IPv4 部分下添加以下内容:

...
# IPv4 local connections:
hostssl    all             all             0.0.0.0/0            cert
...

Maven配置
让我们将PostgreSQL JDBC 驱动程序依赖项添加到我们的pom.xml文件中以连接到服务器:

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
</dependency>

在撰写本文时,我们正在使用最新版本的驱动程序,以便我们使用 pkcs-12 客户端证书格式。

从 JDBC 连接
需要客户端证书才能通过 SSL 建立安全连接。因此,我们需要准备好客户端证书和密钥文件,并使用用于生成服务器证书的相同根证书创建证书。

但是,我们不能使用私钥直接从 JDBC 连接,因此,我们需要将私钥导出为“pkcs-8”兼容格式:

openssl pkcs8 -topk8 -inform PEM -outform DER -in certs/pg_client.key -out certs/pg_client.pk8 -nocrypt

使用导出的密钥,我们可以通过定义以下属性来适当地连接到 PostgreSQL 服务器:
  • 用户名和密码
  • 证书位置
  • pkcs-8 密钥位置以及最后
  • 根 CA 证书位置。
为了演示这一点,让我们创建一个名为checkConnectionSsl的方法的PgJdbc类:

public class PgJdbc {
    public void checkConnectionSsl(String url, String username, String password, Map<String, String> extraProps) {
        Properties props = new Properties();
        props.putAll(extraProps);
        props.put("username", username);
        props.put(
"password", password);
        props.put(
"sslmode", "verify-ca");
        props.put(
"ssl", "true");
        try (Connection connection = DriverManager.getConnection(url, props)) {
            if (!connection.isClosed()) {
                connection.close();
            }
            System.out.println(
"Connection was successful");
        } catch (SQLException e) {
            System.out.println(
"Connection failed");
        }
    }
   
// ...
}

checkConnectionSsl 方法接受连接所需的参数。根据我们想要的连接方式,我们将通过extraProps 属性传递适当的键值对。我们将 ssl 属性设置为 true以指示我们想要使用 SSL 进行连接,sslmode 属性指定证书验证的类型。

让我们添加一个主要方法并尝试建立连接:

public class PgJdbc {
    // ...
    public static void main(String[] args) {
        PgJdbc pg = new PgJdbc();
        String url =
"jdbc:postgresql://localhost:5432/testdb";
        String username =
"postgres";
        String password =
"password";
        String BASE_PATH = Paths.get(
"certs")
          .toAbsolutePath()
          .toString();
        Map<String, String> connectionProperties = Map.of(
         
"sslcert", BASE_PATH.concat("/pg_client.crt"),
         
"sslkey", BASE_PATH.concat("/pg_client.pk8"),
         
"sslrootcert", BASE_PATH.concat("/root.crt"));
        System.out.println(
"Connection without keystore and truststore");
        pg.checkConnectionSsl(url, username, password, connectionProperties);
    }
}
$ mvn clean compile -q exec:java -Dexec.mainClass=
"com.baeldung.pgoverssl.PgJdbc"
Connection was successful

从上面的输出可以看出,我们已经能够建立成功的连接。

使用 Keystore 从 JDBC 进行连接
也可以使用密钥库和信任库建立相同的连接。但是,这需要将客户端证书和私钥转换为 pkcs-12 兼容格式,然后使用 Java 附带的 keytool 实用程序从中创建密钥库,并从根 CA 证书中创建信任库。

我们将证书和密钥导出为 pkcs-12 格式:

$ openssl pkcs12 -export -in certs/pg_client.crt -inkey certs/pg_client.key -out certs/pg_client.p12 -name postgres

使用导出的证书,让我们创建一个密钥库:

$ keytool -importkeystore -destkeystore certs/pg_client.jks -srckeystore certs/pg_client.p12 -srcstoretype pkcs12
Importing keystore certs/pg_client.p12 to certs/pg_client.jks...
Enter destination keystore password: 
...
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

最后,我们可以创建信任库:

$ keytool -import -alias server -file certs/root.crt -keystore certs/truststore.jks -storepass password
...
Certificate was added to keystore

现在有了密钥库和信任库,让我们修改主要方法并尝试建立连接:

public class PgJdbc {
    // ...
    public static void main(String[] args) {
       
// ...
        System.setProperty(
"javax.net.ssl.keyStore", BASE_PATH.concat("/pg_client.jks"));
        System.setProperty(
"javax.net.ssl.keyStorePassword", "password");
        System.setProperty(
"javax.net.ssl.trustStore", BASE_PATH.concat("/truststore.jks"));
        System.setProperty(
"javax.net.ssl.trustStorePassword", "password");
        System.out.println(
"\nConnection using keystore and truststore");
        pg.checkConnectionSsl(url, username, password, Map.of(
"sslfactory", "org.postgresql.ssl.DefaultJavaSSLFactory"));
    }
}

请注意,我们提供了四个系统属性,其中两个是密码。这些密码是在创建密钥库和信任库时提供的。此外,我们还必须使用DefaultJavaSSLFactory提供sslfactory参数以进行验证。

我们再测试一下:

Connection using keystore and truststore
Connection was successful

这也是一次成功的连接。

从 Spring Boot 连接
以类似的方式,我们可以从 Spring Boot 应用程序通过 SSL 进行连接。让我们将基本 Spring Boot 应用程序所需的依赖项添加到pom.xml文件中:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.3</version>
</parent>
// ...
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

我们需要一个基本的 Spring Boot 启动类:

@SpringBootApplication
public class PgSpringboot {
    public static void main(String[] args) {
        SpringApplication.run(PgSpringboot.class, args);
    }
}

接下来,让我们使用连接所需的属性配置application.yaml文件:

spring:
  application:
    name: postgresqlssltest
  datasource:
    url: jdbc:postgresql://localhost:5432/testdb?ssl=true&sslmode=verify-ca&sslrootcert=certs/root.crt&sslcert=certs/pg_client.crt&sslkey=certs/pg_client.pk8
    username: postgres
    password:
"password"
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.PostgreSQLDialect

让我们尝试通过运行应用程序来连接到 PostgreSQL 服务器:

$ mvn clean compile -q exec:java -Dexec.mainClass="com.baeldung.pgoverssl.PgSpringboot" -Dspring.config.location=classpath:./pgoverssl/application.yaml
...
2024-07-04T21:41:17.552+01:00  INFO 458 --- [postgresqlssltest] [ringboot.main()] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-07-04T21:41:18.290+01:00  INFO 458 --- [postgresqlssltest] [ringboot.main()] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@4e331d3d
2024-07-04T21:41:18.291+01:00  INFO 458 --- [postgresqlssltest] [ringboot.main()] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
...

正如我们从 JDBC 连接一样,我们已经能够使用 Spring Boot 成功建立与 PostgreSQL 服务器的连接。