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

97

Я не могу понять, почему Java HttpURLConnectionне выполняет перенаправление HTTP с HTTP на URL-адрес HTTPS. Я использую следующий код для получения страницы по адресу 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();
        }
    }
}

Результат этой программы:

Исходный URL: http://httpstat.us/301
Подключено к: http://httpstat.us/301
Получен код ответа HTTP: 301
Получено ответное сообщение HTTP: перемещено навсегда

Запрос на 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 будет следовать за перенаправлением, как ожидалось !?

Щеклеин
источник
Привет, я отредактировал ваш вопрос для ясности и указал, что перенаправление на HTTPS, в частности, является проблемой. Кроме того, я изменил домен bit.ly на другой, так как использование bit.ly занесено в черный список вопросов. Надеюсь, вы не против, не стесняйтесь редактировать заново.
sleske

Ответы:

119

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

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

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

Эриксон
источник
60
Спасибо. Я только что нашел подтверждение: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . А именно: «После обсуждения между инженерами Java Networking возникло мнение, что мы не должны автоматически выполнять перенаправление с одного протокола на другой, например с http на https и наоборот, это может иметь серьезные последствия для безопасности. Таким образом, исправление для возврата ответов сервера для перенаправления. Проверьте код ответа и значение поля заголовка Location для получения информации о перенаправлении. Следить за перенаправлением отвечает приложение.
Щеклейн
2
Но следует ли это перенаправлению с http на http или https на https? Даже это было бы неправильно. Не так ли?
Сударшан Бхат
7
@JoshuaDavis Да, это относится только к перенаправлению на тот же протокол. An HttpURLConnectionне будет автоматически выполнять перенаправления на другой протокол, даже если установлен флаг перенаправления.
erickson 04
8
Инженеры Java Networking могут предложить параметр setFollowTransProtocol (true), потому что, если он нам понадобится, мы все равно запрограммируем его. FYI веб-браузеры, curl и wget и другие могут следовать перенаправлениям с HTTP на HTTPS и наоборот.
supercobra
18
Никто не настраивает автоматический вход на HTTPS и не ожидает, что HTTP будет «анонимным». Это бессмысленно. Совершенно безопасно и нормально следовать перенаправлениям с HTTP на HTTPS (а не наоборот). Это просто типично плохой Java API.
Гленн Мейнард,
54

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

Как указывает Эриксон , предположим, что приложение настроено для автоматической аутентификации клиента. Пользователь ожидает, что будет просматривать страницы анонимно, потому что он использует 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();
  ...
Натан
источник
Это только одно решение, которое работает более чем с 1 переадресацией. Спасибо!
Roger Alien
Это прекрасно работает для множественных перенаправлений (HTTPS API -> HTTP -> HTTP-изображение)! Идеальное простое решение.
EricH206
1
@ Натан - спасибо за подробности, но я все еще не покупаю это. Например, if находится под контролем клиента, отправляются ли какие-либо учетные данные или сертификаты клиента. Если больно, не делайте этого (в этом случае не выполняйте редирект).
Джулиан Решке
1
Я только часть не понимаю location = URLDecoder.decode(location.... Это декодирует рабочую закодированную относительную часть (в моем случае с пробелом = +) в нерабочую. После того, как я его удалил, меня все устроило.
Niek
@Niek Я не знаю, почему тебе это не нужно, но мне это нужно.
Натан
26

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

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

conn.setInstanceFollowRedirects(true);

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

Джон Скит
источник
Ооо ... не знал об этом ... Хорошая находка ... Я собирался поискать класс, если там была такая логика ... Имеет смысл, что он будет возвращать этот заголовок, давая единственную ответственность принципал .... а теперь
вернись
2
Обратите внимание, что setFollowRedirects () следует вызывать для класса, а не для экземпляра.
karlbecker_com
3
@dldnh: Хотя karlbecker_com был абсолютно прав в отношении вызова setFollowRedirectsтипа, setInstanceFollowRedirectsэто метод экземпляра и не может быть вызван для типа.
Джон Скит,
1
тьфу, как я это неправильно понял. извините за неправильное редактирование. также пытался откатиться и не знал, как я это сделал.
dldnh
7

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

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

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

Шалвика
источник
2

Другой вариант - использовать клиент Apache HttpComponents :

<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();
Корай Тугай
источник
-4

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

монашество
источник
7
Почему в этом случае он установилInstanceFollowRedirects? ))
Щеклейн
Я предполагаю, что это была предложенная функция для добавления позже, это имеет смысл .. мой комментарий был больше отражен в отношении ... класс предназначен для того, чтобы брать веб-контент и возвращать его ... люди могут захотеть получать сообщения не HTTP 200.
monksy