HTTPURL La connessione non segue il reindirizzamento da HTTP a HTTPS


97

Non riesco a capire perché Java HttpURLConnectionnon segue un reindirizzamento HTTP da un HTTP a un URL HTTPS. Uso il codice seguente per ottenere la pagina su 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();
        }
    }
}

L'output di questo programma è:

URL originale: http://httpstat.us/301
Collegato a: http://httpstat.us/301
Codice di risposta HTTP ricevuto: 301
Messaggio di risposta HTTP ricevuto: spostato in modo permanente

Una richiesta a http://httpstat.us/301 restituisce la seguente risposta (abbreviata) (che sembra assolutamente corretta!):

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

Sfortunatamente, Java HttpURLConnectionnon segue il reindirizzamento!

Si noti che se si modifica l'URL originale per HTTPS ( https://httpstat.us/301 ), Java sarà seguire il reindirizzamento come previsto !?


Ciao, ho modificato la tua domanda per chiarezza e per sottolineare che il reindirizzamento a HTTPS in particolare è il problema. Inoltre, ho cambiato il dominio bit.ly in uno diverso, poiché l'uso di bit.ly è nella lista nera delle domande. Spero non ti dispiaccia, sentiti libero di modificare nuovamente.
sleske

Risposte:


119

I reindirizzamenti vengono seguiti solo se utilizzano lo stesso protocollo. (Vedere il followRedirect()metodo nel codice sorgente.) Non è possibile disabilitare questo controllo.

Anche se sappiamo che rispecchia HTTP, dal punto di vista del protocollo HTTP, HTTPS è solo un altro protocollo sconosciuto, completamente diverso. Non sarebbe sicuro seguire il reindirizzamento senza l'approvazione dell'utente.

Ad esempio, supponiamo che l'applicazione sia configurata per eseguire automaticamente l'autenticazione del client. L'utente si aspetta di navigare in modo anonimo perché utilizza HTTP. Ma se il suo client segue HTTPS senza chiedere, la sua identità viene rivelata al server.


60
Grazie. Ho appena trovato la conferma: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . Vale a dire: "Dopo aver discusso tra gli ingegneri di Java Networking, si ritiene che non dovremmo seguire automaticamente il reindirizzamento da un protocollo a un altro, ad esempio, da http a https e viceversa, ciò potrebbe avere gravi conseguenze sulla sicurezza. Pertanto la correzione è per restituire le risposte del server per il reindirizzamento. Controlla il codice di risposta e il valore del campo di intestazione Posizione per informazioni sul reindirizzamento. È responsabilità dell'applicazione seguire il reindirizzamento. "
Shcheklein

2
Ma segue il reindirizzamento da http a http o da https a https? Anche quello sarebbe sbagliato. Non è vero?
Sudarshan Bhat

7
@JoshuaDavis Sì, si applica solo ai reindirizzamenti allo stesso protocollo. Un HttpURLConnectionnon seguirà automaticamente i reindirizzamenti a un protocollo diverso, anche se è impostato il flag di reindirizzamento.
erickson

8
Gli ingegneri di Java Networking potrebbero offrire un'opzione setFollowTransProtocol (true) perché se ne avremo bisogno la programmeremo comunque. FYI browser web, curl e wget e altri potrebbero seguire reindirizzamenti da HTTP a HTTPS e viceversa.
supercobra

18
Nessuno imposta l'accesso automatico su HTTPS e quindi si aspetta che HTTP sia "anonimo". Non ha senso. È perfettamente sicuro e normale seguire i reindirizzamenti da HTTP a HTTPS (non viceversa). Questa è solo un'API Java tipicamente cattiva.
Glenn Maynard

54

HttpURLConnection by design non reindirizzerà automaticamente da HTTP a HTTPS (o viceversa). Seguire il reindirizzamento potrebbe avere gravi conseguenze sulla sicurezza. SSL (da qui HTTPS) crea una sessione che è univoca per l'utente. Questa sessione può essere riutilizzata per più richieste. Pertanto, il server può tenere traccia di tutte le richieste effettuate da una singola persona. Questa è una forma debole di identità ed è sfruttabile. Inoltre, l'handshake SSL può richiedere il certificato del client. Se inviato al server, l'identità del client viene fornita al server.

Come sottolinea Erickson , supponiamo che l'applicazione sia configurata per eseguire automaticamente l'autenticazione del client. L'utente si aspetta di navigare in modo anonimo perché utilizza HTTP. Ma se il suo client segue HTTPS senza chiedere, la sua identità viene rivelata al server.

Il programmatore deve eseguire passaggi aggiuntivi per garantire che le credenziali, i certificati client o l'ID di sessione SSL non vengano inviati prima del reindirizzamento da HTTP a HTTPS. L'impostazione predefinita è inviarli. Se il reindirizzamento danneggia l'utente, non seguire il reindirizzamento. Questo è il motivo per cui il reindirizzamento automatico non è supportato.

Detto ciò, ecco il codice che seguirà i reindirizzamenti.

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

Questa è solo una soluzione che funziona per più di 1 reindirizzamento. Grazie!
Roger Alien

Funziona magnificamente per più reindirizzamenti (API HTTPS -> HTTP -> immagine HTTP)! Soluzione semplice perfetta.
EricH206

1
@ Nathan - grazie per i dettagli, ma continuo a non comprarlo. Ad esempio, se è sotto il controllo del cliente se vengono inviate credenziali o certificati client. Se fa male, non farlo (in questo caso, non seguire il reindirizzamento).
Julian Reschke

1
Solo non capisco la location = URLDecoder.decode(location...parte. Questo decodifica una parte relativa codificata funzionante (con spazio = + nel mio caso) in una non funzionante. Dopo averlo rimosso, per me andava bene.
Niek

@Niek Non sono sicuro del motivo per cui non ne hai bisogno, ma lo faccio.
Nathan

26

Qualcosa ha chiamato HttpURLConnection.setFollowRedirects(false)per caso?

Potresti sempre chiamare

conn.setInstanceFollowRedirects(true);

se vuoi assicurarti di non influenzare il resto del comportamento dell'app.


Ooo ... non lo sapevo ... Bella scoperta ... stavo per cercare la classe nel caso in cui ci fosse una logica del genere .... Ha senso che restituirebbe quell'intestazione dando la singola responsabilità preside .... ora torna a rispondere alle domande di C #: P [sto scherzando]
monksy

2
Nota che setFollowRedirects () dovrebbe essere chiamato sulla classe e non su un'istanza.
karlbecker_com

3
@dldnh: sebbene karlbecker_com avesse assolutamente ragione nel chiamare setFollowRedirectsil tipo, setInstanceFollowRedirectsè un metodo di istanza e non può essere chiamato sul tipo.
Jon Skeet

1
uggh, come l'ho interpretato male. scusa per la modifica errata. Ho anche provato a eseguire il rollback e non sono sicuro di come sia stato anche questo.
dldnh

7

Come accennato da alcuni di voi sopra, setFollowRedirect e setInstanceFollowRedirect funzionano automaticamente solo quando il protocollo reindirizzato è lo stesso. cioè da http a http e da https a https.

setFolloRedirect è a livello di classe e lo imposta per tutte le istanze della connessione URL, mentre setInstanceFollowRedirects è solo per una determinata istanza. In questo modo possiamo avere comportamenti diversi per istanze diverse.

Ho trovato un ottimo esempio qui http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/


2

Un'altra opzione può essere quella di utilizzare Apache HttpComponents Client :

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

Codice di esempio:

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 non è responsabile della gestione della risposta dell'oggetto. Funziona come previsto, acquisisce il contenuto dell'URL richiesto. Spetta all'utente della funzionalità interpretare la risposta. Non è in grado di leggere le intenzioni dello sviluppatore senza specifica.


7
Perché ha setInstanceFollowRedirects in questo caso? ))
Shcheklein

La mia ipotesi è che fosse una funzionalità suggerita da aggiungere in seguito, ha senso .. il mio commento è stato più riflesso verso ... la classe è progettata per andare a prendere i contenuti web e riportarli indietro ... le persone potrebbero volerlo ottenere messaggi non HTTP 200.
monksy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.