今天在连接到医院的某个 SqlServer 数据库时连接不上,报错
“Encrypt”属性设置为“true”且 “trustServerCertificate”属性设置为“false”,但驱动程序无法使用安全套接字层 (SSL) 加密与 SQL Server 建立安全连接:错误:The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12]。
根本原因是客户端支持的TLS版本(在这个案例中是TLS1.3和TLS1.2)比服务器提供的TLS版本(TLS1.0)要新,因此客户端拒绝了使用服务器提供的旧版本协议进行通信。
对于服务端 SqlServer,应该是用了比较早的版本,导致服务器只提供 TLS1.0,但是让医院升级是不可能的,果断得从我们自己的 Java 客户端入手。
从 Java 8u291(即 1.8.0_291)开始,Oracle 和 OpenJDK 默认在安全策略中 禁用了 TLS 1.0 和 1.1。我们可以从 Java 目录的配置文件 conf/security/java.security
中 找到
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
我们可以通过修改这行配置,把TLSv1, TLSv1.1
删掉来解除 Java 的禁用,这样客户端就可以使用 TLS1.0 了
注意:jdk.tls.disabledAlgorithms
主要用于禁用某些不安全的算法(如 MD5withRSA、RC4 等),并不直接控制协议版本。所以还需要指定客户端使用 TLS1.0 才可以。在这里需要使用jdk.tls.client.protocols
参数来控制,如:-Djdk.tls.client.protocols=TLSv1,TLSv1.1
实际踩坑过程
环境背景:JDK17+、IDEA、springboot3
➡️ 没有直接修改conf/security/java.security
而是使用了 JVM 参数
由于 JDK 是为很多项目服务的,我担心修改配置文件会影响其他项目,所以优先使用 JVM 参数,这也是 DataGrip 的做法
"-Djdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL"
加了这个 JVM 参数之后,重启,依然报错❌,于是尝试在 JDBC 连接字符串中加入 ;sslProtocol=TLSv1
来尝试指明客户端版本但依然不行❌,也就是说客户端依然是使用TLS13, TLS12
➡️ 问 AI,得到下面的线索
//通过下面的代码可以查看客户端具体的协议情况
SSLContext context = SSLContext.getDefault();
System.out.println("Supported protocols: " + Arrays.toString(context.getSupportedSSLParameters().getProtocols()));
System.out.println("Default protocols: " + Arrays.toString(context.getDefaultSSLParameters().getProtocols()));
-Djdk.tls.disabledAlgorithms
会影响 SSL/TLS 握手时的“client preferences”,即告诉服务器:我支持哪些算法和协议。但注意:-Djdk.tls.disabledAlgorithms
主要用于禁用某些不安全的算法(如 MD5withRSA、RC4 等),并不直接控制协议版本。
-Dhttps.protocols=TLSv1.0,TLSv1.1
能够控制客户端协议版本,试了一下依然无效❌
➡️ 进一步问 AI,得到以下线索
-
很多人以为
-Dhttps.protocols
能控制所有 SSL/TLS 连接的行为,其实不然。 -
SQL Server JDBC 驱动程序使用的 SSLContext 并不是基于
https.protocols
的上下文!
AI建议是直接用代码定义SSLContext,但代码太长了,我还是不想用这种方法
// 创建 SSLContext,默认使用 TLS
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化为空信任管理器和密钥管理器(适用于大多数客户端场景)
sslContext.init(null, null, null);
// 获取默认参数并修改协议版本
SSLParameters params = sslContext.getDefaultSSLParameters();
params.setProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}); // 允许 TLSv1(SQL Server 使用)
// 创建新的 SSLContext,并设置为全局默认
SSLContext customSSLContext = SSLContext.getInstance("TLS");
customSSLContext.init(
sslContext.getKeyManagers(),
sslContext.getTrustManagers(),
null
);
SSLContext.setDefault(customSSLContext);
➡️ 继续问 AI
Q:怎样改变 JDK17 context.getDefaultSSLParameters().getProtocols()
A:AI 提到参数 -Djdk.tls.client.protocols="TLSv1.2"
实际上这个参数是有用的✅
通用测试工具
因为参数太多了,一时难以确定到底是哪个参数管用,所以根据上面的经验,直接写了一个类来测试
public class CheckHttpsProtocols {
public static void main(String[] args) {
String protocols = System.getProperty("https.protocols");
String protocols2 = System.getProperty("jdk.tls.disabledAlgorithms");
String protocols3 = System.getProperty("jdk.tls.client.protocols");
System.out.println("https.protocols = " + protocols);
System.out.println("jdk.tls.disabledAlgorithms = " + protocols2);
System.out.println("jdk.tls.client.protocols = " + protocols3);
try {
javax.net.ssl.SSLContext context = javax.net.ssl.SSLContext.getDefault();
System.out.println("Supported protocols: " + java.util.Arrays.toString(context.getSupportedSSLParameters().getProtocols()));
System.out.println("Default protocols: " + java.util.Arrays.toString(context.getDefaultSSLParameters().getProtocols()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用特定的 jdk 来进行 javac
编译然后java
运行
# 不使用任何 JVM 参数时,可以看到Default protocols: [TLSv1.3, TLSv1.2]❌
openjdk-21.0.1/Contents/Home/bin/java CheckHttpsProtocols
https.protocols = null
jdk.tls.disabledAlgorithms = null
jdk.tls.client.protocols = null
Supported protocols: [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
Default protocols: [TLSv1.3, TLSv1.2]
# jdk.tls.disabledAlgorithms 解除限制,发现没有变化❌
openjdk-21.0.1/Contents/Home/bin/java "-Djdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL" CheckHttpsProtocols
https.protocols = null
jdk.tls.disabledAlgorithms = SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
jdk.tls.client.protocols = null
Supported protocols: [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
Default protocols: [TLSv1.3, TLSv1.2]
# 使用jdk.tls.client.protocols,发现Default protocols直接没了❌,是因为被jdk.tls.disabledAlgorithms限制了
openjdk-21.0.1/Contents/Home/bin/java -Djdk.tls.client.protocols=TLSv1,TLSv1.1 CheckHttpsProtocols
https.protocols = null
jdk.tls.disabledAlgorithms = null
jdk.tls.client.protocols = TLSv1,TLSv1.1
Supported protocols: [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
Default protocols: []
# jdk.tls.disabledAlgorithms、jdk.tls.client.protocols 两个参数一起用,竟然也没有用 ❌
openjdk-21.0.1/Contents/Home/bin/java "-Djdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL" -Djdk.tls.client.protocols=TLSv1,TLSv1.1 CheckHttpsProtocols
https.protocols = null
jdk.tls.disabledAlgorithms = SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
jdk.tls.client.protocols = TLSv1,TLSv1.1
Supported protocols: [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
Default protocols: []
# 试了很久都不行,无奈先修改conf/security/java.security试一下,竟然成功了 ✅
openjdk-21.0.1/Contents/Home/bin/java -Djdk.tls.client.protocols=TLSv1,TLSv1.1 CheckHttpsProtocols
https.protocols = null
jdk.tls.disabledAlgorithms = null
jdk.tls.client.protocols = TLSv1,TLSv1.1
Supported protocols: [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
Default protocols: [TLSv1, TLSv1.1]
看起来 JVM 参数不管用, 经过排查,文件里的配置优先级更高,如果把文件中的这部分直接去掉,JVM 参数才会生效