jdk1.8.0未发送扩展服务器名称(SNI扩展),但jdk1.7.0发送了。

23

我已经使用ApacheCXF(v3.0.4)实现了一个JAX-WS客户端,一切都能够成功运行,但是当我想要在Java 8(jdk1.8.0_25)中使用安全连接(SSL/TLS)时出现问题。

我在日志(-Djavax.net.debug=all)中看到以下异常:

main, handling exception: java.net.SocketException: Connection reset
main, SEND TLSv1.2 ALERT:  fatal, description =    unexpected_message
main, WRITE: TLSv1.2 Alert, length = 2
main, Exception sending alert: java.net.SocketException: Connection reset by peer: socket write error
在进行深入分析后,我观察到这个问题是由于使用Java 8时未发送服务器名称(SNI),而在使用Java 7时发送了服务器名称,因此Web服务调用成功。 Java 8日志(-Djavax.net.debug=all): 缺少“扩展server_name”。
[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
***
[...]

Java 7日志 (-Djavax.net.debug=all) (可行): "扩展服务名称"已设置

[...]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [host_name: testeo.hostname.es]
***
[...]

观察到使用Java 7时会设置扩展server_name, server_name: [host_name: testeo.hostname.es],然后Web服务调用成功。

为什么Java 8没有像Java 7那样设置server_name呢?这是Java配置问题吗?

4个回答

15

正如提到的那样,原因与JDK的一个bug有关,在使用setHostnameVerifier()时会破坏SNI(扩展服务器名称)。 https://bugs.openjdk.java.net/browse/JDK-8144566

我们的解决方法: 经过测试,我们发现将连接的SSLSocketFactory设置为默认值以外的任何东西似乎都可以解决此问题。

这不起作用: HttpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());

这起作用: HttpsURLConnection.setSSLSocketFactory(new SSLSocketFactoryFacade());

因此,要为JAX-WS客户端修复它,你可以像这样做: bindingProvider.getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", new SSLSocketFactoryFacade());

我们的SSLSocketFactory门面:(请注意,它实际上什么也没做)

public class SSLSocketFactoryFacade extends SSLSocketFactory {

    SSLSocketFactory sslsf;

    public SSLSocketFactoryFacade() {
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
        return sslsf.createSocket(socket, s, i, b);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
        return sslsf.createSocket(s, i, inetAddress, i1);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
        return createSocket(inetAddress, i);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
        return createSocket(inetAddress, i, inetAddress1, i1);
    }
}

请解释一下什么是bindingProvider,或者添加代码进行演示。我想在尝试修复现有系统中的错误时使用您的解决方案。 - chrisinmtown
这个门面类技巧连同自定义的HostnameVerifier修复了我的JDK8应用程序。如果没有门面技巧,我将无法使用自定义验证器。 - Whome
每次我重新启动电脑并使用客户端应用程序启动Tomcat时,都会出现异常:“BuilderException:无法找到请求目标的有效认证路径”。 重新启动Tomcat后一切正常。 在查看日志后,我发现由于某种原因,在第一次运行期间,ClientHello中不存在扩展服务器名称。 自定义SSLSocketFactoryFacade解决了这个问题。 另外,Lombok的@Delegate注释将使类变得超级小。 - Yuriy
你能解释一下这是如何解决问题的吗?如果门面没有任何作用,那么是否有依赖于类名称的东西? - lrxw

10

请使用JDK版本8u141及以上,此问题已得到解决。请查看JDK 8u141错误修复页面了解更多详情。


9

0

我尝试了Benjamin Parry提供的解决方案,但对我没有用。经过一番搜索,我还发现了this solution,它看起来非常相似,但SSLSocketFactoryFacade手动插入正确的SSL头而不是纯粹的传递。下面是我的最终代码,略有不同,但基本思路归功于javabreaks的Girish Kamath:

    private static class SSLSocketFactoryFacade extends SSLSocketFactory {
    private SSLSocketFactory sslsf;
    private SSLParameters sslParameters;

    public SSLSocketFactoryFacade(String hostName) {
        sslParameters = new SSLParameters();
        sslParameters.setServerNames(Arrays.asList(new SNIHostName(hostName)));
        sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();;
    }

    public Socket createSocket() throws IOException {
        Socket socket = sslsf.createSocket();
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, InputStream arg1, boolean arg2) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
            throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1, arg2, arg3);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public Socket createSocket(String arg0, int arg1) throws IOException, UnknownHostException {
        Socket socket = sslsf.createSocket(arg0, arg1);
        ((SSLSocket) socket).setSSLParameters(sslParameters);
        return socket;
    }

    public String[] getDefaultCipherSuites() {
        return sslsf.getDefaultCipherSuites();
    }

    public String[] getSupportedCipherSuites() {
        return sslsf.getSupportedCipherSuites();
    }
}

然后我可以调用

sslConnection.setSSLSocketFactory(new SSLSocketFactoryFacade(sslConnection.getURL().getHost()));

其中sslConnectionHttpsURLConnection


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接