JDK多版本TLS协议升级实操指南
一、概述
TLS协议安全背景
随着网络安全标准的不断提升,老旧的加密协议已无法满足当前的安全需求。SSLv3 存在严重的 POODLE 漏洞(CVE-2014-3566),TLSv1.0 易受 BEAST 攻击(CVE-2011-3389),而 TLSv1.1 也已被 IETF 在 RFC 8996 中正式废弃。为保障数据传输安全,禁用不安全的 SSLv3/TLSv1.0/TLSv1.1 并全面启用 TLSv1.2/TLSv1.3 已成为系统运维的必选项。
四个JDK版本的TLS支持能力总览表
| JDK版本 | 原生支持TLS版本 | 默认启用状态 | 是否需要额外配置 | 推荐操作 |
|---|---|---|---|---|
| jdk1.6.0_45 | 最高 TLSv1.0 | 仅启用 TLSv1.0 | 是 | 升级小版本至 1.6.0_181/211 |
| jdk1.7.0_79 | 最高 TLSv1.2 | 默认未启用 TLSv1.2 | 是 | 添加 JVM 启动参数或修改 java.security |
| jdk1.8.0_112 | 最高 TLSv1.2 | 默认未禁用 TLSv1.0/1.1 | 是 | 修改 java.security 禁用旧协议 |
| jdk-11.0.0.2 | 最高 TLSv1.3 | 默认未禁用 TLSv1.0/1.1 | 是 | 升级小版本至 11.0.11+ 或禁用 TLSv1.3 |
二、JDK 1.6.0_45 TLS处理方案
2.1 版本现状分析
JDK 1.6.0_45 原生不支持 TLSv1.2,默认最高仅支持 TLSv1.0。
- 支持的协议:SSLv3, TLSv1.0
- 不支持的协议:TLSv1.1, TLSv1.2, TLSv1.3
这意味着任何需要连接现代 HTTPS 接口(通常强制要求 TLSv1.2+)的场景都会直接失败。
2.2 方案一:升级JDK小版本(最推荐)
具体操作: 将 jdk1.6.0_45 升级到 jdk1.6.0_181 或 1.6.0_211(1.6终版)。
修改位置及影响范围:
| 修改位置 | 修改内容 | 影响范围 |
|---|---|---|
/etc/profile 中的 JAVA_HOME 和 PATH |
指向新的 JDK 安装目录 | 该用户所有Java应用 |
Tomcat catalina.sh 中的 CATALINA_OPTS |
无需额外配置,只需指定协议 | 仅当前Tomcat应用 |
| 启动参数 | 添加 -Dhttps.protocols=TLSv1.2 |
仅当前Java进程 |
启动参数示例:
-Dhttps.protocols=TLSv1.2
优点:
- 无需修改 Struts 或 Hibernate 的任何代码
- 无需引入第三方 Jar 包
- 官方支持的路径,最稳定
2.3 方案二:引入BouncyCastle(不升级JDK)
适用场景: 受限于操作系统太老,无法升级 JDK 版本。
操作步骤:
- 下载
bcprov-jdk15on老版本(注意版本兼容性,需寻找支持 JDK 1.6 的版本,如 1.46 或 1.50)。 - 将 jar 包放入项目的 classpath 中。
- 在代码中注册 Provider 并显式指定 SSLContext。
代码注册Provider示例(在 static 块或 Filter 中):
java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
static {
Security.addProvider(new BouncyCastleProvider());
}
代码强制指定SSLContext示例:
java
// 针对 HttpsURLConnection
SSLContext context = SSLContext.getInstance("TLSv1.2", "BC"); // 指定使用 BC 库
context.init(null, null, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
影响范围: 仅影响当前应用代码。
2.4 方案三:Nginx反向代理(最后手段)
架构图说明:
客户端 --HTTPS(TLSv1.2)--> Nginx --HTTP(明文)--> JDK 1.6 应用
Nginx 使用新版 OpenSSL 处理 TLSv1.2,然后内部通过 HTTP 转发给 JDK 1.6 应用。JDK 1.6 应用完全不需要支持 TLSv1.2。
Nginx配置示例:
nginx
server {
listen 443 ssl;
ssl_protocols TLSv1.2;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
影响范围: Nginx 层处理 TLS,JDK 1.6 应用只处理内部 HTTP 请求。
2.5 关键风险排查
SNI问题(Server Name Indication):
- 现代 HTTPS 服务器(如阿里云、AWS)通常开启 SNI。JDK 1.6 对 SNI 支持极差,可能会导致握手失败。
- 解决: 添加启动参数
-Djsse.enableSNIExtension=false来尝试绕过(但这可能导致某些域名访问失败)。
证书算法问题:
- 现代 SSL 证书多使用 SHA-256 签名。JDK 1.6.0_45 对 SHA-256 的支持可能不完善,或者根证书库里没有现代 CA(如 Let's Encrypt, Amazon Root CA 等)。
- 现象: 报错
PKIX path building failed。 - 解决: 手动将新的根证书导入到 JDK 1.6 的
cacerts文件中:
bash
keytool -import -alias rootca -file rootca.crt \
-keystore $JAVA_HOME/jre/lib/security/cacerts \
-storepass changeit
Hibernate连接池SSL问题:
- 如果数据库连接也是 SSL 的(如 RDS),老版本的 Hibernate/JDBC 驱动可能也不支持新的加密套件。
2.6 验证方法
openssl测试命令:
bash
# 测试 TLSv1.2 连通性
openssl s_client -connect <host>:<port> -tls1_2
# 确认 TLSv1.0 被拒绝(应该连接失败)
openssl s_client -connect <host>:<port> -tls1
启动日志检查: 确认应用启动时无 SSL/TLS 相关异常。
三、JDK 1.7.0_79 TLS处理方案
3.1 版本现状分析
- 原生支持 TLSv1.2,但默认未启用。
- 不支持 TLSv1.3。
- 默认最高支持 TLSv1.0,需要手动启用 TLSv1.2。
3.2 方案一:JVM启动参数(最推荐)
修改位置详细说明:
| 修改位置 | 修改内容 | 影响范围 |
|---|---|---|
/etc/profile 中的 JAVA_HOME |
指向新的 JDK 目录 | 该用户所有Java应用 |
Tomcat catalina.sh 中的 CATALINA_OPTS |
添加 JVM 参数 | 仅当前Tomcat应用 |
Tomcat setenv.sh(推荐方式) |
添加 JVM 参数 | 仅当前Tomcat应用 |
| 普通Java应用启动脚本 | 添加 JVM 参数 | 仅当前Java进程 |
添加参数:
-Dhttps.protocols=TLSv1.2 -Djdk.tls.client.protocols=TLSv1.2
完整配置示例(setenv.sh):
bash
export JAVA_OPTS="$JAVA_OPTS -Dhttps.protocols=TLSv1.2 -Djdk.tls.client.protocols=TLSv1.2"
影响范围: 仅影响当前应用。
3.3 方案二:修改java.security文件(全局生效,谨慎操作)
文件路径: $JAVA_HOME/jre/lib/security/java.security
修改 jdk.tls.disabledAlgorithms 行:
- 修改前:
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768 - 修改后:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 768
影响范围: 影响该 JDK 上运行的所有应用。修改后需重启所有使用该 JDK 的应用。
3.4 方案三:代码层指定
HttpsURLConnection 示例:
java
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
URL url = new URL("https://your-api-endpoint.com");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setEnabledProtocols(new String[]{"TLSv1.2"});
Apache HttpClient 示例:
java
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, new SecureRandom());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier()
);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
影响范围: 仅影响指定代码段。
3.5 代码排查
全局搜索命令:
bash
grep -rn "SSLContext.getInstance" --include="*.java" .
grep -rn "TLSv1\"" --include="*.java" .
grep -rn "SSLv3" --include="*.java" .
重点排查硬编码为 "SSL" 或 "TLSv1" 的地方,改为 "TLSv1.2"。
3.6 验证方法
bash
# 测试 TLSv1.2 连通性
openssl s_client -connect <host>:<port> -tls1_2
# 确认 TLSv1.0 被拒绝
openssl s_client -connect <host>:<port> -tls1
检查应用日志确认协议协商成功,无 SSLHandshakeException。
四、JDK 1.8.0_112 TLS处理方案
4.1 版本现状分析
- 原生支持 TLSv1.2,不支持 TLSv1.3。
- 当前 java.security 配置:
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768 - 未禁用 TLSv1 和 TLSv1.1,存在安全风险。
4.2 方案一:JVM启动参数(最推荐)
修改位置详细说明:
| 修改位置 | 修改内容 | 影响范围 | 适用场景 |
|---|---|---|---|
/etc/profile 中的 JAVA_HOME |
指向新的 JDK 目录 | 整个服务器(该用户所有应用) | 全局统一升级 |
catalina.sh 中的 CATALINA_OPTS |
添加 JVM 参数 | 仅当前Tomcat应用 | 单应用隔离升级 |
setenv.sh(Tomcat 推荐方式) |
添加 JVM 参数 | 仅当前Tomcat应用 | 单应用隔离升级 |
| 普通Java应用启动脚本 | 添加 JVM 参数 | 仅当前Java进程 | 独立微服务升级 |
添加参数:
-Dhttps.protocols=TLSv1.2
4.3 方案二:修改java.security文件(全局生效,谨慎操作)
文件路径: /opt/Java/jdk1.8.0_112/jre/lib/security/java.security(用户实际路径)
修改 jdk.tls.disabledAlgorithms 行:
- 修改前:
jdk.tls.disabledAlgorithms=SSLv3, RC4, MD5withRSA, DH keySize < 768 - 修改后:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, MD5withRSA, DH keySize < 768
影响范围: 该 JDK 上所有 Java 应用。修改后需重启所有使用该 JDK 的应用。
注意事项:
- 修改后需要重启应用才能生效。
- 影响范围是整个 JDK,该 JDK 上运行的所有 Java 应用都会受影响。
- 如果同一台机器上有其他老旧应用依赖 TLSv1/TLSv1.1 通信,它们会连接失败,请提前确认。
- 如果不想动全局配置,可以在启动脚本中加
-Dhttps.protocols=TLSv1.2来达到同样的效果,影响范围更可控。
4.4 代码层排查
全局搜索命令清单:
bash
# 排查 SSLContext 硬编码
grep -rn "SSLContext.getInstance" --include="*.java" .
# 排查禁用主机名验证
grep -rn "setHostnameVerifier" --include="*.java" .
# 排查旧协议硬编码
grep -rn "TLSv1\"" --include="*.java" .
grep -rn "SSLv3" --include="*.java" .
重点排查项:
- HttpsURLConnection.setEnabledProtocols --- 确保未硬编码旧协议。
- Apache HttpClient 的 SSLConnectionSocketFactory --- 检查构造函数中的协议列表。
- RestTemplate 的自定义配置 --- 检查是否使用了自定义的 SSL 上下文。
- 自定义 HostnameVerifier --- 严禁在生产环境使用跳过证书验证的代码。
4.5 验证方法
openssl 测试命令:
bash
# TLSv1.2 连通
openssl s_client -connect <host>:<port> -tls1_2
# TLSv1.0 被拒(应该连接失败)
openssl s_client -connect <host>:<port> -tls1
# TLSv1.1 被拒(应该连接失败)
openssl s_client -connect <host>:<port> -tls1_1
启动日志检查: 确认无 SSLHandshakeException、NoSuchAlgorithmException 等异常,确认日志中打印的协议版本为 TLSv1.2。
在线工具检测: 使用 SSL Labs 等工具进行外部检测。
五、JDK 11.0.0.2 TLS处理方案
5.1 版本现状分析
- 原生支持 TLS 1.2 和 TLS 1.3(JEP 332)。
- 但 11.0.0.2 是 2019年1月发布的早期版本,默认未禁用 TLSv1.0 和 TLSv1.1(从 11.0.11 起才默认禁用)。
- TLS 1.3 实现有多个已知严重 Bug:
| Bug ID | 问题描述 | 影响 | 修复版本 |
|---|---|---|---|
| JDK-8211806 | TLS 1.3 会话恢复时不发送 SNI 扩展 | 调用 Google/BoringSSL 服务器连接失败,报 protocol_version 错误 |
11.0.3 |
| JDK-8212885 | TLS 1.3 恢复会话不保留对端证书链 | 使用 HttpClient + checkPeerName 时抛出 SSLPeerUnverifiedException |
11.0.3 |
| JDK-8213202 | TLS 1.3 会话恢复存在竞态条件 | 偶发 SSLException: Received fatal alert: internal_error |
11.0.8 |
| JDK-8214418 | HttpClient 在 TLS 1.3 下 100% CPU 死循环 | I/O Dispatcher 线程卡死在 SSLEngineImpl.wrap() |
11.0.8 |
结论:JDK 11.0.2 的 TLS 1.3 不靠谱,强烈建议不要直接使用。
5.2 方案一:升级JDK小版本到 11.0.11+(最推荐)
推荐版本: 升级到 11.0.24(当前最新 LTS 补丁版本)。
升级步骤:
bash
# 1. 下载 JDK 11.0.24(以 Adoptium/Eclipse Temurin 为例,免费商用)
# https://adoptium.net/temurin/releases/?version=11
# 2. 解压到目标目录
tar -xzf OpenJDK11U-jdk_x64_linux_hotspot_11.0.24_xxx.tar.gz -C /usr/local/
# 3. 修改环境变量(/etc/profile 或 ~/.bashrc)
export JAVA_HOME=/usr/local/jdk-11.0.24
export PATH=$JAVA_HOME/bin:$PATH
# 4. 验证版本
java -version
# 应输出: openjdk version "11.0.24" xxx
升级后影响:
| 项目 | 升级前(11.0.0.2) | 升级后(11.0.11+) |
|---|---|---|
java.security 手动禁用 TLSv1.3 |
必须手动加 | 不需要 |
java.security 手动禁用 TLSv1/1.1 |
必须手动加 | 默认已禁用 |
启动参数 -Dhttps.protocols=TLSv1.2 |
建议加 | 不需要 |
| TLS 1.3 支持 | 有严重 bug,不能用 | 稳定可用 |
application.yml |
不用动(Nginx做TLS卸载时) | 不用动 |
| Nginx 配置 | 不用动 | 不用动 |
影响范围: 仅替换 JDK 安装包,同一主版本内完全兼容,不需要改代码、不需要改配置。
升级后仍需确认的事项:
- 代码搜 SSLContext:
grep -rn "SSLContext.getInstance" --include="*.java" .,如果有SSLContext.getInstance("SSL"),改成SSLContext.getInstance("TLS")。 - 跑集成测试: 重点验证所有对外 HTTPS 调用是否正常(因为 JDK 升级后默认禁用了 TLS 1.0/1.1,如果调了老服务可能会断)。
- Boot 层 forward-headers-strategy 配置: 如果前面有 Nginx 做 TLS 卸载,确认已配置
server.forward-headers-strategy: framework。
5.3 方案二:不升级JDK,临时方案(禁用TLS 1.3仅用TLS 1.2)
适用场景: 短期内无法升级 JDK 版本。
修改 java.security 文件:
文件路径: openjdk-11.0.0.2/conf/security/java.security
修改 jdk.tls.disabledAlgorithms(同时禁用 TLSv1/1.1/1.3):
- 修改前:
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL - 修改后:
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, TLSv1.3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
这一行同时做了两件事:
- 禁用
TLSv1、TLSv1.1→ 满足安全合规,堵住旧协议漏洞 - 禁用
TLSv1.3→ 规避 11.0.0.2 的 TLS 1.3 已知 bug - 最终效果:只允许 TLS 1.2,既安全又稳定
启动参数(双重保险):
bash
java -Dhttps.protocols=TLSv1.2 \
-Djdk.tls.client.protocols=TLSv1.2 \
-jar your-app.jar
影响范围: 仅影响当前应用。
代码层修改:
java
// 修改前(在 11.0.2 上会启用有 bug 的 TLS 1.3)
SSLContext sslContext = SSLContext.getInstance("TLS");
// 修改后(明确指定 TLSv1.2)
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
5.4 JDK 11 模块化问题
javax.xml.ws.spi.Provider 缺失问题:
- 报错信息:
Error while searching for service [javax.xml.ws.spi.Provider] - 原因:
javax.xml.ws(JAX-WS)在 JDK 8 中是内置的,但从 JDK 9 起被标记为废弃,JDK 11 中被彻底移除。代码或依赖在运行时通过ServiceLoader查找javax.xml.ws.spi.Provider的实现类,但 JDK 11 里已经找不到这个类了。 - 解决方案: 在
pom.xml中添加以下依赖:
xml
<!-- JAX-WS API -->
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- JAX-WS 运行时实现(Metro) -->
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.3.5</version>
</dependency>
其他被移除的Java EE模块清单:
| 被移除的模块 | 用途 | 常见报错关键字 | 替代方案 |
|---|---|---|---|
java.xml.ws |
JAX-WS(Web Service) | javax.xml.ws |
jakarta.xml.ws-api + jaxws-rt |
java.xml.bind |
JAXB(XML 绑定) | javax.xml.bind |
jakarta.xml.bind-api + jaxb-runtime |
java.activation |
JAF(数据激活) | javax.activation |
jakarta.activation-api |
java.annotation |
通用注解 | javax.annotation |
jakarta.annotation-api |
java.transaction |
JTA(事务) | javax.transaction |
jakarta.transaction-api |
java.corba |
CORBA | javax.rmi.CORBA |
迁移至其他方案 |
5.5 证书主机名不匹配问题
报错信息:
javax.net.ssl.SSLHandshakeException: No subject alternative DNS name matching accgatewaytest.hz-ins.cn found.
原因: 升级 JDK 后安全策略变得更加严格,对证书主机名的校验也更强。在旧版本中可能被忽略的证书问题,在新版本中会被直接拦截并抛出异常。你的程序在连接目标地址时,发现对方服务器提供的 SSL 证书里并没有包含访问的域名。
解决方案(按推荐顺序):
- 联系接口提供方(首选方案): 告知他们服务器证书不包含访问的域名,请他们更新服务器配置,使用包含该域名的有效 SSL 证书。
- 检查调用地址(自查): 确认代码中调用的 URL 是否完全正确,有没有拼写错误。
- 临时方案:在代码中禁用主机名验证(仅限测试环境):
java
// 警告:以下代码会禁用主机名验证,仅用于测试!严禁在生产环境使用!
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.HttpsURLConnection;
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true; // 直接返回 true,表示信任任何主机名
}
});
5.6 升级后仍需确认的事项
- 代码搜 SSLContext:
grep -rn "SSLContext.getInstance" --include="*.java" . - 跑集成测试: 重点验证所有对外 HTTPS 调用是否正常。
- Boot 层 forward-headers-strategy 配置: 如果前面有 Nginx 做 TLS 卸载,确认已配置
server.forward-headers-strategy: framework。
5.7 验证方法
bash
# 确认 JDK 版本
java -version
# 如果已升级到 11.0.11+,验证 TLS 1.0/1.1 已被默认禁用
# 在 java.security 中检查 jdk.tls.disabledAlgorithms 是否包含 TLSv1, TLSv1.1
# 测试 TLS 1.2 是否可用
openssl s_client -connect <host>:<port> -tls1_2
# 测试 TLS 1.3 是否可用
openssl s_client -connect <host>:<port> -tls1_3
# 测试 TLS 1.0 是否已被禁用(应该连接失败)
openssl s_client -connect <host>:<port> -tls1
# 测试 TLS 1.1 是否已被禁用(应该连接失败)
openssl s_client -connect <host>:<port> -tls1_1
六、各版本TLS处理对比总览表
| JDK版本 | 推荐方案 | 是否需改代码 | 是否需改配置 | 影响范围 | 备注 |
|---|---|---|---|---|---|
| jdk1.6.0_45 | 升级小版本至 1.6.0_181/211 | 否 | 是(启动参数) | 仅当前应用 | 若无法升级则用 BouncyCastle 或 Nginx |
| jdk1.7.0_79 | JVM 启动参数 | 否 | 是(启动参数/java.security) | 仅当前应用/全局 | 优先启动参数,全局改 java.security 需谨慎 |
| jdk1.8.0_112 | 修改 java.security | 否 | 是(java.security) | 全局 | 需重启所有使用该 JDK 的应用 |
| jdk-11.0.0.2 | 升级小版本至 11.0.11+ | 否 | 否(升级后默认安全) | 仅替换 JDK | 若无法升级则禁用 TLSv1.3 仅用 TLSv1.2 |
七、Nginx TLS卸载架构说明
架构图说明
客户端 --HTTPS(TLSv1.2/1.3)--> Nginx --HTTP(明文)--> 后端应用(JDK 1.6/1.7/1.8/11)
↑
TLS配置在这里搞定
后端 JDK 不涉及
Nginx 负责 TLS 终结,后端应用仅处理明文 HTTP,彻底规避老旧 JDK 的 TLS 兼容性问题。
Nginx完整配置示例
nginx
server {
listen 443 ssl http2;
server_name example.com;
# 核心:只允许 TLS 1.2 和 1.3
ssl_protocols TLSv1.2 TLSv1.3;
# 强加密套件(前向保密 + AEAD)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# HSTS(强制浏览器只用 HTTPS)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# 会话复用(性能优化)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# 关键:透传原始请求信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
Boot层配合配置
若使用 Spring Boot,且前面有 Nginx 做 TLS 卸载,需配置:
yaml
server:
forward-headers-strategy: framework
这一行让 Spring 读取 X-Forwarded-Proto 等头信息,正确感知原始请求是 HTTPS。
验证命令
bash
# TLS 1.2 应该成功
echo | openssl s_client -connect example.com:443 -tls1_2
# TLS 1.3 应该成功(如果 OpenSSL 支持)
echo | openssl s_client -connect example.com:443 -tls1_3
# TLS 1.0 应该失败
echo | openssl s_client -connect example.com:443 -tls1
# TLS 1.1 应该失败
echo | openssl s_client -connect example.com:443 -tls1_1
Nginx版本要求
- TLS 1.3 需要 Nginx 1.13.0+ 且 OpenSSL 1.1.1+
- 用
nginx -v和openssl version检查版本 - 如果 OpenSSL 版本低于 1.1.1,TLS 1.3 无法启用,只能配到
TLSv1.2
八、代码层通用排查清单
依赖冲突排查
Hutool多版本冲突:
- 同时引入了
5.7.22,5.8.11,5.7.9,5.8.25四个版本 - 排查命令:
mvn dependency:tree | grep hutool - 后果: 代码编译时可能引用的是新版本的 API,但运行时加载的是旧版本,导致核心工具类在生产环境随机报错
- 解决: 使用
<dependencyManagement>统一管理版本,只保留最新稳定版
BouncyCastle加密库冲突:
- 同时存在
1.66和1.68版本 - 排查命令:
mvn dependency:tree | grep bouncycastle - 后果: 安全加密库版本不一致会导致 SM2/SM3/SM4 加解密失败,签名验证不通过
- 解决: 统一使用
bcprov-jdk15on的单一版本
Fastjson2版本不一致:
fastjson2是2.0.60,但fastjson2-extension-spring5是2.0.43- 排查命令:
mvn dependency:tree | grep fastjson - 后果: 扩展包依赖核心包的具体内部实现,版本不匹配极易导致 JSON 序列化/反序列化失败
- 解决: 统一版本号
老旧组件排查
| 组件 | 版本 | 问题 | 建议 |
|---|---|---|---|
| XFire | 1.2.6 | 停止维护超10年,强依赖 Java EE 内部 API,JDK 11 中已移除 | 迁移至 Apache CXF 或 Spring-WS |
| Axis | 1.4 | 发布于2006年,不原生支持 JDK 11 模块化路径,存在已知安全漏洞 | 升级至 Axis2 或 CXF |
安全组件版本排查
| 组件 | 当前版本 | 问题 | 建议版本 |
|---|---|---|---|
| Logstash Logback Encoder | 5.0 | 对 Logback 1.2+ 和 JDK 11 支持存在 Bug(MDC 数据丢失、异步日志死锁) | 6.x 或 7.x |
| EasyExcel | 2.0.5 | 大数据量写入时存在内存溢出风险,JDK 11 适配不完善 | 3.x+ |
| Druid | 1.1.9 | 存在 SQL 注入绕过等安全漏洞 | 1.2.x+ |
反编译代码风险
- 检查项目中是否存在反编译的 class 文件(注释中可能有
JDK11后将此jar里的class文件反编译放到项目内容使用) - 反编译代码通常丢失内部类结构、泛型信息和注释,且可能包含语法错误
- 一旦原 Jar 包修复了 Bug,无法同步,且这部分代码在 JDK 11 下运行时可能因为反射权限问题随时挂掉
具体 grep 命令清单
bash
# 排查 SSLContext 硬编码
grep -rn "SSLContext.getInstance" --include="*.java" .
# 排查禁用主机名验证
grep -rn "setHostnameVerifier" --include="*.java" .
# 排查旧协议硬编码
grep -rn "TLSv1\"" --include="*.java" .
grep -rn "SSLv3" --include="*.java" .
# 排查自定义 SSL 配置
grep -rn "SSLContext" --include="*.java" .
grep -rn "SSLSocketFactory" --include="*.java" .
grep -rn "HostnameVerifier" --include="*.java" .
九、验证方法总览
openssl 命令清单
bash
# 测试 TLSv1.2 连通性
openssl s_client -connect <host>:<port> -tls1_2
# 测试 TLSv1.3 连通性
openssl s_client -connect <host>:<port> -tls1_3
# 确认 TLSv1.0 被拒绝(应该连接失败)
openssl s_client -connect <host>:<port> -tls1
# 确认 TLSv1.1 被拒绝(应该连接失败)
openssl s_client -connect <host>:<port> -tls1_1
启动日志检查
-
检查应用启动日志,确认无
SSLHandshakeException、NoSuchAlgorithmException等异常。 -
确认日志中打印的协议版本为 TLSv1.2(或 TLSv1.3)。
-
Spring Boot 启动日志中应看到类似输出:
Tomcat initialized with port(s): 8443 (https) Protocol: TLSv1.3 Cipher: TLS_AES_256_GCM_SHA384
自动化测试
- 编写集成测试,覆盖所有对外 HTTPS 调用链路。
- 重点验证:OAuth 令牌获取、第三方 API 调用、短信/邮件服务调用等。
外部工具检测
- 使用 SSL Labs 进行全面检测。
- 使用
curl -v --tlsv1.2 https://example.com快速验证。
性能监控
- 升级后监控 CPU 使用率和峰值。
- 监控内存占用和 GC 频率。
- 监控错误率和异常日志。
- 监控接口响应时间和吞吐量。
十、注意事项与风险提示
Nginx TLS卸载架构注意事项
- 如果服务前面有 Nginx/网关做 TLS 卸载,TLS 协议配置主要在 Nginx 层做,Boot 层只需保证内部通信安全即可。
- Boot 服务本身不需要配置 SSL,监听普通 HTTP 端口即可。
- 如果是云厂商的负载均衡/网关(如阿里云 SLB、AWS ALB),TLS 协议配置在云控制台操作,不需要改 Nginx。
证书问题
- 升级 JDK 后安全策略更严格,旧版本中被忽略的证书问题会直接暴露。
- 确保所有对外调用的服务证书有效且域名匹配。
- 定期更新 cacerts 中的根证书。
渐进式升级策略
- 第一阶段: 1% 流量走新 JDK 节点
- 第二阶段: 5% 流量走新 JDK 节点
- 第三阶段: 100% 流量切换
- 全程监控: CPU、内存、错误率、响应时间
回滚预案
- 使用 Git 标签标记升级前的代码版本。
- 保留旧版本 JDK 的构建配置和安装包。
- 在 CI/CD 中配置多 JDK 版本构建。
- 保留旧版本的构建产物。
长期规划
- JDK 11 LTS 支持至 2026年9月(Oracle 商业支持),OpenJDK 社区持续维护。
- 下一步升级目标: JDK 17(LTS 支持至 2029年)。
- 分阶段升级策略:JDK 11 → JDK 17 → JDK 21(如需要)。
- 每季度运行
mvn dependency:tree或jdeprscan检查依赖兼容性。 - 优先升级到支持目标 JDK 的最新版本。