Spring Boot内嵌Jetty服务器TLSv1握手失败:无通用密码套件
Spring Boot内嵌Jetty服务器TLSv1握手失败解决方案
问题场景
运行在内嵌Jetty服务器上的Spring Boot REST API,被仅支持TLSv1的Java客户端调用时,出现以下错误:
fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common on spring boot embedded jetty server
客户端侧网络调试日志:
pool-2-thread-1, READ: TLSv1 Alert, length = 2 pool-2-thread-1, RECV TLSv1.2 ALERT: fatal, handshake_failure pool-2-thread-1, called closeSocket() pool-2-thread-1, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
尝试在application.properties中配置启用TLSv1,但未生效:
server.ssl.enabled=true server.ssl.enabled-protocols=TLSv1 server.ssl.disabled-protocols=TLSv1.2 server.ssl.protocol=TLS server.ssl.disabled-cipher-suites= server.ssl.key-store-type=JKS server.ssl.keystore=keystore_path server.ssl.key-store-password=password server.ssl.key-alias=alias_name trust.store=truststore_path trust.store.password=password
服务器侧网络调试日志:
Using SSLEngineImpl. Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 Allow unsafe renegotiation: false Allow legacy hello messages: true Is initial handshake: true Is secure renegotiation: false Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 for TLSv1 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 [Raw read]: length = 5 0000: 16 03 01 00 63 ....c [Raw read]: length = 99 0000: 01 00 00 5F 03 01 63 AE A7 33 45 C3 64 64 C9 BD ..._..c..3E.dd.. 0010: BD 19 74 DA 21 29 8C 62 6B FB 32 67 86 C6 9A E0 ..t.!).bk.2g.... 0020: 91 04 3C 85 84 83 00 00 1E 00 FF C0 0A C0 14 00 ..<............. 0030: 35 C0 05 C0 0F 00 39 00 38 C0 09 C0 13 00 2F C0 5.....9.8...../. 0040: 04 C0 0E 00 33 00 32 01 00 00 18 00 0A 00 0A 00 ....3.2......... 0050: 08 00 17 00 18 00 19 00 16 00 0B 00 02 01 00 00 ................ 0060: 17 00 00 ... qtp1209702763-21, READ: TLSv1 Handshake, length = 99 *** ClientHello, TLSv1 RandomCookie: GMT: 1655547699 bytes = { 69, 195, 100, 100, 201, 189, 189, 25, 116, 218, 33, 41, 140, 98, 107, 251, 50, 103, 134, 198, 154, 224, 145, 4, 60, 133, 132, 131 } Session ID: {} Cipher Suites: [TLS_EMPTY_RENEGOTIATION_INFO_SCSV, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA] Compression Methods: { 0 } Extension elliptic_curves, curve names: {secp256r1, secp384r1, secp521r1, secp256k1} Extension ec_point_formats, formats: [uncompressed] Unsupported extension type_23, data: *** [read] MD5 and SHA1 hashes: len = 99 0000: 01 00 00 5F 03 01 63 AE A7 33 45 C3 64 64 C9 BD ..._..c..3E.dd.. 0010: BD 19 74 DA 21 29 8C 62 6B FB 32 67 86 C6 9A E0 ..t.!).bk.2g.... 0020: 91 04 3C 85 84 83 00 00 1E 00 FF C0 0A C0 14 00 ..<............. 0030: 35 C0 05 C0 0F 00 39 00 38 C0 09 C0 13 00 2F C0 5.....9.8...../. 0040: 04 C0 0E 00 33 00 32 01 00 00 18 00 0A 00 0A 00 ....3.2......... 0050: 08 00 17 00 18 00 19 00 16 00 0B 00 02 01 00 00 ................ 0060: 17 00 00 ... %% Initialized: [Session-3, SSL_NULL_WITH_NULL_NULL] qtp1209702763-21, fatal error: 40: no cipher suites in common javax.net.ssl.SSLHandshakeException: no cipher suites in common %% Invalidated: [Session-3, SSL_NULL_WITH_NULL_NULL] qtp1209702763-21, SEND TLSv1 ALERT: fatal, description = handshake_failure qtp1209702763-21, WRITE: TLSv1 Alert, length = 2 qtp1209702763-21, fatal: engine already closed. Rethrowing javax.net.ssl.SSLHandshakeException: no cipher suites in common qtp1209702763-21, called closeOutbound() qtp1209702763-21, closeOutboundInternal()
问题分析
从服务器日志可以看出:
- 客户端发送的
ClientHello明确使用TLSv1,且支持的密码套件包含TLS_RSA_WITH_AES_256_CBC_SHA、TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA等。 - 服务器日志中标记这些客户端支持的密码套件为
Ignoring unavailable cipher suite,说明服务器未启用这些套件,导致双方没有共同的密码套件,握手失败。 - 仅配置启用TLSv1协议不足以解决问题,还需要确保服务器启用客户端支持的密码套件。
解决方案
1. 修改Spring Boot配置文件,指定支持的密码套件
更新application.properties,添加客户端支持的密码套件列表,覆盖默认配置:
server.ssl.enabled=true server.ssl.enabled-protocols=TLSv1 server.ssl.disabled-protocols=TLSv1.2 server.ssl.protocol=TLS # 指定客户端支持的密码套件 server.ssl.cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA server.ssl.key-store-type=JKS server.ssl.keystore=keystore_path server.ssl.key-store-password=password server.ssl.key-alias=alias_name trust.store=truststore_path trust.store.password=password
2. 调整JVM参数(针对高版本JDK)
如果使用Java 8u161及以上版本,JDK默认禁用了部分旧版TLSv1密码套件,需要通过JVM参数启用:
-Djdk.tls.disabledAlgorithms="SSLv3, RC4, MD5withRSA, DH keySize < 1024, EC keySize < 224"
注意:不要清空所有禁用项,仅移除与TLSv1和目标密码套件相关的禁用规则,比如如果默认禁用了AES_256_CBC_SHA,需要从列表中删除。
3. 编程方式自定义Jetty SSL配置(如果配置文件不生效)
创建配置类,手动配置Jetty的SslContextFactory,确保启用TLSv1和对应密码套件:
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JettySslConfig { @Bean public WebServerFactoryCustomizer<JettyServletWebServerFactory> jettySslCustomizer() { return factory -> { factory.addServerCustomizers(server -> { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); // 配置证书信息 sslContextFactory.setKeyStorePath("keystore_path"); sslContextFactory.setKeyStorePassword("password"); sslContextFactory.setKeyAlias("alias_name"); sslContextFactory.setKeyStoreType("JKS"); // 启用TLSv1 sslContextFactory.setIncludeProtocols("TLSv1"); // 指定支持的密码套件
相关产品推荐
相关产品推荐

