在本文中,我们通过 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 服务器的连接。