HTTPURLConnection ne suit pas la redirection de HTTP vers HTTPS

97

Je ne comprends pas pourquoi Java HttpURLConnectionne suit pas une redirection HTTP d'un HTTP vers une URL HTTPS. J'utilise le code suivant pour obtenir la page à l' adresse 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();
        }
    }
}

Le résultat de ce programme est:

URL d'origine: http://httpstat.us/301
Connecté à: http://httpstat.us/301
Code de réponse HTTP reçu: 301
Message de réponse HTTP reçu: déplacé définitivement

Une requête à http://httpstat.us/301 renvoie la réponse (abrégée) suivante (qui semble tout à fait juste!):

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

Malheureusement, Java HttpURLConnectionne suit pas la redirection!

Notez que si vous modifiez l'URL d' origine HTTPS ( https://httpstat.us/301 ), Java va suivre la redirection comme prévu !?

Shcheklein
la source
Salut, j'ai édité votre question pour plus de clarté et pour souligner que la redirection vers HTTPS en particulier est le problème. De plus, j'ai changé le domaine bit.ly en un domaine différent, car use bit.ly est sur liste noire dans les questions. J'espère que cela ne vous dérange pas, n'hésitez pas à rééditer.
sleske

Réponses:

119

Les redirections ne sont suivies que si elles utilisent le même protocole. (Voir la followRedirect()méthode dans la source.) Il n'y a aucun moyen de désactiver cette vérification.

Même si nous savons qu'il reflète HTTP, du point de vue du protocole HTTP, HTTPS n'est qu'un autre protocole complètement 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 effectuer automatiquement l'authentification client. L'utilisateur s'attend à surfer de manière anonyme car il utilise HTTP. Mais si son client suit HTTPS sans demander, son identité est révélée au serveur.

Erickson
la source
60
Merci. Je viens de trouver la confirmation: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . À savoir: «Après discussion entre les ingénieurs de Java Networking, il est estimé que nous ne devrions pas suivre automatiquement la redirection d'un protocole à un autre, par exemple, de http vers https et vice versa, cela peut avoir de graves conséquences sur la sécurité. Ainsi, le correctif est pour renvoyer les réponses du serveur pour la redirection. Vérifiez le code de réponse et la valeur du champ d'en-tête Emplacement pour les informations de redirection. Il est de la responsabilité de l'application de suivre la redirection. "
Shcheklein
2
Mais suit-il la redirection de http vers http ou https vers https? Même cela serait faux. N'est-ce pas?
Sudarshan Bhat
7
@JoshuaDavis Oui, cela ne s'applique qu'aux redirections vers le même protocole. An HttpURLConnectionne suivra pas automatiquement les redirections vers un protocole différent, même si l'indicateur de redirection est défini.
erickson le
8
Les ingénieurs Java Networking pourraient proposer une option setFollowTransProtocol (true) car si nous en avons besoin, nous la programmerons quand même. Les navigateurs Web FYI, curl et wget et peuvent plus suivre les redirections de HTTP vers HTTPS et vice-versa.
supercobra du
18
Personne ne met en place une connexion automatique sur HTTPS et s'attend ensuite à ce que HTTP soit "anonyme". C'est absurde. Il est parfaitement sûr et normal de suivre les redirections de HTTP vers HTTPS (et non l'inverse). Ceci est juste une API Java généralement mauvaise.
Glenn Maynard
54

HttpURLConnection par conception ne redirigera pas automatiquement de HTTP vers HTTPS (ou vice versa). La suite de la redirection peut avoir de graves conséquences sur la sécurité. SSL (donc HTTPS) crée une session qui est unique à 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. C'est une forme d'identité faible et exploitable. En outre, la négociation SSL peut demander le certificat du client. Si elle est envoyée au serveur, l'identité du client est donnée au serveur.

Comme le souligne erickson , supposons que l'application soit configurée pour effectuer automatiquement l'authentification client. L'utilisateur s'attend à surfer de manière anonyme 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. La valeur par 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.

Cela dit, 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();
  ...
Nathan
la source
Ce n'est qu'une solution qui fonctionne pour plus d'une redirection. Je vous remercie!
Roger Alien
Cela fonctionne à merveille pour plusieurs redirections (API HTTPS -> HTTP -> image HTTP)! Solution simple parfaite.
EricH206
1
@Nathan - merci pour les détails, mais je ne l'achète toujours pas. Par exemple, si est sous le contrôle du client si des informations d'identification ou des certificats de client sont envoyés. Si ça fait mal, ne le faites pas (dans ce cas, ne suivez pas la redirection).
Julian Reschke
1
Je ne comprends seulement pas la location = URLDecoder.decode(location...partie. Cela décode une partie relative codée de travail (avec espace = + dans mon cas) en une partie non fonctionnelle. Après l'avoir retiré, c'était OK pour moi.
Niek
@Niek Je ne sais pas pourquoi vous n'en avez pas besoin, mais je le fais.
Nathan
26

A quelque chose appelé HttpURLConnection.setFollowRedirects(false) par hasard?

Tu pourrais toujours appeler

conn.setInstanceFollowRedirects(true);

si vous voulez vous assurer de ne pas affecter le reste du comportement de l'application.

Jon Skeet
la source
Ooo ... je ne savais pas à ce sujet ... Belle découverte ... J'étais sur le point de rechercher la classe au cas où il y aurait une logique comme celle-là ... Il est logique que cela renvoie cet en-tête donnant la responsabilité unique principal .... revenez maintenant aux questions en C #: P [Je plaisante]
monksy
2
Notez que setFollowRedirects () doit être appelé sur la classe, et non sur une instance.
karlbecker_com
3
@dldnh: Bien que karlbecker_com ait absolument raison d'appeler setFollowRedirectsle type, setInstanceFollowRedirectsc'est une méthode d' instance et ne peut pas être appelée sur le type.
Jon Skeet
1
uggh, comment ai-je mal lu cela. désolé pour la modification incorrecte. J'ai également essayé de revenir en arrière et je ne sais pas comment je l'ai également bloqué.
dldnh
7

Comme mentionné par certains d'entre vous ci-dessus, les instructions 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 définit ceci pour toutes les instances de la connexion url, alors que setInstanceFollowRedirects est uniquement pour 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/

Shalvika
la source
2

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

<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();
Koray Tugay
la source
-4

HTTPUrlConnection n'est pas responsable de la gestion de la réponse de l'objet. C'est des performances comme prévu, il saisit le contenu de l'URL demandée. C'est à vous l'utilisateur de la fonctionnalité d'interpréter la réponse. Il n'est pas capable de lire les intentions du développeur sans spécification.

monksy
la source
7
Pourquoi a-t-il setInstanceFollowRedirects dans ce cas? ))
Shcheklein
Je suppose que c'était une fonctionnalité suggérée à ajouter plus tard, cela a du sens ... mon commentaire était davantage reflété vers ... la classe est conçue pour récupérer du contenu Web et le ramener ... les gens voudront peut-être obtenir des messages non HTTP 200.
monksy