我在服务上遇到旧的HTTP transport error: javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
错误,因为我需要禁用SNI握手。我不希望在其他地方使用System.setProperty("jsse.enableSNIExtension", "false");
进行全局操作,http绑定使用sun.net.www.protocol.https.HttpsClient
,我似乎无法改变它。我尝试过像https://erikwramner.wordpress.com/2013/03/27/trust-self-signed-ssl-certificates-and-skip-host-name-verification-with-jax-ws/
我已经设置了自定义SSLSocketFactories和HostnameVerifiers,如下所示:
BindingProvider bp = (BindingProvider) webServicePort;
Map requestContext = bp.getRequestContext();
requestContext.put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", getTrustingSSLSocketFactory());
requestContext.put("com.sun.xml.ws.transport.https.client.SSLSocketFactory", getTrustingSSLSocketFactory());
final NaiveHostnameVerifier naiveHostnameVerifier = new NaiveHostnameVerifier();
requestContext.put("com.sun.xml.internal.ws.transport.https.client.hostname.verifier", naiveHostnameVerifier);
requestContext.put("com.sun.xml.ws.transport.https.client.hostname.verifier", naiveHostnameVerifier);
这简直没有效果,因为http客户端似乎不尊重这些属性。
任何提示赞赏!
答案 0 :(得分:0)
我遇到同样的问题......将java.net.debug
系统属性激活为all
我已经确定问题是服务器在某种程度上配置错误(它接受协商SNI握手,但不是接受我们传递的名称 - 我们用来连接它的名称)这通常是服务器配置错误,因为服务器以unrecognized_name
响应自己的名称,这导致客户端断开连接(java维护者假设服务器必须知道我们用来连接它的名称)。
这对HostnameVerifier
个实例来说不是问题,因为客户端尚未验证服务器名称(这是主机名验证器),如握手中所示(实际上,客户端中止了SERVER HELLO握手后的连接)。警告之后客户端正在断开连接(SSL协议的错误112
)因此,客户端握手应该配置为继续此类事件。到目前为止,服务器正在删除连接,并且没有在本地进行服务器主机名验证,我们只需要在服务器的警告消息之后继续进行初始握手。由于目前我没有SSLSocketImpl
类的源代码,我无法进一步研究这个问题,所以一旦得到源代码并测试是否可能,我可能会收到更多信息。 strong>还有另一种方法来设置SSLSocketImpl
实例的属性,而不是从系统属性配置中获取它。
主要让人困惑的是SNI握手用于允许服务器在握手中使用多域(或虚拟主机),因此它通常配备有正确设计的证书(失败的),具有备用的dns条目客户端使用了名字。由于名称列表为空或仅具有主机的规范名称,因此客户端使用的名称无法识别并返回给客户端(应将其解释为警告),但是java的默认实现,具体取决于系统的属性,继续或丢弃连接。应该启用另一种在每个连接的基础上配置它的方法,但我还不知道。
在查看openjdk源代码(sorrry,但Oracle的实现没有发布ClientHandshaker
类的代码之后,内部字段enableSNIextension
(private
,{{只有一个地方1}}和final
字段,因此在初始化类时没有机会更改它。这使得算法只有一种方式,一旦你加载了static
类。< / p>
由于仅检查了值以在初始ClientHandshaker
消息中包含可选的SNI扩展名,因此对其的答案是固定的,因此在默认为helloclient
后,绕过它的唯一方法是strong>是在服务器上配置适当的证书,包括备用服务器名称列表中的正确服务器名称,或者在服务器中根本禁用SNI协商。您可以查看openJDK here的实现源。
有一个更复杂的方法,它允许你只为一个连接禁用SNI扩展,并暗示使用这样的true
派生工厂:
SSLConnectionSocketFactory
正如您将看到的,这使用普通地址连接到套接字,因此没有使用主机名传递给远程服务器,从而无法有效地禁止SNI。它有一个缺点,您需要使用匹配所有主机名验证程序(如上所示)构造它,因为它将在验证服务器名称时失败(这是在SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(
ctx, // specific context, or default
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER // important, see the text below
) {
@Override
public Socket connectSocket( int connectTimeout,
Socket socket,
HttpHost host,
InetSocketAddress remoteAddress,
InetSocketAddress localAddress,
HttpContext context )
throws IOException {
return super.connectSocket( connectTimeout,
socket,
new HttpHost( remoteAddress.getAddress(),
remoteAddress.getPort() ),
remoteAddress,
localAddress,
context );
}
};
CloseableHttpClient cl =
HttpClients.custom()
.setSSLSocketFactory( sslsf )
.build();
超类内部完成初始握手之后进行的,并且失败,因为使用的地址与服务器在其证书中使用的主机名不匹配)