3

在检索 XML 数据的自定义 HTTP 客户端中,我收到 unrecognized_name 错误。不幸的是,类似问题的答案对我不起作用:

  • 我无法通过系统属性关闭 SNI 支持,因为我的代码在应用程序服务器内运行,我无法控制它
  • 我无法修复 Web 服务服务器配置(添加 ServerName)

现在,我的代码正在使用 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,似乎可以实现类似于https://stackoverflow.com/a/14884941/3676401中提到的解决方法,但似乎有很多潜在的安全相关代码。

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

4

2 回答 2

3

您正在连接到 SNI 配置错误的服务器。如果您无法控制该服务器并且无法修复它,那么您将不得不禁用 SNI。

1) 如果您希望服务器在某个时候修复,请使用运行时标志暂时禁用 SNI,直到服务器修复。这样,您可以稍后删除此标志,甚至无需在服务器修复后重新编译。

java -Djsse.enableSNIExtension=false

2)如果您不希望服务器会被修复,请实施永久解决方法,例如在您的应用程序中禁用 SNI:

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

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

于 2014-05-26T14:53:27.490 回答
2

所以这是我对该代码的尝试。我已经将它隐藏在我们可以在部署时调整的配置属性后面,所以希望我们可以在 Web 服务服务器配置修复后尽快摆脱这个缺陷。

我没有检查过这段代码的任何安全隐患。我只用我的应用程序对此进行了测试,它可能不适合你。其他 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() 方法要复杂得多,因为它需要在 HTTPClients 的 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);
  }
}
于 2014-05-27T15:09:54.880 回答