如何为 SMTP、外部 JSON API 和 MySQL JDBC 在 Java Tomcat 中配置具有不同 SSL 配置的不同 interfaces/contexts(生命射线)?
How to configure different interfaces/contexts with different SSL configurations for SMTP, external JSON API and MySQL JDBC in Java Tomcat (Liferay)?
我们在我们的环境中使用 Liferay 应用程序。这是一个Java CMS软件,我们已经用Tomcat部署了 9. 在我们的应用中需要使用到以下接口:
- 我们通过基于 SSL 的外部 SMTP 邮件网关发送电子邮件。该网关具有“特殊”SSL 证书链。
- 我们还有一个外部 JSON API 需要 SSL 和证书身份验证!该接口有自己的“特殊”SSL 证书链。
- 而且我们还使用托管 MySQL 数据库实例,并且还有一个要求,该数据库必须通过 SSL
连接
现在,为了能够通过带有证书身份验证的 SSL 使用 SSL 邮件网关和外部 JSON API,已完成以下配置:
- 我们创建了一个 Java KeyStore 文件,其中包含 SMTP 邮件网关的证书链和外部 JSON API
的证书链
- 我们将此 KeyStore 文件配置为 Java 启动参数进入 [TOMCAT]/bin/setenv.sh 脚本 - 如下所示:
-Djavax.net.ssl.keyStore=/path/to/my/certificate.store
-Djavax.net.ssl.keyStorePassword=PASSW0RD!
-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStore=/path/to/my/certificate.store
-Djavax.net.ssl.trustStorePassword=PASSW0RD!
-Djavax.net.ssl.trustStoreType=PKCS12
(我们需要将证书存储文件用作 KeyStore 和 TrustStore,因为我们需要能够建立到外部 SMTP 邮件网关和外部 JSON API 的 SSL 连接以及JSON API 需要通过 SSL 证书进行身份验证。因此我们的应用程序也需要提供有效的证书)。
现在的问题!如果我们开启了所描述的 SSL 配置,我们将无法通过 SSL 连接到我们的 MySQL 数据库。只有这个 JDBC URL 有效:
jdbc.default.url=jdbc:mysql://[MYSQL_IP]:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=DISABLED
如果我们从 setenv.sh 中删除 -Djavax.net.ssl.* 参数,我们可以像这样通过 SSL 连接到我们的数据库:
jdbc.default.url=jdbc:mysql://[MYSQL_IP]:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=REQUIRED&enabledTLSProtocols=TLSv1.2
是的,我们还尝试将托管 MySQL 数据库中的 ca-bundle.pem 放入我们的证书存储中!
所有其他配置都会导致:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure__The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
和
Bad Handshake
因此,仅当我们不将 -Djavax.net.ssl.* 参数放入 setenv.sh 时,基于 JDBC 的 SSL 才有效。即使来自托管 MySQL 数据库的 ca-bundle.pem 在同一个 KeyStore/TrustStore 中,那么 JDBC 上的 SSL 将无法工作!曾经!我已经尝试了很多来获得 运行,但它就是行不通。我也使用了不同的 MySQL Connector/J 驱动程序版本。
是否有可能以某种方式让 JDBC URL 完全忽略 setenv.sh?
中的 -Djavax.net.ssl.* 参数
或者是否有其他方法可以将 SMTP over SSL、API over SSL 和 JDBC over SSL 合而为一?但是,所有接口都使用不同的证书链。
提前致谢!
我找到了问题的答案!解决方案是将 JDBC SQL 连接配置移动到 JNDI 数据源中。
在上面的配置中,JDBC 配置在 Java.properties 配置文件中,如下所示:
jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.default.url=jdbc:mysql://10.0.0.1:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=DISABLED
jdbc.default.username=mysqluser
jdbc.default.password=PASSW0RD
现在我已将 JDBC 配置移动到 Tomcat JNDI 数据源中,因此我现在只有这一行,而不是显示的 JDBC 属性:
jdbc.default.jndi.name=jdbc/LiferayPool
在 [TOMCAT_HOME]/conf/context.xml 中,我添加了这一行:
<Context>
...
<ResourceLink name="jdbc/LiferayPool" global="jdbc/LiferayPool" type="javax.sql.DataSource"/>
...
</Context>
我已经为我的 SQL 服务器 CA 证书创建了一个额外的 Java 密钥库。这将在下一步的 JNDI 配置中使用。
并且在 [TOMCAT_HOME]/conf/server.xml 我有我的 JNDI 数据源配置:
<GlobalNamingResources>
...
<Resource name="jdbc/LiferayPool"
auth="Container"
description="Portal DB Connection"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://10.0.0.1:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=VERIFY_CA&clientCertificateKeyStoreUrl=file:///path/to/my.keystore&clientCertificateKeyStorePassword=PASSW0RD&clientCertificateKeyStoreType=PKCS12&trustCertificateKeyStoreUrl=file:///path/to/my.keystore&trustCertificateKeyStorePassword=PASSW0RD&trustCertificateKeyStoreType=PKCS12&enabledTLSProtocols=TLSv1.2&autoReconnect=true"
username="mysqluser"
password="PASSW0RD"
maxActive="20"
maxIdle="5"
maxWait="10000" />
</GlobalNamingResources>
现在我的数据库和 SMTP 以及具有不同 SSL 配置的 JSON API 都建立了 SSL 连接,现在所有这些都可以很好地协同工作!
mysql> SELECT sbt.variable_value AS tls_version, t2.variable_value AS cipher, processlist_user AS user, processlist_host AS host FROM performance_schema.status_by_thread AS sbt JOIN performance_schema.threads AS t ON t.thread_id = sbt.thread_id JOIN performance_schema.status_by_thread AS t2 ON t2.thread_id = t.thread_id WHERE sbt.variable_name = 'Ssl_version' and t2.variable_name = 'Ssl_cipher' ORDER BY tls_version, 4;
+-------------+-----------------------------+--------------+--------------+
| tls_version | cipher | user | host |
+-------------+-----------------------------+--------------+--------------+
| TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 | mysqluser | 10.0.0.101 |
我希望这对遇到同样问题的人有所帮助...
我们在我们的环境中使用 Liferay 应用程序。这是一个Java CMS软件,我们已经用Tomcat部署了 9. 在我们的应用中需要使用到以下接口:
- 我们通过基于 SSL 的外部 SMTP 邮件网关发送电子邮件。该网关具有“特殊”SSL 证书链。
- 我们还有一个外部 JSON API 需要 SSL 和证书身份验证!该接口有自己的“特殊”SSL 证书链。
- 而且我们还使用托管 MySQL 数据库实例,并且还有一个要求,该数据库必须通过 SSL 连接
现在,为了能够通过带有证书身份验证的 SSL 使用 SSL 邮件网关和外部 JSON API,已完成以下配置:
- 我们创建了一个 Java KeyStore 文件,其中包含 SMTP 邮件网关的证书链和外部 JSON API 的证书链
- 我们将此 KeyStore 文件配置为 Java 启动参数进入 [TOMCAT]/bin/setenv.sh 脚本 - 如下所示:
-Djavax.net.ssl.keyStore=/path/to/my/certificate.store
-Djavax.net.ssl.keyStorePassword=PASSW0RD!
-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStore=/path/to/my/certificate.store
-Djavax.net.ssl.trustStorePassword=PASSW0RD!
-Djavax.net.ssl.trustStoreType=PKCS12
(我们需要将证书存储文件用作 KeyStore 和 TrustStore,因为我们需要能够建立到外部 SMTP 邮件网关和外部 JSON API 的 SSL 连接以及JSON API 需要通过 SSL 证书进行身份验证。因此我们的应用程序也需要提供有效的证书)。
现在的问题!如果我们开启了所描述的 SSL 配置,我们将无法通过 SSL 连接到我们的 MySQL 数据库。只有这个 JDBC URL 有效:
jdbc.default.url=jdbc:mysql://[MYSQL_IP]:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=DISABLED
如果我们从 setenv.sh 中删除 -Djavax.net.ssl.* 参数,我们可以像这样通过 SSL 连接到我们的数据库:
jdbc.default.url=jdbc:mysql://[MYSQL_IP]:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=REQUIRED&enabledTLSProtocols=TLSv1.2
是的,我们还尝试将托管 MySQL 数据库中的 ca-bundle.pem 放入我们的证书存储中!
所有其他配置都会导致:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure__The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
和
Bad Handshake
因此,仅当我们不将 -Djavax.net.ssl.* 参数放入 setenv.sh 时,基于 JDBC 的 SSL 才有效。即使来自托管 MySQL 数据库的 ca-bundle.pem 在同一个 KeyStore/TrustStore 中,那么 JDBC 上的 SSL 将无法工作!曾经!我已经尝试了很多来获得 运行,但它就是行不通。我也使用了不同的 MySQL Connector/J 驱动程序版本。
是否有可能以某种方式让 JDBC URL 完全忽略 setenv.sh?
中的 -Djavax.net.ssl.* 参数或者是否有其他方法可以将 SMTP over SSL、API over SSL 和 JDBC over SSL 合而为一?但是,所有接口都使用不同的证书链。
提前致谢!
我找到了问题的答案!解决方案是将 JDBC SQL 连接配置移动到 JNDI 数据源中。
在上面的配置中,JDBC 配置在 Java.properties 配置文件中,如下所示:
jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.default.url=jdbc:mysql://10.0.0.1:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=DISABLED
jdbc.default.username=mysqluser
jdbc.default.password=PASSW0RD
现在我已将 JDBC 配置移动到 Tomcat JNDI 数据源中,因此我现在只有这一行,而不是显示的 JDBC 属性:
jdbc.default.jndi.name=jdbc/LiferayPool
在 [TOMCAT_HOME]/conf/context.xml 中,我添加了这一行:
<Context>
...
<ResourceLink name="jdbc/LiferayPool" global="jdbc/LiferayPool" type="javax.sql.DataSource"/>
...
</Context>
我已经为我的 SQL 服务器 CA 证书创建了一个额外的 Java 密钥库。这将在下一步的 JNDI 配置中使用。
并且在 [TOMCAT_HOME]/conf/server.xml 我有我的 JNDI 数据源配置:
<GlobalNamingResources>
...
<Resource name="jdbc/LiferayPool"
auth="Container"
description="Portal DB Connection"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://10.0.0.1:3306/lportal?characterEncoding=UTF-8&dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false&useUnicode=true&sslMode=VERIFY_CA&clientCertificateKeyStoreUrl=file:///path/to/my.keystore&clientCertificateKeyStorePassword=PASSW0RD&clientCertificateKeyStoreType=PKCS12&trustCertificateKeyStoreUrl=file:///path/to/my.keystore&trustCertificateKeyStorePassword=PASSW0RD&trustCertificateKeyStoreType=PKCS12&enabledTLSProtocols=TLSv1.2&autoReconnect=true"
username="mysqluser"
password="PASSW0RD"
maxActive="20"
maxIdle="5"
maxWait="10000" />
</GlobalNamingResources>
现在我的数据库和 SMTP 以及具有不同 SSL 配置的 JSON API 都建立了 SSL 连接,现在所有这些都可以很好地协同工作!
mysql> SELECT sbt.variable_value AS tls_version, t2.variable_value AS cipher, processlist_user AS user, processlist_host AS host FROM performance_schema.status_by_thread AS sbt JOIN performance_schema.threads AS t ON t.thread_id = sbt.thread_id JOIN performance_schema.status_by_thread AS t2 ON t2.thread_id = t.thread_id WHERE sbt.variable_name = 'Ssl_version' and t2.variable_name = 'Ssl_cipher' ORDER BY tls_version, 4;
+-------------+-----------------------------+--------------+--------------+
| tls_version | cipher | user | host |
+-------------+-----------------------------+--------------+--------------+
| TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 | mysqluser | 10.0.0.101 |
我希望这对遇到同样问题的人有所帮助...