Java SSLException: имя хоста в сертификате не совпало
Я использовал следующий код для подключения к одной из сервисов google. Этот код отлично работал на моей локальной машине:
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. По запросу они разрешили общение с сервером Google, предоставив мне доступ к IP: 74.125.236.52 - который является одним из IP-адресов Google. Я также отредактировал файл моих хостов, чтобы добавить эту запись.
Тем не менее, я не смог получить доступ к URL-адресу, и мне интересно, почему. Поэтому я заменил приведенный выше код:
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
Теперь я получаю сообщение об ошибке:
javax.net.ssl.SSLException: имя хоста в сертификате не соответствует: < 74.125.236.52 > != < www.google.com >
Я предполагаю, что это связано с тем, что Google имеет несколько IP-адресов. Я не могу попросить администратора сети разрешить мне доступ ко всем этим IP-адресам - я даже не могу получить весь этот список.
Что мне теперь делать? Есть ли обходной путь на уровне Java? Или это полностью в руках сетевого парня?
Ответы
Ответ 1
Спасибо Vineet Reynolds. Ссылка, которую вы предоставили, содержала множество комментариев пользователей - одна из которых я старался в отчаянии, и это помогло. Я добавил этот метод:
// Do not do this in production!!!
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){
public boolean verify(String string,SSLSession ssls) {
return true;
}
});
Теперь это кажется мне прекрасным, хотя я знаю, что это решение является временным. Я работаю с сетевыми пользователями, чтобы определить, почему мой файл hosts игнорируется.
Ответ 2
Вы также можете попробовать установить 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);
Ответ 3
Процесс проверки сертификата всегда будет проверять DNS-имя сертификата, представленного сервером, с именем хоста сервера в URL-адресе, используемом клиентом.
Следующий код
HttpPost post = new HttpPost("https://74.125.236.52/accounts/ClientLogin");
приведет к процессу проверки сертификата, проверяя, совпадает ли общее имя сертификата, выданного сервером, т.е. www.google.com
, имени хоста i.e. 74.125.236.52
. Очевидно, что это обязательно приведет к сбою (вы могли бы проверить это, просмотрев URL-адрес https://74.125.236.52/accounts/ClientLogin
с помощью браузера и сами увидев результирующую ошибку).
Предположительно, ради безопасности вы не решаетесь написать свой собственный TrustManager
(и, если вы не понимаете, как писать безопасный), вы должны посмотреть на создание записей DNS в своем центре данных убедитесь, что все поисковые запросы www.google.com
будут разрешены на 74.125.236.52
; это должно быть сделано либо на локальных DNS-серверах, либо в файле hosts
вашей ОС; вам может потребоваться добавить записи и в другие домены. Излишне говорить, что вам нужно будет убедиться, что это согласуется с записями, возвращаемыми вашим интернет-провайдером.
Ответ 4
У меня была аналогичная проблема. Я использовал Android DefaultHttpClient. Я прочитал, что HttpsURLConnection может обрабатывать такое исключение. Поэтому я создал собственный HostnameVerifier, который использует верификатор из HttpsURLConnection. Я также завернул реализацию в пользовательский 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 {
}
}
Ответ 5
Более чистый подход (только для тестовой среды) в httpcliet4.3.3 выглядит следующим образом.
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
Ответ 6
В 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. Он может обрабатывать этот имя хоста в сертификате не соответствует.
Ответ 7
Мы не должны использовать ALLOW_ALL_HOSTNAME_VERIFIER.
Как я могу реализовать собственный верификатор имени хоста?
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");
}
}
Проведите тест против 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);
}
}
SSLException:
Исключение в потоке "main" javax.net.ssl.SSLException: имя хоста в сертификате не соответствует: www.rideforrainbows.org!= stac.rt.sg ИЛИ stac.rt.sg ИЛИ www.stac.rt. SG
at 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);
}
}
Показывает:
Host = www.rideforrainbows.org
SSL Host = www.rideforrainbows.org
По крайней мере, у меня есть логика для сравнения (Host == SSL Host) и возвращает true.
Этот исходный код работает для httpclient-4.2.3.jar и httpclient-4.3.3.jar.