web-dev-qa-db-fra.com

URLConnection ne suit pas la redirection

Je ne comprends pas pourquoi la HttpURLConnection de Java ne suit pas la redirection. J'utilise le code suivant pour obtenir cette page :

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 bitlyUrl = "http://bit.ly/4hW294";
            URL resourceUrl = new URL(bitlyUrl);
            HttpURLConnection conn = (HttpURLConnection)resourceUrl.openConnection();
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(15000);
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)");
            conn.connect();
            is = conn.getInputStream();
            String res = conn.getURL().toString();
            if (res.toLowerCase().contains("bit.ly"))
                System.out.println("bit.ly is after resolving: "+res);
       }
       catch (Exception e) {
           System.out.println("error happened: "+e.toString());
       }
       finally {
            if (is != null) is.close(); 
        }
    }
}

De plus, j'obtiens la réponse suivante (cela semble absolument juste!):

GET /4hW294 HTTP/1.1
Host: bit.ly
Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; ru-RU; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
HTTP/1.1 301 Moved
Server: nginx/0.7.42
Date: Thu, 10 Dec 2009 20:28:44 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Location: https://www.myganocafe.com/CafeMacy
MIME-Version: 1.0
Content-Length: 297

Malheureusement, la variable res contient la même URL et le flux contient les éléments suivants (évidemment, la HttpURLConnection de Java ne suit pas la redirection!):

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
<TITLE>Moved</TITLE>
</HEAD>
<BODY>
<H2>Moved</H2>
<A HREF="https://www.myganocafe.com/CafeMacy">The requested URL has moved here.</A>
<P ALIGN=RIGHT><SMALL><I>AOLserver/4.5.1 on http://127.0.0.1:7400</I></SMALL></P>
</BODY>
</HTML>
90
Shcheklein

Je ne pense pas qu'il va automatiquement rediriger de HTTP vers HTTPS (ou vice-versa).

Bien que nous sachions qu'il reflète le protocole HTTP, du point de vue du protocole HTTP, HTTPS n’est en fait qu’un autre protocole, totalement différent et inconnu. Il serait dangereux de suivre la redirection sans l'approbation de l'utilisateur.

Par exemple, supposons que l'application soit configurée pour exécuter automatiquement l'authentification du client. L'utilisateur s'attend à surfer anonymement car il utilise HTTP. Mais si son client suit HTTPS sans demander, son identité est révélée au serveur.

109
erickson

HttpURLConnection by design ne redirigera pas automatiquement de HTTP vers HTTPS (ou vice versa). Suivre la redirection peut avoir de graves conséquences sur la sécurité. SSL (donc HTTPS) crée une session unique pour l'utilisateur. Cette session peut être réutilisée pour plusieurs demandes. Ainsi, le serveur peut suivre toutes les demandes faites par une seule personne. Ceci est une forme d'identité faible et est exploitable. En outre, la négociation SSL peut demander le certificat du client. S'il est envoyé au serveur, l'identité du client lui est transmise.

Comme erickson le fait remarquer, supposons que l'application est configurée pour effectuer automatiquement l'authentification du client. L'utilisateur s'attend à surfer anonymement car il utilise HTTP. Mais si son client suit HTTPS sans demander, son identité est révélée au serveur.

Le programmeur doit prendre des mesures supplémentaires pour s'assurer que les informations d'identification, les certificats clients ou l'identifiant de session SSL ne seront pas envoyés avant la redirection de HTTP vers HTTPS. Le défaut est de les envoyer. Si la redirection blesse l'utilisateur, ne suivez pas la redirection. C'est pourquoi la redirection automatique n'est pas prise en charge.

Ceci compris, voici le code qui suivra les redirections.

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

Quelque chose a appelé HttpURLConnection.setFollowRedirects(false) par hasard?

Vous pouvez toujours appeler

conn.setInstanceFollowRedirects(true);

si vous voulez vous assurer que vous n'affectez pas le reste du comportement de l'application.

26
Jon Skeet

Comme mentionné par certains d'entre vous ci-dessus, les ensembles setFollowRedirect et setInstanceFollowRedirect ne fonctionnent automatiquement que lorsque le protocole redirigé est le même. c'est-à-dire de http à http et https à https.

setFolloRedirect est au niveau de la classe et la définit pour toutes les instances de la connexion url, alors que setInstanceFollowRedirects ne s'applique qu'à une instance donnée. De cette façon, nous pouvons avoir un comportement différent pour différentes instances.

J'ai trouvé un très bon exemple ici http://www.mkyong.com/Java/java-httpurlconnection-follow-redirect-example/

6
Shalvika

Une autre option peut être d'utiliser Client Apache HttpComponents:

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

Exemple de code:

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();
2
Koray Tugay