HTTPURLConnection не следует перенаправлению с HTTP на HTTPS

Я не могу понять, почему Java HttpURLConnection не следует перенаправлению HTTP с HTTP на HTTPS URL. Я использую следующий код для перехода на страницу https://httpstat.us/:

import java.net.URL;
import java.net.HttpURLConnection;
import java.io.InputStream;

public class Tester {

    public static void main(String argv[]) throws Exception{
        InputStream is = null;

        try {
            String httpUrl = "http://httpstat.us/301";
            URL resourceUrl = new URL(httpUrl);
            HttpURLConnection conn = (HttpURLConnection)resourceUrl.openConnection();
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(15000);
            conn.connect();
            is = conn.getInputStream();
            System.out.println("Original URL: "+httpUrl);
            System.out.println("Connected to: "+conn.getURL());
            System.out.println("HTTP response code received: "+conn.getResponseCode());
            System.out.println("HTTP response message received: "+conn.getResponseMessage());
       } finally {
            if (is != null) is.close();
        }
    }
}

Вывод этой программы:

Original URL: http://httpstat.us/301
Connected to: http://httpstat.us/301
HTTP response code received: 301
HTTP response message received: Moved Permanently

Запрос http://httpstat.us/301 возвращает следующий (сокращенный) ответ (который кажется абсолютно правильным!):

HTTP/1.1 301 Moved Permanently
Cache-Control: private
Content-Length: 21
Content-Type: text/plain; charset=utf-8
Location: https://httpstat.us

К сожалению, Java HttpURLConnection не выполняет перенаправление!

Обратите внимание, что если вы измените исходный URL-адрес на HTTPS (https://httpstat.us/301), Java будет следовать перенаправлению, как и ожидалось !?

Ответ 1

Перенаправления выполняются, только если они используют один и тот же протокол. (См. метод followRedirect() в источнике.) Отключить эту проверку невозможно.

Хотя мы знаем, что он отражает HTTP, с точки зрения протокола HTTP, HTTPS - это просто какой-то другой, совершенно другой, неизвестный протокол. Было бы небезопасно следовать перенаправлению без согласия пользователя.

Например, предположим, что приложение настроено для автоматической аутентификации клиента. Пользователь ожидает анонимного серфинга, потому что он использует HTTP. Но если его клиент следует HTTPS без запроса, его личность раскрывается на сервере.

Ответ 2

HttpURLConnection design не будет автоматически перенаправлять с HTTP на HTTPS (или наоборот). После перенаправления могут возникнуть серьезные последствия для безопасности. SSL (следовательно, HTTPS) создает сеанс, который является уникальным для пользователя. Этот сеанс может быть повторно использован для нескольких запросов. Таким образом, сервер может отслеживать все запросы, сделанные от одного человека. Это слабая форма идентичности и ее можно использовать. Кроме того, SSL рукопожатие может запросить сертификат клиента. При отправке на сервер, идентификатор клиента передается серверу.

Как указывает erickson, предположим, что приложение настроено для автоматической аутентификации клиента. Пользователь ожидает анонимного серфинга, потому что он использует HTTP. Но если его клиент следует HTTPS без запроса, его личность раскрывается на сервере.

Программист должен предпринять дополнительные шаги, чтобы гарантировать, что учетные данные, клиентские сертификаты или идентификатор сеанса SSL не будут отправлены перед перенаправлением с HTTP на HTTPS. По умолчанию это отправить. Если перенаправление причиняет вред пользователю, не следуйте за перенаправлением. Вот почему автоматическое перенаправление не поддерживается.

С этим понял, здесь код, который будет следовать за перенаправлениями.

  URL resourceUrl, base, next;
  Map<String, Integer> visited;
  HttpURLConnection conn;
  String location;
  int times;

  ...
  visited = new HashMap<>();

  while (true)
  {
     times = visited.compute(url, (key, count) -> count == null ? 1 : count + 1);

     if (times > 3)
        throw new IOException("Stuck in redirect loop");

     resourceUrl = new URL(url);
     conn        = (HttpURLConnection) resourceUrl.openConnection();

     conn.setConnectTimeout(15000);
     conn.setReadTimeout(15000);
     conn.setInstanceFollowRedirects(false);   // Make the logic below easier to detect redirections
     conn.setRequestProperty("User-Agent", "Mozilla/5.0...");

     switch (conn.getResponseCode())
     {
        case HttpURLConnection.HTTP_MOVED_PERM:
        case HttpURLConnection.HTTP_MOVED_TEMP:
           location = conn.getHeaderField("Location");
           location = URLDecoder.decode(location, "UTF-8");
           base     = new URL(url);               
           next     = new URL(base, location);  // Deal with relative URLs
           url      = next.toExternalForm();
           continue;
     }

     break;
  }

  is = conn.openStream();
  ...

Ответ 3

Что-то назвало HttpURLConnection.setFollowRedirects(false) случайно?

Вы всегда можете позвонить

conn.setInstanceFollowRedirects(true);

если вы хотите, чтобы вы не влияли на остальную часть поведения приложения.

Ответ 4

Как уже упоминалось выше, setFollowRedirect и setInstanceFollowRedirects работают только тогда, когда перенаправленный протокол тот же. т.е. от http до http и https до https.

setFolloRedirect находится на уровне класса и устанавливает это для всех экземпляров url-соединения, тогда как setInstanceFollowRedirects - только для данного экземпляра. Таким образом, мы можем иметь различное поведение для разных экземпляров.

Я нашел здесь очень хороший пример http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Ответ 5

Другим вариантом может быть использование Apache HttpComponents Client:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

Образец кода:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("https://media-hearth.cursecdn.com/avatars/330/498/212.png");
CloseableHttpResponse response = httpclient.execute(httpget);
final HttpEntity entity = response.getEntity();
final InputStream is = entity.getContent();

Ответ 6

Правильный ответ, но вы знаете, что нужно получить новое местоположение из ответа и использовать его как URL

Ответ 7

HTTPUrlConnection не отвечает за обработку ответа объекта. Это производительность, как ожидалось, она захватывает содержимое запрошенного URL-адреса. Вы можете использовать функцию для интерпретации ответа. Он не может читать намерения разработчика без спецификации.