HTTPURLConnection no sigue la redirección de HTTP a HTTPS

97

No puedo entender por qué Java HttpURLConnectionno sigue un redireccionamiento HTTP de un HTTP a una URL HTTPS. Utilizo el siguiente código para obtener la página en 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();
        }
    }
}

El resultado de este programa es:

URL original: http://httpstat.us/301
Conectado a: http://httpstat.us/301
Código de respuesta HTTP recibido: 301
Mensaje de respuesta HTTP recibido: Movido permanentemente

Una solicitud a http://httpstat.us/301 devuelve la siguiente respuesta (abreviada) (¡que parece absolutamente correcta!):

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

Desafortunadamente, Java HttpURLConnectionno sigue la redirección.

Tenga en cuenta que si cambia la dirección URL original a HTTPS ( https://httpstat.us/301 ), Java va a seguir la redirección como se esperaba !?

Shcheklein
fuente
Hola, edité tu pregunta para mayor claridad y para señalar que la redirección a HTTPS en particular es el problema. Además, cambié el dominio bit.ly a uno diferente, ya que use bit.ly está en la lista negra en las preguntas. Espero que no te importe, no dudes en volver a editar.
sábado

Respuestas:

119

Los redireccionamientos se siguen solo si usan el mismo protocolo. (Consulte el followRedirect()método en la fuente). No hay forma de deshabilitar esta verificación.

Aunque sabemos que refleja HTTP, desde el punto de vista del protocolo HTTP, HTTPS es solo otro protocolo desconocido completamente diferente. No sería seguro seguir la redirección sin la aprobación del usuario.

Por ejemplo, suponga que la aplicación está configurada para realizar la autenticación del cliente automáticamente. El usuario espera navegar de forma anónima porque usa HTTP. Pero si su cliente sigue HTTPS sin preguntar, su identidad se revela al servidor.

erickson
fuente
60
Gracias. Acabo de encontrar la confirmación: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . A saber: "Después de una discusión entre los ingenieros de Java Networking, se cree que no deberíamos seguir automáticamente la redirección de un protocolo a otro, por ejemplo, de http a https y viceversa, hacerlo puede tener graves consecuencias de seguridad. Por lo tanto, la solución es para devolver las respuestas del servidor para la redirección. Compruebe el código de respuesta y el valor del campo de encabezado de ubicación para obtener información sobre la redirección. Es responsabilidad de la aplicación seguir la redirección ".
Shcheklein
2
Pero, ¿sigue la redirección de http a http o de https a https? Incluso eso estaría mal. ¿No es así?
Sudarshan Bhat
7
@JoshuaDavis Sí, solo se aplica a redireccionamientos al mismo protocolo. No HttpURLConnectionseguirá automáticamente las redirecciones a un protocolo diferente, incluso si se establece la marca de redirección.
erickson
8
Los ingenieros de Java Networking podrían ofrecer una opción setFollowTransProtocol (true) porque si la necesitamos la programaremos de todos modos. Para su información, los navegadores web, curl y wget y pueden seguir redireccionamientos de HTTP a HTTPS y viceversa.
supercobra
18
Nadie configura el inicio de sesión automático en HTTPS y luego espera que HTTP sea "anónimo". Eso es una tontería. Es perfectamente seguro y normal seguir las redirecciones de HTTP a HTTPS (no al revés). Esta es solo una API de Java normalmente mala.
Glenn Maynard
54

HttpURLConnection por diseño no redirigirá automáticamente de HTTP a HTTPS (o viceversa). Seguir la redirección puede tener graves consecuencias para la seguridad. SSL (de ahí HTTPS) crea una sesión que es única para el usuario. Esta sesión se puede reutilizar para múltiples solicitudes. Por lo tanto, el servidor puede rastrear todas las solicitudes realizadas por una sola persona. Ésta es una forma débil de identidad y es explotable. Además, el protocolo de enlace SSL puede solicitar el certificado del cliente. Si se envía al servidor, la identidad del cliente se le da al servidor.

Como señala Erickson , suponga que la aplicación está configurada para realizar la autenticación del cliente automáticamente. El usuario espera navegar de forma anónima porque usa HTTP. Pero si su cliente sigue HTTPS sin preguntar, su identidad se revela al servidor.

El programador debe tomar medidas adicionales para asegurarse de que las credenciales, los certificados de cliente o la identificación de sesión SSL no se envíen antes de redirigir de HTTP a HTTPS. El valor predeterminado es enviarlos. Si la redirección perjudica al usuario, no siga la redirección. Por eso no se admite el redireccionamiento automático.

Con eso entendido, aquí está el código que seguirá las redirecciones.

  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
fuente
Esta es solo una solución que funciona para más de 1 redireccionamiento. ¡Gracias!
Roger Alien
¡Esto funciona maravillosamente para múltiples redirecciones (API HTTPS -> HTTP -> imagen HTTP)! Solución sencilla perfecta.
EricH206
1
@Nathan: gracias por los detalles, pero todavía no me lo creo. Por ejemplo, si está bajo el control del cliente si se envían credenciales o certificados de cliente. Si te duele, no lo hagas (en este caso, no sigas la redirección).
Julian Reschke
1
Solo que no entiendo la location = URLDecoder.decode(location...parte. Esto decodifica una parte relativa codificada de trabajo (con espacio = + en mi caso) en una que no funciona. Después de que lo quité, estaba bien para mí.
Niek
@Niek No estoy seguro de por qué no lo necesitas, pero yo sí.
Nathan
26

¿Ha llamado algo HttpURLConnection.setFollowRedirects(false)por casualidad?

Siempre puedes llamar

conn.setInstanceFollowRedirects(true);

si desea asegurarse de no afectar el resto del comportamiento de la aplicación.

Jon Skeet
fuente
Ooo ... no sabía sobre eso ... Buen hallazgo ... Estaba a punto de buscar la clase en caso de que hubiera una lógica como esa ... Tiene sentido que devuelva ese encabezado dando la única responsabilidad director .... ahora vuelva a responder las preguntas de C #: P [Estoy bromeando]
monksy
2
Tenga en cuenta que setFollowRedirects () debe invocarse en la clase y no en una instancia.
karlbecker_com
3
@dldnh: Aunque karlbecker_com tenía toda la razón al llamar setFollowRedirectsal tipo, setInstanceFollowRedirectses un método de instancia y no se puede llamar al tipo.
Jon Skeet
1
uggh, ¿cómo leí mal eso? perdón por la edición incorrecta. También traté de retroceder y no estoy seguro de cómo lo hice.
dldnh
7

Como algunos de ustedes mencionaron anteriormente, setFollowRedirect y setInstanceFollowRedirects solo funcionan automáticamente cuando el protocolo redirigido es el mismo. es decir, de http a http y de https a https.

setFolloRedirect está en el nivel de clase y establece esto para todas las instancias de la conexión de URL, mientras que setInstanceFollowRedirects es solo para una instancia determinada. De esta forma podemos tener un comportamiento diferente para diferentes instancias.

Encontré un muy buen ejemplo aquí http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Shalvika
fuente
2

Otra opción puede ser utilizar Apache HttpComponents Client :

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

Código de muestra:

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

HTTPUrlConnection no es responsable de manejar la respuesta del objeto. Tiene el rendimiento esperado, captura el contenido de la URL solicitada. Depende de usted, el usuario de la funcionalidad, interpretar la respuesta. No es capaz de leer las intenciones del desarrollador sin especificación.

monje
fuente
7
¿Por qué ha establecido setInstanceFollowRedirects en este caso? ))
Shcheklein
Supongo que fue una función sugerida para agregar más tarde, tiene sentido ... mi comentario se reflejó más en ... la clase está diseñada para ir y tomar contenido web y traerlo de vuelta ... la gente puede querer obtener mensajes que no sean HTTP 200.
Monksy