HTTPURLConnection nie śledzi przekierowania z HTTP do HTTPS

99

Nie mogę zrozumieć, dlaczego Java HttpURLConnectionnie śledzi przekierowania HTTP z HTTP do adresu URL HTTPS. Używam następującego kodu, aby uzyskać stronę pod adresem 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();
        }
    }
}

Wynik tego programu to:

Oryginalny adres URL: http://httpstat.us/301
Połączony z: http://httpstat.us/301
Otrzymany kod odpowiedzi HTTP: 301
Otrzymano wiadomość odpowiedzi HTTP: przeniesiono na stałe

Żądanie do http://httpstat.us/301 zwraca następującą (skróconą) odpowiedź (która wydaje się absolutnie słuszna!):

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

Niestety, Java HttpURLConnectionnie śledzi przekierowania!

Zwróć uwagę, że jeśli zmienisz oryginalny adres URL na HTTPS ( https://httpstat.us/301 ), Java będzie postępować zgodnie z oczekiwaniami !?

Shcheklein
źródło
1
Cześć, zredagowałem Twoje pytanie dla jasności i aby wskazać, że przekierowanie do HTTPS jest w szczególności problemem. Zmieniłem również domenę bit.ly na inną, ponieważ użycie bit.ly jest na czarnej liście w pytaniach. Mam nadzieję, że nie masz nic przeciwko, możesz ponownie edytować.
sleske

Odpowiedzi:

120

Przekierowania są przestrzegane tylko wtedy, gdy używają tego samego protokołu. (Patrz ten followRedirect()sposób w źródle). Nie ma sposobu, aby wyłączyć ten czek.

Chociaż wiemy, że odzwierciedla HTTP, z punktu widzenia protokołu HTTP, HTTPS jest po prostu innym, zupełnie innym, nieznanym protokołem. Podążanie za przekierowaniem bez zgody użytkownika byłoby niebezpieczne.

Załóżmy na przykład, że aplikacja jest skonfigurowana do automatycznego uwierzytelniania klienta. Użytkownik oczekuje, że będzie surfował anonimowo, ponieważ używa protokołu HTTP. Ale jeśli jego klient korzysta z protokołu HTTPS bez pytania, jego tożsamość zostaje ujawniona serwerowi.

erickson
źródło
60
Dzięki. Właśnie znalazłem potwierdzenie: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . Mianowicie: „Po dyskusji między inżynierami Java Networking wydaje się, że nie powinniśmy automatycznie śledzić przekierowań z jednego protokołu na inny, na przykład z http na https i odwrotnie, może to mieć poważne konsekwencje dla bezpieczeństwa. aby zwrócić odpowiedzi serwera w celu przekierowania. Sprawdź kod odpowiedzi i wartość pola nagłówka lokalizacji w celu uzyskania informacji o przekierowaniu. Za przestrzeganie przekierowania odpowiada aplikacja. "
Shcheklein
2
Ale czy następuje przekierowanie z http na http lub z https na https? Nawet to byłoby złe. Prawda?
Sudarshan Bhat
7
@JoshuaDavis Tak, dotyczy tylko przekierowań do tego samego protokołu. Nie HttpURLConnectionbędzie automatycznie śledzić przekierowań do innego protokołu, nawet jeśli flaga przekierowania jest ustawiona.
erickson
8
Inżynierowie Java Networking mogliby zaoferować opcję setFollowTransProtocol (true), ponieważ jeśli będziemy jej potrzebować, i tak ją zaprogramujemy. Przeglądarki internetowe FYI, curl i wget, a także mogą podążać za przekierowaniami z HTTP do HTTPS i odwrotnie.
supercobra
18
Nikt nie konfiguruje automatycznego logowania na HTTPS i nie oczekuje, że HTTP będzie „anonimowe”. To bezsensowne. Podążanie za przekierowaniami z HTTP do HTTPS jest całkowicie bezpieczne i normalne (a nie odwrotnie). To tylko typowy zły interfejs Java API.
Glenn Maynard
55

HttpURLConnection z założenia nie przekierowuje automatycznie z HTTP do HTTPS (i odwrotnie). Podążanie za przekierowaniem może mieć poważne konsekwencje dla bezpieczeństwa. SSL (stąd HTTPS) tworzy sesję, która jest unikalna dla użytkownika. Ta sesja może być ponownie wykorzystana do wielu żądań. W ten sposób serwer może śledzić wszystkie żądania wysyłane od jednej osoby. To słaba forma tożsamości i można ją wykorzystać. Ponadto uzgadnianie SSL może wymagać certyfikatu klienta. W przypadku przesłania na serwer tożsamość klienta jest przekazywana serwerowi.

Jak wskazuje erickson , załóżmy, że aplikacja jest skonfigurowana do automatycznego uwierzytelniania klienta. Użytkownik oczekuje, że będzie surfował anonimowo, ponieważ używa protokołu HTTP. Ale jeśli jego klient korzysta z protokołu HTTPS bez pytania, jego tożsamość zostaje ujawniona serwerowi.

Programista musi podjąć dodatkowe kroki, aby upewnić się, że poświadczenia, certyfikaty klienta lub identyfikator sesji SSL nie zostaną wysłane przed przekierowaniem z HTTP do HTTPS. Domyślnie są one wysyłane. Jeśli przekierowanie boli użytkownika, nie postępuj zgodnie z przekierowaniem. Dlatego automatyczne przekierowanie nie jest obsługiwane.

Po zrozumieniu tego, oto kod, który będzie podążał za przekierowaniami.

  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();
  ...
Nathan
źródło
To tylko jedno rozwiązanie, które działa w przypadku więcej niż 1 przekierowań. Dziękuję Ci!
Roger Alien
Działa to pięknie w przypadku wielu przekierowań (HTTPS API -> HTTP -> obraz HTTP)! Idealne proste rozwiązanie.
EricH206
1
@Nathan - dzięki za szczegóły, ale nadal tego nie kupuję. Na przykład, jeśli klient jest pod kontrolą, czy wysyłane są jakiekolwiek poświadczenia lub certyfikaty klienta. Jeśli to boli, nie rób tego (w tym przypadku nie podążaj za przekierowaniem).
Julian Reschke
1
Tylko nie rozumiem location = URLDecoder.decode(location...części. To dekoduje działającą zakodowaną część względną (ze spacją = + w moim przypadku) na niedziałającą. Po usunięciu było dla mnie OK.
Niek
@Niek Nie jestem pewien, dlaczego tego nie potrzebujesz, ale tak.
Nathan
27

Czy przypadkiem coś zostało nazwane HttpURLConnection.setFollowRedirects(false)?

Zawsze możesz zadzwonić

conn.setInstanceFollowRedirects(true);

jeśli chcesz mieć pewność, że nie wpłyniesz na resztę działania aplikacji.

Jon Skeet
źródło
Ooo ... nie wiedziałem o tym ... Niezłe znalezisko ... Właśnie miałem odszukać klasę na wypadek, gdyby istniała taka logika ... To ma sens, że zwracałby ten nagłówek, dając jedyną odpowiedzialność dyrektor… teraz wróć do odpowiadania na pytania C #: P [
Żartuję
2
Należy zauważyć, że metoda setFollowRedirects () powinna być wywoływana w klasie, a nie w instancji.
karlbecker_com
3
@dldnh: Chociaż karlbecker_com miał absolutną rację co do wywoływania setFollowRedirectstypu, setInstanceFollowRedirectsjest metodą instancji i nie można go wywołać na typie.
Jon Skeet
1
brzydko, jak to źle odczytałem. przepraszam za nieprawidłową edycję. również próbowałem wycofać się i nie jestem pewien, jak to również bollocks.
dldnh
7

Jak niektórzy z was wspomnieli powyżej, setFollowRedirect i setInstanceFollowRedirects działają automatycznie tylko wtedy, gdy przekierowany protokół jest taki sam. tj. z http na http i https na https.

setFolloRedirect jest na poziomie klasy i ustawia to dla wszystkich instancji połączenia url, podczas gdy setInstanceFollowRedirects jest tylko dla danej instancji. W ten sposób możemy mieć różne zachowanie w różnych przypadkach.

Znalazłem tutaj bardzo dobry przykład http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Shalvika
źródło
2

Inną opcją może być użycie klienta Apache HttpComponents :

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

Przykładowy kod:

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();
Koray Tugay
źródło
-4

HTTPUrlConnection nie odpowiada za obsługę odpowiedzi obiektu. Działa zgodnie z oczekiwaniami, przechwytuje zawartość żądanego adresu URL. Interpretacja odpowiedzi zależy od użytkownika funkcji. Nie jest w stanie odczytać zamiarów dewelopera bez specyfikacji.

mnich
źródło
7
Dlaczego w tym przypadku ustawiłInstanceFollowRedirects? ))
Shcheklein
Domyślam się, że była to sugerowana funkcja do dodania później, ma to sens ... mój komentarz był bardziej odzwierciedlony w kierunku ... klasa została zaprojektowana tak, aby pobierać treści internetowe i przywracać je ... ludzie mogą chcieć otrzymywać wiadomości inne niż HTTP 200.
mnich