HTTPURLConnection folgt nicht der Umleitung von HTTP zu HTTPS

96

Ich kann nicht verstehen, warum Java HttpURLConnectioneiner HTTP-Umleitung von einem HTTP zu einer HTTPS-URL nicht folgt. Ich verwende den folgenden Code, um die Seite unter https://httpstat.us/ abzurufen :

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();
        }
    }
}

Die Ausgabe dieses Programms ist:

Ursprüngliche URL: http://httpstat.us/301
Verbunden mit: http://httpstat.us/301
HTTP-Antwortcode empfangen: 301
Empfangene HTTP-Antwortnachricht: Permanent verschoben

Eine Anfrage an http://httpstat.us/301 gibt die folgende (verkürzte) Antwort zurück (was absolut richtig erscheint!):

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

Leider HttpURLConnectionfolgt Java nicht der Weiterleitung!

Beachten Sie, dass , wenn Sie die ursprüngliche URL zu HTTPS ändern ( https://httpstat.us/301 ), Java wird die Umleitung folgen wie erwartet !?

Shcheklein
quelle
Hallo, ich habe Ihre Frage aus Gründen der Klarheit bearbeitet und darauf hinzuweisen, dass insbesondere die Weiterleitung zu HTTPS das Problem ist. Außerdem habe ich die bit.ly-Domain in eine andere geändert, da die Verwendung von bit.ly in Fragen auf der schwarzen Liste steht. Ich hoffe, es macht Ihnen nichts aus, Sie können es jederzeit erneut bearbeiten.
Sleske

Antworten:

118

Weiterleitungen werden nur befolgt, wenn sie dasselbe Protokoll verwenden. (Siehe die followRedirect()Methode in der Quelle.) Es gibt keine Möglichkeit, diese Prüfung zu deaktivieren.

Obwohl wir wissen, dass es HTTP widerspiegelt, ist HTTPS aus Sicht des HTTP-Protokolls nur ein anderes, völlig anderes, unbekanntes Protokoll. Es wäre unsicher, der Weiterleitung ohne Zustimmung des Benutzers zu folgen.

Angenommen, die Anwendung ist so eingerichtet, dass die Clientauthentifizierung automatisch durchgeführt wird. Der Benutzer erwartet, anonym zu surfen, da er HTTP verwendet. Wenn sein Client jedoch HTTPS folgt, ohne zu fragen, wird seine Identität dem Server mitgeteilt.

erickson
quelle
60
Vielen Dank. Ich habe gerade eine Bestätigung gefunden: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . Nämlich: "Nach einer Diskussion unter Java-Netzwerkingenieuren ist man der Ansicht, dass wir die Umleitung von einem Protokoll zum anderen nicht automatisch verfolgen sollten, beispielsweise von http zu https und umgekehrt, da dies schwerwiegende Sicherheitsfolgen haben kann Überprüfen Sie den Antwortcode und den Wert des Standortheaderfelds auf Umleitungsinformationen. Es liegt in der Verantwortung der Anwendung, der Umleitung zu folgen. "
Shcheklein
2
Aber folgt die Umleitung von http zu http oder https zu https? Auch das wäre falsch. Ist es nicht?
Sudarshan Bhat
7
@JoshuaDavis Ja, dies gilt nur für Weiterleitungen zum gleichen Protokoll. Eine HttpURLConnectionWeiterleitung zu einem anderen Protokoll wird nicht automatisch verfolgt, selbst wenn das Umleitungsflag gesetzt ist.
Erickson
8
Java Networking-Ingenieure könnten eine setFollowTransProtocol-Option (true) anbieten, da wir sie bei Bedarf trotzdem programmieren. Zu Ihrer Information Webbrowser, Curl und Wget und möglicherweise weitere Weiterleitungen von HTTP zu HTTPS und umgekehrt.
Supercobra
18
Niemand richtet die automatische Anmeldung bei HTTPS ein und erwartet dann, dass HTTP "anonym" ist. Das ist unsinnig. Es ist absolut sicher und normal, Weiterleitungen von HTTP zu HTTPS zu verfolgen (nicht umgekehrt). Dies ist nur eine normalerweise schlechte Java-API.
Glenn Maynard
53

HttpURLConnection von Design wird von HTTP zu HTTPS (oder umgekehrt) nicht automatisch umleiten. Das Folgen der Weiterleitung kann schwerwiegende Sicherheitsfolgen haben. SSL (daher HTTPS) erstellt eine Sitzung, die für den Benutzer eindeutig ist. Diese Sitzung kann für mehrere Anforderungen wiederverwendet werden. Auf diese Weise kann der Server alle Anforderungen einer einzelnen Person verfolgen. Dies ist eine schwache Form der Identität und kann ausgenutzt werden. Außerdem kann der SSL-Handshake das Zertifikat des Clients anfordern. Wenn an den Server gesendet, wird die Identität des Clients an den Server übergeben.

Wie Erickson weist darauf hin, nimmt die Anwendung eingerichtet ist , die Client - Authentifizierung automatisch auszuführen. Der Benutzer erwartet, anonym zu surfen, da er HTTP verwendet. Wenn sein Client jedoch HTTPS folgt, ohne zu fragen, wird seine Identität dem Server mitgeteilt.

Der Programmierer muss zusätzliche Schritte unternehmen, um sicherzustellen, dass Anmeldeinformationen, Clientzertifikate oder SSL-Sitzungs-ID nicht gesendet werden, bevor er von HTTP zu HTTPS umleitet. Standardmäßig werden diese gesendet. Wenn die Umleitung dem Benutzer weh tut, folgen Sie nicht der Umleitung. Aus diesem Grund wird die automatische Umleitung nicht unterstützt.

Nachdem dies verstanden wurde, ist hier der Code, der den Weiterleitungen folgt.

  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
quelle
Dies ist nur eine Lösung, die für mehr als 1 Weiterleitungen funktioniert. Danke dir!
Roger Alien
Dies funktioniert wunderbar für mehrere Weiterleitungen (HTTPS-API -> HTTP -> HTTP-Image)! Perfekte einfache Lösung.
EricH206
1
@ Nathan - danke für die Details, aber ich kaufe es immer noch nicht. Zum Beispiel, wenn unter der Kontrolle des Clients steht, ob Anmeldeinformationen oder Client-Zertifikate gesendet werden. Wenn es weh tut, tun Sie es nicht (in diesem Fall folgen Sie nicht der Weiterleitung).
Julian Reschke
1
Ich verstehe den location = URLDecoder.decode(location...Teil nur nicht . Dies dekodiert einen funktionierenden codierten relativen Teil (in meinem Fall mit Leerzeichen = +) in einen nicht funktionierenden. Nachdem ich es entfernt hatte, war es für mich in Ordnung.
Niek
@Niek Ich bin mir nicht sicher, warum du es nicht brauchst, aber ich tue es.
Nathan
26

Hat etwas HttpURLConnection.setFollowRedirects(false)zufällig angerufen ?

Sie können immer anrufen

conn.setInstanceFollowRedirects(true);

Wenn Sie sicherstellen möchten, dass Sie den Rest des Verhaltens der App nicht beeinflussen.

Jon Skeet
quelle
Ooo ... wusste nichts davon ... Netter Fund ... Ich wollte gerade die Klasse nachschlagen, falls es eine solche Logik gibt ... Es macht Sinn, dass es diesen Header zurückgeben würde, der die einzige Verantwortung trägt Schulleiter .... jetzt zurück zur Beantwortung von C #
-Fragen
2
Beachten Sie, dass setFollowRedirects () für die Klasse und nicht für eine Instanz aufgerufen werden sollte.
karlbecker_com
3
@dldnh: Während karlbecker_com beim Aufrufen setFollowRedirectsdes Typs absolut Recht hatte , setInstanceFollowRedirectshandelt es sich um eine Instanzmethode , die für den Typ nicht aufgerufen werden kann.
Jon Skeet
1
uggh, wie habe ich das falsch verstanden? Entschuldigung für die falsche Bearbeitung. Ich habe auch versucht, einen Rollback durchzuführen und bin mir nicht sicher, wie ich das auch verpatzt habe.
dldnh
7

Wie von einigen von Ihnen oben erwähnt, funktionieren setFollowRedirect und setInstanceFollowRedirects nur dann automatisch, wenn das umgeleitete Protokoll identisch ist. dh von http zu http und https zu https.

setFolloRedirect befindet sich auf Klassenebene und legt dies für alle Instanzen der URL-Verbindung fest, während setInstanceFollowRedirects nur für eine bestimmte Instanz gilt. Auf diese Weise können wir für verschiedene Instanzen ein unterschiedliches Verhalten haben.

Ich habe hier ein sehr gutes Beispiel gefunden http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Shalvika
quelle
2

Eine andere Option kann die Verwendung des Apache HttpComponents Client sein :

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

Beispielcode:

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
quelle
-4

HTTPUrlConnection ist nicht für die Verarbeitung der Antwort des Objekts verantwortlich. Es ist Leistung wie erwartet, es erfasst den Inhalt der angeforderten URL. Es liegt an Ihnen, dem Benutzer der Funktionalität, die Antwort zu interpretieren. Es ist nicht in der Lage, die Absichten des Entwicklers ohne Spezifikation zu lesen.

Mönch
quelle
7
Warum hat es in diesem Fall setInstanceFollowRedirects? ))
Shcheklein
Ich vermute, dass es eine vorgeschlagene Funktion war, die später hinzugefügt werden sollte. Es macht Sinn. Mein Kommentar war eher reflektiert in Richtung ... Die Klasse ist so konzipiert, dass sie Webinhalte abruft und zurückbringt ... die Leute möchten vielleicht Nicht-HTTP 200-Nachrichten abrufen.
Mönch