Java SSLProtocolException:握手警报:无法识别的\u名称:客户端代码解决方案?

Java SSLProtocolException:握手警报:无法识别的\u名称:客户端代码解决方案?,java,apache-httpclient-4.x,sni,Java,Apache Httpclient 4.x,Sni,在检索XML数据的自定义HTTP客户端中,我遇到了无法识别的\u名称错误。不幸的是,类似问题的答案对我来说并不适用: 我无法通过system属性关闭SNI支持,因为我的代码在应用程序服务器中运行,对此我没有控制权 我无法修复web服务服务器配置(添加服务器名) 现在,我的代码使用的是commons httpclient 3.1,但我对其他选择持开放态度。下面是我现在使用的简略代码: HttpClient client = new HttpClient(); PostMethod method

在检索XML数据的自定义HTTP客户端中,我遇到了无法识别的\u名称错误。不幸的是,类似问题的答案对我来说并不适用:

  • 我无法通过system属性关闭SNI支持,因为我的代码在应用程序服务器中运行,对此我没有控制权
  • 我无法修复web服务服务器配置(添加服务器名)
现在,我的代码使用的是commons httpclient 3.1,但我对其他选择持开放态度。下面是我现在使用的简略代码:

HttpClient client = new HttpClient();
PostMethod method = new PostMethod(getEndpointUrl());
NameValuePair[] data = {
    new NameValuePair("username", getUsername()),
    new NameValuePair("password", getPassword()),
    new NameValuePair("email", email)
};
method.setRequestBody(data);
client.executeMethod(method);
return method.getResponseBodyAsStream();
我看过HttpClient,似乎我可以实现一个类似于中提到的解决方案,但似乎有很多潜在的安全相关代码


我非常讨厌将大量代码转储到一个简单的客户机中。还有其他建议吗?

您正在连接的服务器配置了错误的SNI。如果您无法控制该服务器,并且无法修复它,则必须禁用SNI

1) 如果您希望服务器在某个时刻得到修复,请使用运行时标志暂时禁用SNI,直到服务器修复为止。通过这种方式,您可以稍后删除此标志,而无需在服务器修复后重新编译

java -Djsse.enableSNIExtension=false
2) 如果您不希望服务器得到修复,请实施永久性解决方案,例如在应用程序中禁用SNI:

System.setProperty("jsse.enableSNIExtension", "false");

您链接中提到的解决方案最终会以一种更麻烦的方式禁用SNI,因此,除非您在应用程序中的其他地方需要SNI,否则我将使用上面的一个选项。

因此,下面是我对该代码的尝试。我将它隐藏在一个配置属性后面,我们可以在部署时进行调整,因此希望在web服务服务器配置修复后,我们能够尽快消除这个缺点

java -Djsse.enableSNIExtension=false
我没有检查此代码的任何安全含义。我只在我的应用程序中测试了此代码,它可能对您不起作用。其他JVM版本的行为可能不同。如果您决定使用此代码,请不要追究我的责任

更新2014-09-01:不幸的是,只有通过HTTPS代理连接到目标时,此解决方案才有效。如果你直接连接,你会得到一个有点难以描述的错误

Caused by: java.net.SocketException: Unconnected sockets not implemented
  at javax.net.SocketFactory.createSocket(SocketFactory.java:125) ~[na:1.7.0_65]
因为我们的目标服务器的配置已经修复,所以我不再需要解决方法。添加工作存根createSocket()方法要复杂得多,因为它需要在HttpClient的SSLSocketFactory中完成

import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * When a TLS client sends the Server Name Indication extension in the Client Hello, the server might reply with an
 * alert that it does not know this particular server name.  Usually, this means that the server is misconfigured,
 * or that you're using the wrong hostname.  But as long as the server does get you to the correct web service or
 * site, it's harmless.
 * <p/>
 * Oracle in it's wisdom has decided that the warning should be treated as an error.  This cannot be changed.  This
 * class overrides all createSocket calls so that the hostname (for TLS purposes) is blanked-out.  In the
 * implementation in 1.7.0_51-b13, this makes SSL not issue the SNI extension in the hello,
 * thereby not triggering the problematic response.
 * <p/>
 * To use this with Apache HttpComponents' HttpClient, you need to create a ConnectionManager that uses a
 * SchemeManager, which in turn uses a custom https SchemeHandler using this socket factory.
 *
 * <pre>
 *   {@code
 *   SchemeRegistry schemeRegistry = new SchemeRegistry();
 *   schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
 *   if (disableServerNameIndication) {
 *       schemeRegistry.register(new Scheme("https", 443, new SSLSocketFactory(new NoSNISSLSocketFactory
 *           (sslcontext.getSocketFactory()),
 *           SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)));
 *   } else {
 *       schemeRegistry.register(new Scheme("https", 443, new SSLSocketFactory(sslcontext)));
 *   }
 *   defaultHttpClient = new DefaultHttpClient(new PoolingClientConnectionManager(schemeRegistry));}
 * </pre>
 */
public class NoSNISSLSocketFactory extends SSLSocketFactory {

  private final SSLSocketFactory sslSocketFactory;

  protected NoSNISSLSocketFactory(SSLSocketFactory socketFactory) {
    this.sslSocketFactory = socketFactory;
  }

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

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

  @Override
  public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
    return sslSocketFactory.createSocket(socket, "", port, autoClose);
  }

  @Override
  public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    return createSocket(new Socket(host, port), host, port, true);
  }

  @Override
  public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException,
      UnknownHostException {
    return createSocket(new Socket(host, port, localHost, localPort), host, port, true);
  }

  @Override
  public Socket createSocket(InetAddress host, int port) throws IOException {
    return sslSocketFactory.createSocket(host, port);
  }

  @Override
  public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
    return createSocket(new Socket(host, port, localHost, localPort), host.getHostName(), port, true);
  }
}
导入javax.net.ssl.SSLSocketFactory;
导入java.io.IOException;
导入java.net.InetAddress;
导入java.net.Socket;
导入java.net.UnknownHostException;
/**
*当TLS客户机在客户机Hello中发送服务器名称指示扩展名时,服务器可能会使用
*警告它不知道此特定服务器名称。通常,这意味着服务器配置错误,
*或者您使用了错误的主机名。但只要服务器让您找到正确的web服务或
*这是无害的。
*

*甲骨文凭借其智慧决定将该警告视为错误。这是无法改变的。这 *类重写所有createSocket调用,以便主机名(用于TLS)被清除。在 *在1.7.0_51-b13中实现,这使得SSL不会在hello中发出SNI扩展, *因此不会触发有问题的响应。 *

*要将其用于Apache HttpComponents的HttpClient,您需要创建一个使用 *SchemeManager,它反过来使用使用此套接字工厂的自定义https SchemeHandler。 * * *{@code *SchemeRegistry SchemeRegistry=新SchemeRegistry(); *register(新方案(“http”,80,PlainSocketFactory.getSocketFactory()); *if(禁用服务器名称指示){ *注册(新方案(“https”),443,新SSLSocketFactory(新NoSNISSLSocketFactory *(sslcontext.getSocketFactory()), *SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)); *}其他{ *register(新方案(“https”,443,新SSLSocketFactory(sslcontext)); * } *defaultHttpClient=新的defaultHttpClient(新的池客户端连接管理器(schemeRegistry));} * */ 公共类nosnisslocketfactory扩展了SSLSocketFactory{ 私人最终SSLSocketFactory SSLSocketFactory; 受保护的NoSNISSLSocketFactory(SSLSocketFactory socketFactory){ this.sslSocketFactory=socketFactory; } @凌驾 公共字符串[]GetDefaultCipherSuite(){ 返回sslSocketFactory.getDefaultCipherSuite(); } @凌驾 公共字符串[]GetSupportedCipherSuite(){ 返回sslSocketFactory.GetSupportedCipherSuite(); } @凌驾 公共套接字createSocket(套接字套接字、字符串主机、int端口、布尔自动关闭)引发IOException{ 返回sslSocketFactory.createSocket(套接字,“,端口,自动关闭); } @凌驾 公共套接字createSocket(字符串主机,int端口)引发IOException,UnknownHostException{ 返回createSocket(新套接字(主机,端口),主机,端口,true); } @凌驾 公共套接字createSocket(字符串主机、int端口、InetAddress本地主机、int本地端口)引发IOException, 未知后异常{ 返回createSocket(新套接字(主机、端口、本地主机、本地端口)、主机、端口、true); } @凌驾 公共套接字createSocket(InetAddress主机,int端口)引发IOException{ 返回sslSocketFactory.createSocket(主机、端口); } @凌驾 公共套接字createSocket(InetAddress主机、int端口、InetAddress本地主机、int本地端口)引发IOException{ 返回createSocket(新套接字(主机、端口、本地主机、本地端口),host.getHostName(),port,true); } }


正如我在问题中所说,我无法设置系统属性,因为我的代码运行在我无法控制的应用服务器上;在我的代码中设置属性