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 і навпаки, це може мати серйозні наслідки для безпеки. Таким чином, виправлення щоб повернути відповіді сервера на переспрямування. Перевірте код відповіді та значення поля заголовка Розташування для інформації про перенаправлення. Додаток відповідає за перенаправлення. "
Щеклейн

2
Але чи слід перенаправлення з http на http або https на https? Навіть це було б неправильно. Чи не так?
Сударшан Бхат,

7
@JoshuaDavis Так, це стосується лише переспрямувань на той самий протокол. Не HttpURLConnectionбуде автоматично слідувати за переспрямуванням на інший протокол, навіть якщо встановлений прапор переспрямування.
erickson

8
Інженери Java Networking можуть запропонувати опцію setFollowTransProtocol (true), тому що якщо вона нам потрібна, ми все одно її запрограмуємо. Веб-браузери FYI, curl і wget і, можливо, більше переспрямовують із HTTP на HTTPS і навпаки.
суперкобра

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
@Nathan - дякую за подробиці, але я все одно не купую його. Наприклад, якщо під контролем клієнта надсилаються будь-які облікові дані або сертифікати клієнта. Якщо боляче, не робіть цього (у цьому випадку не слідкуйте за перенаправленням).
Джуліан Решке

1
Я лише не розумію location = URLDecoder.decode(location...частину. Це декодує робочу закодовану відносну частину (з пробілом = + у моєму випадку) у неробочу. Після того, як я його видалив, для мене це було нормально.
Niek

@Niek Я не впевнений, чому це вам не потрібно, але мені це потрібно.
Натан

26

Чи HttpURLConnection.setFollowRedirects(false)випадково щось зателефонувало ?

Ви завжди можете зателефонувати

conn.setInstanceFollowRedirects(true);

якщо ви хочете переконатися, що не впливаєте на решту поведінки програми.


Ооо ... не знав про це ... Приємна знахідка ... Я збирався шукати клас, якщо там була така логіка .... Має сенс, що він повертає цей заголовок, даючи єдину відповідальність директора .... тепер поверніться до відповідей на запитання на C #: P [жартую]
monksy

2
Зауважте, що setFollowRedirects () слід викликати в класі, а не в екземплярі.
karlbecker_com

3
@dldnh: Хоча karlbecker_com був абсолютно правий щодо виклику setFollowRedirectsтипу, setInstanceFollowRedirectsце метод екземпляра, і його не можна викликати для типу.
Джон Скіт,

1
uggh, як я це неправильно прочитав. вибачте за неправильне редагування. Я також намагався відмовитись, і не впевнений, як я це все зробив.
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
Чому в цьому випадку він має setInstanceFollowRedirects? ))
Щеклейн

Я припускаю, що це була запропонована функція для додавання пізніше, це має сенс .. мій коментар більше відображався на ... клас розроблений для того, щоб захопити веб-вміст і повернути його назад ... люди можуть захотіти отримувати не HTTP 200 повідомлення.
monksy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.