Как обрабатывать https-url, который заканчивает соединение с открытым текстом
Я пытаюсь получить содержимое страницы https-url, который генерирует исключение при получении входного потока.
String httpsURL = "https://careers.virtusa.com/";
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection)myurl.openConnection();
InputStream ins = con.getInputStream();
Исключение составляет, как показано ниже,
Exception in thread "main" javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at com.sun.net.ssl.internal.ssl.InputRecord.handleUnknownRecord(InputRecord.java:523)
at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:355)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:798)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1149)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:434)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1172)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:234)
at url.JavaHttpsExample.main(JavaHttpsExample.java:18)
Оба HttpURLConnection
и HttpsURLConnection
терпят неудачу. Я пробовал org.apache.http.impl.client.CloseableHttpClient
, но получал то же исключение. В браузере он отлично работает.
Ответы
Ответ 1
Я не получаю ошибку plaintext connection
, которую вы получаете. Я также подтвердил, что сайт, который вы пытаетесь расчесывать, фактически настроен для HTTPS. Это исключение, которое я получаю:
java.security.cert.CertificateException: не найдено альтернативного имени DNS-имени, найденного sub.website.com.
Одним из решений этой проблемы является установка верификатора хоста, который доверяет всем хостам (включая sun.website.com
). Попробуйте использовать следующий фрагмент кода, который выводит вашу целевую страницу на консоль Java:
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
String httpsURL = "https://sub.website.com/";
URL myurl = new URL(httpsURL);
HttpsURLConnection con = (HttpsURLConnection)myurl.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
String input;
while ((input = br.readLine()) != null) {
System.out.println(input);
}
br.close();
Примечание:
Я дал этот ответ, когда OP использовал другой сайт. Ответ действителен для условий, указанных, хотя OP может измениться с тех пор.
Ответ 2
Вы можете найти всю информацию о сертификате страницы, которую хотите загрузить на этой странице: сертификат careers.virtusa.com.
В нем говорится, что проблема заключается в следующем:
- Java 6u45: несоответствие протокола или шифрования
- Java 7u25: несоответствие набора протоколов или шифров
Чтобы решить проблему, вы можете ослабить сертификацию или настроить клиента на наличие протокола и шифрования, соответствующих сертификату. Например, они могут быть:
Протокол: TLS 1.2
Шифр: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Как решить и получить контент
Я пишу код с помощью apache httpcomponents, он в основном доверяет любому источнику и не заботится о сертификации.
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
Он возвращает содержимое страницы.
import java.io.*;
import org.apache.http.*;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.*;
import org.apache.http.impl.client.*;
import org.apache.http.ssl.SSLContextBuilder;
public class Main {
public static void main(String[] args) throws Exception {
String httpsURL = "https://careers.virtusa.com";
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
sslsf).build();
HttpGet httpget = new HttpGet(httpsURL);
HttpResponse response = httpclient.execute(httpget);
HttpEntity httpEntity = response.getEntity();
InputStream inputStream = httpEntity.getContent();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer result = new StringBuffer();
String line = "";
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
System.out.println(result.toString());
}
}
Он печатает:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transiti ...
Ответ 3
Укажите версию SSL, которую использует careers.virtusa.com. т.е. TLSv1.2
в коде.
SSLContext sc = SSLContext.getInstance( "TLSv1.2" );
public String getData(String URL)
{
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
}
} };
String output = "";
try{
//System.setProperty("https.proxyHost", "<PROXY HOST IP>"); // Uncomment if using proxy
//System.setProperty("https.proxyPort", "<PROXY HOST PORT>"); // Uncomment if using proxy
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
/*
* end of the fix
*/
URL url = new URL(URL);
URLConnection con = url.openConnection();
InputStream ins = con.getInputStream();
InputStreamReader isr = new InputStreamReader(ins);
BufferedReader in = new BufferedReader(isr);
String inputLine;
while ((inputLine = in.readLine()) != null) {
output = output + inputLine;
}
System.out.println(output);
in.close();
}
catch(Exception e){
e.printStackTrace();
}
return output;
}
Ответ 4
Я думаю, что проблема заключается в перенаправлении (302). Последняя страница - http не https.
Установите
HttpsURLConnection.setFollowRedirects(false);
Вызвать первый URL-адрес, получить заголовок Местоположение, выполнить другой вызов в определенном месте. Получите cookie во втором вызове в заголовке Set-Cookie и снова в заголовке Местоположение. Наконец, откройте еще один URLConnection со вторым перенаправленным URL, сообщающим cookie (con.setRequestProperty( "Cookie", COOKIE)).
Это должно работать.
HttpsURLConnection.setFollowRedirects(false);
String url = "https://<host>:443/OA_HTML/IrcVisitor";
String cookie = "";
HttpURLConnection con = (HttpURLConnection)new URL(url).openConnection();
con.setRequestMethod("GET");
con.connect();
// aasuming that is always a redirect // if(con.getResponseCode() == 302) {
url = con.getHeaderField("Location");
con.disconnect();
con = (HttpsURLConnection)new URL(url).openConnection();
con.setRequestMethod("GET");
con.connect();
// aasuming that is always a redirect // if(con.getResponseCode() == 302) {
url = con.getHeaderField("Location");
cookie = con.getHeaderField("Set-Cookie");
cookie = cookie.substring(0, cookie.indexOf(';'));
con = (HttpsURLConnection)new URL(url).openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Cookie", cookie);
con.setDoInput(true);
con.connect();
if(con.getResponseCode() == 200) {
//readStream(con.getInputStream());
//if you need to download something with a relative path from this page use "Content-Location" as base path
String basePath = con.getHeaderField("Content-Location");
}
Ответ 5
Все запросы https://careers.virtusa.com
перенаправляются на веб-шлюз McAfee (прокси)
Request URL:https://careers.virtusa.com/
Request Method:GET
Status Code:302 Found
Remote Address:203.62.173.60:443
Если вы просто запросите этот адрес http://203.62.173.60:443
, вы получите сообщение об ошибке рукопожатия
Handshake failed
The SSL handshake could not be performed.
Host: 10.4.190.60
Reason: :state 21:Application response 500 handshakefailed
потому что шлюз ожидает безопасного HTTP-запроса от доверенного клиента с сертификатом careers.virtusa.com
.
Проблема не появляется в веб-браузере, потому что я полагаю, что передний веб-сервер virtusa internaly перенаправляется на веб-шлюз с использованием доверенного сертификата, поэтому он, наконец, возвращает веб-страницу без проблем.
С другой стороны, большинство современных веб-браузеров по умолчанию используют TLS 1.1
или TLS 1.2
для выполнения защищенных запросов, но Java dont, зависит от версии Java.
Если вы проанализируете careers.virtusa.com
, вы увидите, что поддерживает только 1.1 и 1.2
TLS 1.2 Yes
TLS 1.1 Yes
TLS 1.0 No
SSL 3 No
SSL 2 No
JDK 5
и 6
поддерживает SSLv3
и TLSv1
, поэтому, если вы используете эту версию, вы получите исключение SSL.
JDK 7, 8
и 9
поддерживает SSLv3, TLSv1, TLSv1.1
и TLSv1.2
, но вам нужно явно указать поддерживаемые протоколы для вашего соединения, в этом случае
new String[] { "TLSv1.1", "TLSv1.2" }
Итак, вам нужно:
- Сертификат с открытым ключом
careers.virtusa.com
(используя openssl s_client
или непосредственно из браузера)
- Импортировать сертификат в хранилище ключей, чтобы использовать его как доверенное хранилище с вашим HTTP-клиентом.
- версия Java > 6.
- Установите поддерживаемые протоколы для подключения.
Пример (с Apache HttpComponents 4.4.1)
import java.io.File;
import javax.net.ssl.SSLContext;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
public class SSLTest {
public final static void main(String[] args) throws Exception {
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File("/tmp/careers.virtusa.com.jks"), "changeit".toCharArray(), new TrustSelfSignedStrategy()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1.1", "TLSv1.2" }, null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
HttpGet httpget = new HttpGet("https://careers.virtusa.com/");
CloseableHttpResponse response = httpClient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
System.out.println(IOUtils.toString(entity.getContent()));
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpClient.close();
}
}
}
Затем вы можете получить содержимое веб-страницы, используя оба URL-адреса, http://careers.virtusa.com/
или https://careers.virtusa.com/
Надеюсь, что это поможет.
Ответ 6
Если вы доверяете конечной точке, вы можете полностью отключить проверку сертификата, используя опцию 2, перечисленную здесь:
сообщить java, чтобы принять самоподписанный сертификат ssl
Просто добавьте это перед кодом:
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
Ответ 7
В соответствии с вопросом "Исключение SSL-квитирования: невозможно найти правильный путь сертификации к запрашиваемому целевому объекту" в security packexchange, вам нужно либо
- добавить отсутствующий сертификат как доверенный Java
- или добавить отсутствующий сертификат в цепочку веб-сервера.
Я попробовал первый вариант из учебник Mkyong, и он сработал.
Примечание. InstallCert переместился в github.
Возможно, вам понадобится создать временный проект, запускающий этот java файл с вашим целевым сайтом (в вашем случае "careers.virtusa.com" ) в качестве аргументов, см. скриншот. Взаимодействуйте с консолью, он создаст файл с именем "jssecacerts". Просто скопируйте и вставьте этот файл в свою "$ JAVA_HOME\jre\lib\security" (в моем случае, папку "C:\Program Files\Java\jdk1.8.0_60\jre\lib\security" ).
Наконец, запустите приложение, вы получите контент!