适用:Spring Boot 2.x + JDK 8 + Tencent Kona SMSuite + Docker 离线部署 + Nginx-gm 前置
场景:Java 仅暴露 8443 TLCP ,奇安信等国密浏览器经 443 → Nginx → 8443 访问 API。
一、目标架构
text
奇安信浏览器 --[TLCP/国密 HTTPS]--> Nginx-gm:443 --[内网 HTTPS]--> Java Kona:8443/api/
- 对外 :Nginx-gm(GmSSL/OCL)终结国密 HTTPS,用户访问
https://10.x.x.x/api/ - 对内 :Spring Boot 嵌入 Tomcat,只监听 8443 ,协议 TLCPv1.1,证书为 SM2 双证(sign + enc)
- 约束:不开放明文 HTTP(8899),8443 必须真正跑 TLS/TLCP
二、现象回顾(按阶段)
阶段 A:ClientHello 发出,服务端毫无反应
- Kona 探测脚本 / 浏览器:能建 TCP,ClientHello 已写出
docker logs:没有createSSLEngine、没有 ServerHellonc -zv 127.0.0.1 8443成功(容易误判为「TLS 已通」)
阶段 B:启动日志「看起来正常」
text
handlerSSLEnabled=true
Kona TLCP 诊断: Connector 数量=2 (port=0 + port=8443)
defaultCiphers=4
仍无握手日志 → 属性为 true,不等于 TLS 在工作。
阶段 C:主连接器修复后,探测脚本 NPE
服务端已返回 ServerHello、双证、ServerKeyExchange,套件为 TLCP_ECDHE_SM4_CBC_SM3。
客户端报错:
text
X509KeyManager class: DummyX509KeyManager
NullPointerException at SM2EKeyExchange$SM2EPossession.<init>
→ 8443 服务已通 ,失败在探测脚本未配置 keystore。
阶段 D:奇安信浏览器登录成功
更新探测脚本 + 确认 Nginx 上游后,系统跑通。
三、踩过的坑(避免重复)
| 误区 | 事实 |
|---|---|
用 WSL gmssl 测 8443 |
GmSSL 3.1 无 TLCP 客户端 (无 s_client),测不出 Java TLCP |
| 用 Chrome/Edge 直连 8443 | 不支持 TLCP,会一直转圈 |
nc 通 = TLS 通 |
nc 只证明 TCP,TLCP 要看到 ServerHello |
handlerSSLEnabled=true 就够 |
附加 Connector 下可能 只监听 TCP、不跑 TLS |
| 探测只配 truststore | ECDHE 套件要求客户端有 SM2 私钥(keystore) |
Nginx 上游写 GMTLSv1.1 |
很多 nginx-gm 不支持 ,现网 443 用 TLSv1.2 + 国密 cipher |
docker run --add-host=host-gateway |
老版本 Docker 报错,本项目 env 用具体 IP,可去掉 |
四、根因 1:附加 Connector 导致 8443 不做 TLS(最关键)
错误写法(早期)
java
tomcat.setPort(-1); // 关掉默认 HTTP
tomcat.addAdditionalTomcatConnectors(createHttpsConnector(ssl)); // 再挂 8443
Spring Boot 文档说明:addAdditionalTomcatConnectors 加的 Connector 不会套用工厂里那套 Connector 定制,在部分环境下会出现:
- 8443 TCP 可连
- 不创建 SSLEngine (日志无
createSSLEngine) - 客户端 ClientHello 发出后 永远等不到 ServerHello
启动诊断若看到 2 个 Connector (port=0 + port=8443),就是典型症状。
正确写法(最终)
生产 kona.ssl.http-enabled=false 时:
java
tomcat.setPort(ssl.getHttpsPort()); // 8443 作为主端口
tomcat.addConnectorCustomizers(connector -> configureKonaTlsConnector(connector, ssl));
// 不再 addAdditionalTomcatConnectors + setPort(-1)
并在 configureKonaTlsConnector 中:
scheme=https、SSLEnabled=true、sslImplementationName=KonaSSLImplAbstractHttp11Protocol.setSSLEnabled(true)(仅 setProperty 可能不生效)SSLHostConfig+ SM2 双证 keystore(单条目、不绑双别名,与 Kona 官方 Tomcat demo 一致)KonaSSLContext使用TLCPv1.1创建SSLContext,握手时启用 TLCP 套件
验收
启动后:
text
Kona TLCP 主连接器定制: port=8443
Kona TLCP 诊断: Tomcat Connector 数量=1
connector port=8443, scheme=https, handlerSSLEnabled=true
探测时:
text
KonaSSLContext.createSSLEngine 调用 #1
五、根因 2:探测脚本缺客户端 keystore(ECDHE)
服务端选中 TLCP_ECDHE_SM4_CBC_SM3 后,客户端必须完成 SM2 密钥交换 。
若 SSLContext.init(null, trustManagers, null),Kona 使用 DummyX509KeyManager,会在 SM2EPossession 处 NPE。
修复
与业务里 gmRestTemplate 一致,探测也要加载 keystore:
java
KeyManagerFactory kmf = KeyManagerFactory.getInstance("NewSunX509", "Kona");
kmf.init(keyStore, password);
SSLContext ctx = SSLContext.getInstance("TLCPv1.1", "Kona");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
脚本:scripts/tlcp-connect-probe.sh 会自动解析同目录的 ssl/keystore.p12。
六、配置与部署要点
application / docker
yaml
kona:
ssl:
enabled: true
http-enabled: false # 仅 8443,不开 8899
https-port: 8443
protocol: TLCPv1.1
key-store: file:/ssp/ssl/keystore.p12
trust-store: file:/ssp/ssl/truststore.p12
sign-alias: sign
enc-alias: enc
切勿 同时配置 server.ssl.*,会与 Kona 自定义 Connector 冲突。
证书
- 使用
scripts/regenerate-kona-p12.ps1生成国密 PKCS12(HmacPBESM3) - 容器挂载:
./ssl→/ssp/ssl - 密码与
env/docker.env一致
离线部署
bash
mvn clean package -DskipTests
./scripts/offline-pack.sh # 含 webapi.tar、kona-deps、脚本
# 服务器
./server-run.sh
老 Docker 去掉 --add-host=host.docker.internal:host-gateway(脚本已默认不添加)。
七、推荐验证步骤(2 分钟)
服务器本机:
bash
# 1. 架构
docker logs webapi 2>&1 | grep 'Kona TLCP 诊断'
# 2. 握手(需 keystore + truststore)
bash scripts/tlcp-connect-probe.sh 127.0.0.1 8443 ssl/truststore.p12 '你的密码'
# 3. 浏览器
# https://服务器IP/api/ (走 Nginx 443,奇安信)
通过标准:
- Connector 数量 = 1 ,port = 8443
- 探测输出:
握手成功: protocol=TLCPv1.1 - 奇安信可登录系统
八、Nginx 上游(与 Java 8443 对接)
对外 443 保持现网(示例):
nginx
ssl_protocols TLSv1.2;
ssl_ciphers ECC-SM4-CBC-SM3:ECDHE-SM2-WITH-SM4-SM3:ECDHE-SM2-WITH-SM4-GCM-SM3;
/api/ 反代到本机 Java(不要用外网 IP 绕圈):
nginx
location /api/ {
proxy_pass https://127.0.0.1:8443/api/;
proxy_ssl_verify off;
proxy_ssl_protocols TLSv1.2;
proxy_ssl_ciphers ECDHE-SM2-WITH-SM4-SM3:ECC-SM4-CBC-SM3:ECDHE-SM2-WITH-SM4-GCM-SM3;
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;
}
不要 写 proxy_ssl_protocols GMTLSv1.1(多数编译版本不支持)。
九、关键代码位置(本仓库)
| 文件 | 作用 |
|---|---|
KonaTomcatSslConfiguration.java |
8443 主连接器 + Kona SSLImpl |
KonaTomcatSslStartupDiagnostics.java |
启动打印 Connector / handlerSSLEnabled |
scripts/tlcp-connect-probe.sh |
Kona TLCP 客户端探测(trust + key) |
scripts/server-tlcp-handshake-test.sh |
服务器一键验收 |
scripts/run-webapi-container.sh |
Docker 启动(兼容老 Docker) |
docs/tlcp-gmssl-kona-solution.md |
方案说明 |
十、排障决策树(可收藏)
text
TCP 不通 → 防火墙 / docker -p / 容器未启动
TCP 通、无 ServerHello、无 createSSLEngine → 附加 Connector 改主连接器
有 ServerHello、探测 NPE DummyX509KeyManager → 探测/客户端补 keystore
探测通、浏览器不通 → Nginx 上游协议/cipher/地址(127.0.0.1:8443)
十一、总结
- 8443 国密 HTTPS 的核心 :不是「多挂一个 HTTPS 端口」,而是让 8443 成为 Tomcat 主 Connector 并正确启用 Kona TLS。
- 日志比工具重要 :
createSSLEngine是否出现,比nc更能说明问题。 - 探测要配对:truststore 验服务端,keystore 做 ECDHE;两者缺一都会在不同阶段失败。
- 本次最终未采用明文 HTTP 降级,安全目标与奇安信登录均已满足。