Java SSLException:证书中的主机名不匹配
我一直在使用下面的代码连接到谷歌的服务之一。 此代码在我的本地机器上正常工作:
HttpClient client=new DefaultHttpClient(); HttpPost post = new HttpPost("https://www.google.com/accounts/ClientLogin"); post.setEntity(new UrlEncodedFormEntity(myData)); HttpResponse response = client.execute(post);
我把这个代码放到了阻止Google.com的生产环境中。 根据要求,他们允许我访问IP:74.125.236.52 – 这是Google的IP之一,允许与Google服务器通信。 我编辑我的主机文件也添加这个条目。
我仍然无法访问URL,我不知道为什么。 所以我用上面的代码replace了:
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
现在我得到这样的错误:
javax.net.ssl.SSLException:证书中的主机名不匹配:<74.125.236.52>!= <www.google.com>
我猜这是因为Google有多个IP。 我不能要求networkingpipe理员允许我访问所有这些IP – 我甚至可能不会得到这整个列表。
我现在应该怎么做 ? 在Java级别有解决方法吗? 还是完全在networking人手中?
您也可以尝试按照此处所述设置HostnameVerifier。 这对我来说是为了避免这个错误。
// Do not do this in production!!! HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; DefaultHttpClient client = new DefaultHttpClient(); SchemeRegistry registry = new SchemeRegistry(); SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier); registry.register(new Scheme("https", socketFactory, 443)); SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry); DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); // Set verifier HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); // Example send http request final String url = "https://encrypted.google.com/"; HttpPost httpPost = new HttpPost(url); HttpResponse response = httpClient.execute(httpPost);
证书validation过程将始终validation服务器提供的证书的DNS名称,以及客户端使用的URL中的服务器主机名。
下面的代码
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
将导致证书validation过程validation服务器(即www.google.com
发布的证书的通用名称是否与主机名(即74.125.236.52
匹配。 很显然,这肯定会导致失败(你可以通过浏览器浏览到URL https://74.125.236.52/accounts/ClientLogin
来validation,并且自己看到了错误)。
据说,为了安全起见,你不愿写自己的TrustManager
(除非你知道如何编写安全的TrustManager
),你应该考虑在你的数据中心build立DNSlogging,以确保所有的查询www.google.com
将parsing为74.125.236.52
; 这应该在您的本地DNS服务器或您的操作系统的hosts
文件中完成; 您可能还需要将条目添加到其他域。 不用说,您需要确保这与您的ISP返回的logging一致。
我有类似的问题。 我正在使用Android的DefaultHttpClient。 我读过HttpsURLConnection可以处理这种exception。 所以我创build了使用来自HttpsURLConnection的validation器的自定义HostnameVerifier。 我也将实现包装到自定义的HttpClient中。
public class CustomHttpClient extends DefaultHttpClient { public CustomHttpClient() { super(); SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory(); socketFactory.setHostnameVerifier(new CustomHostnameVerifier()); Scheme scheme = (new Scheme("https", socketFactory, 443)); getConnectionManager().getSchemeRegistry().register(scheme); }
这里是CustomHostnameVerifier类:
public class CustomHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier { @Override public boolean verify(String host, SSLSession session) { HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); return hv.verify(host, session); } @Override public void verify(String host, SSLSocket ssl) throws IOException { } @Override public void verify(String host, X509Certificate cert) throws SSLException { } @Override public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { }
}
httpcliet4.3.3中的一个更清晰的方法(仅适用于testing环境)如下所示。
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
感谢维尼特·雷诺兹。 你提供的链接有很多用户的评论 – 其中之一,我试图绝望,它帮助。 我加了这个方法:
// Do not do this in production!!! HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){ public boolean verify(String string,SSLSession ssls) { return true; } });
这对我来说似乎很好,尽pipe我知道这个解决scheme是暂时的。 我正在与networking人员一起确定为什么我的主机文件被忽略。
在httpclient-4.3.3.jar中,有另一个HttpClient可以使用:
public static void main (String[] args) throws Exception { // org.apache.http.client.HttpClient client = new DefaultHttpClient(); org.apache.http.client.HttpClient client = HttpClientBuilder.create().build(); System.out.println("HttpClient = " + client.getClass().toString()); org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); org.apache.http.HttpResponse response = client.execute(post); java.io.InputStream is = response.getEntity().getContent(); java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); String line; while ((line = rd.readLine()) != null) { System.out.println(line); } }
这个HttpClientBuilder.create()。build()将返回org.apache.http.impl.client.InternalHttpClient 。 它可以处理证书中的这个主机名称不匹配的问题。
关心的是我们不应该使用ALLOW_ALL_HOSTNAME_VERIFIER。
我如何实现我自己的主机名称validation?
class MyHostnameVerifier implements org.apache.http.conn.ssl.X509HostnameVerifier { @Override public boolean verify(String host, SSLSession session) { String sslHost = session.getPeerHost(); System.out.println("Host=" + host); System.out.println("SSL Host=" + sslHost); if (host.equals(sslHost)) { return true; } else { return false; } } @Override public void verify(String host, SSLSocket ssl) throws IOException { String sslHost = ssl.getInetAddress().getHostName(); System.out.println("Host=" + host); System.out.println("SSL Host=" + sslHost); if (host.equals(sslHost)) { return; } else { throw new IOException("hostname in certificate didn't match: " + host + " != " + sslHost); } } @Override public void verify(String host, X509Certificate cert) throws SSLException { throw new SSLException("Hostname verification 1 not implemented"); } @Override public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { throw new SSLException("Hostname verification 2 not implemented"); } }
让我们testing一下共享服务器上托pipe的https://www.rideforrainbows.org/ 。
public static void main (String[] args) throws Exception { //org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); //sf.setHostnameVerifier(new MyHostnameVerifier()); //org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf); org.apache.http.client.HttpClient client = new DefaultHttpClient(); //client.getConnectionManager().getSchemeRegistry().register(sch); org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); org.apache.http.HttpResponse response = client.execute(post); java.io.InputStream is = response.getEntity().getContent(); java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); String line; while ((line = rd.readLine()) != null) { System.out.println(line); } }
exceptionSSLException:
线程“main”中的exceptionjavax.net.ssl.SSLException:证书中的主机名不匹配:www.rideforrainbows.org!= stac.rt.sg OR stac.rt.sg或www.stac.rt.sg
在org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:231)
…
用MyHostnameVerifier做:
public static void main (String[] args) throws Exception { org.apache.http.conn.ssl.SSLSocketFactory sf = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); sf.setHostnameVerifier(new MyHostnameVerifier()); org.apache.http.conn.scheme.Scheme sch = new Scheme("https", 443, sf); org.apache.http.client.HttpClient client = new DefaultHttpClient(); client.getConnectionManager().getSchemeRegistry().register(sch); org.apache.http.client.methods.HttpPost post = new HttpPost("https://www.rideforrainbows.org/"); org.apache.http.HttpResponse response = client.execute(post); java.io.InputStream is = response.getEntity().getContent(); java.io.BufferedReader rd = new java.io.BufferedReader(new java.io.InputStreamReader(is)); String line; while ((line = rd.readLine()) != null) { System.out.println(line); } }
显示:
主机= http://www.rideforrainbows.org
SSL主机= http://www.rideforrainbows.org
至less我有逻辑来比较(主机== SSL主机)并返回true。
以上源代码适用于httpclient-4.2.3.jar和httpclient-4.3.3.jar。