使用sun.net以编程方式暂时禁用JAX-RS客户端中的SNI检查。**。HttpsClient

时间:2016-03-15 12:47:02

标签: java jax-ws

我在服务上遇到旧的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客户端似乎不尊重这些属性。

任何提示赞赏!

1 个答案:

答案 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类的代码之后,内部字段enableSNIextensionprivate,{{只有一个地方1}}和final字段,因此在初始化类时没有机会更改它。这使得算法只有一种方式,一旦你加载了static类。< / p>

由于仅检查了值以在初始ClientHandshaker消息中包含可选的SNI扩展名,因此对其的答案是固定的,因此在默认为helloclient后,绕过它的唯一方法是strong>是在服务器上配置适当的证书,包括备用服务器名称列表中的正确服务器名称,或者在服务器中根本禁用SNI协商。您可以查看openJDK here的实现源。

编辑2

有一个更复杂的方法,它允许你只为一个连接禁用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(); 超类内部完成初始握手之后进行的,并且失败,因为使用的地址与服务器在其证书中使用的主机名不匹配)