Spring 云网关:javax.net.ssl.SSLHandshakeException:没有可用的身份验证方案

Spring Cloud Gateway: javax.net.ssl.SSLHandshakeException: No available authentication scheme

我正在尝试配置从 Web 浏览器到 Spring 云网关的 TLS 连接,但出现以下异常:

2020-10-18 12:46:49.119  WARN 7652 --- [ctor-http-nio-5] .s.ApplicationProtocolNegotiationHandler : [id: 0x6fa1f964, L:/0:0:0:0:0:0:0:1:443 - R:/0:0:0:0:0:0:0:1:50229] Failed to select the application-level protocol:

javax.net.ssl.SSLHandshakeException: No available authentication scheme
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:313) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:269) ~[na:na]
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:260) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.onProduceCertificate(CertificateMessage.java:955) ~[na:na]
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateProducer.produce(CertificateMessage.java:944) ~[na:na]
    at java.base/sun.security.ssl.SSLHandshake.produce(SSLHandshake.java:436) ~[na:na]
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.goServerHello(ClientHello.java:1234) ~[na:na]
    at java.base/sun.security.ssl.ClientHello$T13ClientHelloConsumer.consume(ClientHello.java:1170) ~[na:na]
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.onClientHello(ClientHello.java:852) ~[na:na]
    at java.base/sun.security.ssl.ClientHello$ClientHelloConsumer.consume(ClientHello.java:813) ~[na:na]
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392) ~[na:na]
    at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1048) ~[na:na]
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:770) ~[na:na]
    at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:995) ~[na:na]
    at io.netty.handler.ssl.SslHandler.runAllDelegatedTasks(SslHandler.java:1550) ~[netty-handler-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.ssl.SslHandler.runDelegatedTasks(SslHandler.java:1564) ~[netty-handler-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1448) ~[netty-handler-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1275) ~[netty-handler-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1322) ~[netty-handler-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501) ~[netty-codec-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440) ~[netty-codec-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.52.Final.jar:4.1.52.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.52.Final.jar:4.1.52.Final]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

我了解到当您使用 JDK11 并且证书使用 DSA 算法时,可能会发生此错误。但就我而言,CA 和服务器证书是使用 RSA 2048 位和 X.509 格式生成的,并使用此密钥工具命令导入到密钥库中:

$ keytool -import -trustcacerts -alias root -file certs/foo_com.ca-bundle -keystore keystore.p12 -storepass 123456
$ keytool -import -trustcacerts -alias tomcat -file certs/foo_com.crt -keystore keystore.p12 -storepass 123456

application.yml

server:
  forward-headers-strategy: framework
  ssl:
    enabled: true
    key-alias: tomcat
    key-store-password: 123456
    key-store: classpath:keystore.p12
    key-store-type: PKCS12
    trust-store: classpath:keystore.p12
    trust-store-password: 123456
    trust-store-type: PKCS12
  port: 443
  http2:
    enabled: true

我想要从浏览器到网关的 TLS,但从网关到微服务保持一个普通的 http 请求。所以我没有在微服务中配置任何密钥库,但也许这也是必要的?

需要说明的是,此异常可能是由于使用 DSA 证书(和密钥)WITH TLS1.3引起的。 Java 11 up 实现了 TLS1.3,(向后移植)8u261 也是如此。然而,11 以及包括 8u261 到 10 在内的 8 个,支持较低的协议 TLS 1.0-1.2,它允许 DSA-based 密码套件——如果客户端(也)支持并提供它们,这种情况现在比以前更罕见是,但可能取决于您使用的特定浏览器。

但是,任何 SSL/TLS 服务器 始终需要带有 PRIVATEKEY 的证书,并且通常具有适用的链或中间证书以及可选的根证书。您提供了一个显然是服务器证书和一个名为 'bundle' 的文件,您显然认为它是或包含根目录。名为 'bundle' 的文件 通常 不止一个证书,尽管不一定,并且您没有显示任何数据来确定其中的内容,以及它是否确实是或包含一个根。 SSL/TLS 实际上并不需要 root,尽管 Java 鼓励您包括它,因为 Java 除了 [=41= 之外,加密还用于其他用途] 和其他一些东西可能需要根。 keytool -importcert-import 是缩写)在创建新的 (trustedCert) 条目时实际上不会处理捆绑包,它只会采用第一个证书并忽略任何其他证书。 (这与它导入到 existing privateKey 条目时的情况不同。)您没有提供私钥,也没有说明您是否拥有它,所以这个 'keystore' 不能用于 SSL/TLS 服务器。 (Java 使用相同的 'keystore' 文件格式 - 和内部 API - 对于实际上包含私钥的密钥库和实际上只包含证书而不包含私钥的信任库。对于服务器,你需要前者。)

由于您根本没有给出任何描述 'CA and Server certs were generated' 不可能就您应该做什么提供任何有用的建议,除了您必须提供私钥和证书的一般建议Java 可用的形式,即密钥库。注意 PKCS12 不是 Java 支持的唯一一种密钥库,尽管它是最具互操作性的,并且对于 Java 9 是默认的,因此通常更可取。

对于出站 连接(您的应用程序是客户端)您需要一个密钥库(它可能与服务器的分开或结合) 仅当您使用客户端身份验证时,即客户端证书,这不是 SSL/TLS 中的默认设置。此选择由服务器控制——此处为 'Microservices'——如果(且仅当)他们完全使用 SSL/TLS,因此您需要了解或测试您要使用的一个。使用带客户端身份验证的 TLS、无客户端身份验证的 TLS 或不使用 TLS 是否可取或合适甚至有必要取决于许多不在您的 Q 中的因素,并且它可能已经决定,因此可能不是您的选择。但这绝不会影响您 ('Gateway') 从浏览器接收和接受的连接。