Spring Cloud Gateway:javax.net.ssl.SSLHandshakeException:没有可用的身份验证方案

huangapple go评论81阅读模式
英文:

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

问题

以下是翻译好的内容:

我正在尝试从Web浏览器配置到Spring Cloud网关的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] 选择应用级协议时失败:

javax.net.ssl.SSLHandshakeException: 没有可用的认证方案
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
    ...

我阅读到在使用JDK 11且证书采用DSA算法时可能会出现此错误。但在我的情况下,CA和服务器证书是使用RSA 2048位和X.509格式生成的,并使用以下keytool命令导入到密钥库中:

$ 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请求。因此,我在微服务中没有配置任何密钥库,但也许这也是必要的吗?

英文:

I am trying to configure a TLS connection from web browser to a Spring Cloud Gateway but I get the following exception:

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$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.52.Final.jar:4.1.52.Final]
	at io.netty.util.internal.ThreadExecutorMap$2.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]

I read this error may happens when you are using JDK 11 and certificates are with DSA algorithm. But in my case, CA and Server certs were generated with RSA 2048 bits and format X.509 and imported into keystore using this keytool commands:

$ 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

Spring Cloud Gateway:javax.net.ssl.SSLHandshakeException:没有可用的身份验证方案

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

I would like have TLS from the browser to the Gateway but from Gateway to Microservices keep a plain http request. So I did not configure any keystore in the microservices but maybe this is necessary too?

答案1

得分: 2

请注意,此异常可能是由使用带有TLS 1.3的DSA证书(和密钥)引起的。Java 11及更高版本实现了TLS 1.3,8u261也是如此。然而,从11版本开始,包括8u261在内的8版本到10版本,仍支持较低的协议TLS 1.0-1.2,这些协议允许使用基于DSA的密码套件,前提是客户端也支持并提供这些密码套件,尽管这种情况现在比过去变得更加罕见,但可能取决于您使用的特定浏览器。

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

由于您根本没有说明'CA和服务器证书是如何生成的',因此无法就您应该采取什么措施提供任何有用的建议,除非通用建议是您必须以Java可用的形式提供私钥和证书,即密钥库的形式。请注意,PKCS12并不是Java支持的唯一一种密钥库类型,尽管它是最具互操作性的,对于Java 9及更高版本而言,它是默认的,并且通常是首选。

对于出站连接(您的应用程序是客户端),仅当您使用客户端身份验证(也称为客户端证书)时,您才需要一个密钥库(可能与服务器的密钥库分开或合并)。这在SSL/TLS中不是默认设置。这个选择由服务器(在这里是'Microservices')控制,只有在它们实际上使用SSL/TLS的情况下才受到影响,因此您需要了解或测试您想要使用的服务器。是否值得、是否适当,甚至是否有必要使用带有客户端认证的TLS,不带客户端认证的TLS或不使用TLS,取决于许多您问题中未涉及的因素,而且这可能已经决定,因此可能不是您的选择。但无论如何,在任何情况下,这都不会影响您从浏览器接收和接受的连接('Gateway')。

英文:

To be clear, this exception can be caused by using a DSA certificate (and key) WITH TLS1.3. Java 11 up implements TLS1.3, and so does (backported) 8u261. However, 11 up as well as 8 including 8u261 through 10, supports lower protocols TLS 1.0-1.2 which do allow DSA-based ciphersuites -- if the client (also) supports and offers them, which is becoming rarer now than it used to be, but may depend on which specific browser(s) you are using.

However, any SSL/TLS server always needs certificate WITH PRIVATEKEY and usually with applicable chain or intermediate cert(s) and optionally root cert. You provided what is apparently a server cert and a file named 'bundle' which you apparently believe is or contains a root. A file named 'bundle' is typically more than one cert, although not necessarily and you do not show any data to determine what is in it, and whether it actually is or contains a root. SSL/TLS does not actually require the root, although Java encourages you to include it because Java crypto is used for other things besides SSL/TLS and some other things may need the root. keytool -importcert (for which -import is an abbreviation) when creating a new (trustedCert) entry will not actually handle a bundle, it will take only the first cert and ignore any others. (This differs from the case when it imports to an existing privateKey entry.) You did not provide the privatekey, and give no indication whether you have it, so this 'keystore' cannot be used for an SSL/TLS server. (Java uses the same 'keystore' file formats -- and internal API -- for both keystores which actually contain privatekeys, and truststores which actually contain only certs and not privatekeys. For a server you need the former.)

Since you didn't give any description at all how 'CA and Server certs were generated' it is impossible to give any useful advice on what you should do, other than the generic advice that you must provide the privatekey AND cert in a form usable by Java, i.e. a keystore. Note PKCS12 is not the only kind of keystore supported by Java, although it is the most interoperable, and for Java 9 up the default, and thus usually preferable.

For outbound connections (your application is the client) you need a keystore (which might be seperate from or combined with the server's) only if you use client authentication, aka client certificate, which is not the default in SSL/TLS. This choice is controlled by the server(s) -- here the 'Microservices' -- if (and only if) they use SSL/TLS at all, so you would need to find out about, or test, the one(s) you want to use. Whether it is desirable or appropriate or even necessary to use TLS with client auth, TLS without client auth, or no TLS depends on numerous factors not in your Q, and it may already have been decided so it may not be your choice. But in no case does this affect the connections you ('Gateway') receive and accept from browsers.

huangapple
  • 本文由 发表于 2020年10月18日 19:15:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/64412640.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定