HTTPURLConnection不会从HTTP重定向到HTTPS

115

我不明白为什么Java的HttpURLConnection不能跟随从HTTP重定向到HTTPS URL。我使用以下代码获取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();
        }
    }
}
这个程序的输出是:
原始网址:http://httpstat.us/301
连接到:http://httpstat.us/301
收到HTTP响应代码:301
收到HTTP响应消息:永久移动
发出对 http://httpstat.us/301 的请求返回了以下(被缩短的)响应(看起来绝对正确!)。
HTTP/1.1 301 Moved Permanently
Cache-Control: private
Content-Length: 21
Content-Type: text/plain; charset=utf-8
Location: https://httpstat.us

不幸的是,Java的HttpURLConnection不会遵循重定向!

请注意,如果您将原始URL更改为HTTPS(https://httpstat.us/301),Java将按预期跟随重定向!?


1
嗨,我为了更清晰地表达你的问题并指出重定向到HTTPS是问题所在而编辑了它。此外,我更改了bit.ly域名为另一个域名,因为在问题中使用bit.ly已被列入黑名单。希望你不介意,随时可以重新编辑。 - sleske
6个回答

135

只有在使用相同协议的情况下,才会跟随重定向。(请参阅源代码中的 followRedirect() 方法。) 没有办法禁用此检查。

尽管我们知道 HTTPS 是 HTTP 的镜像,但从 HTTP 协议的角度来看,HTTPS 只是另一种完全不同、未知的协议。如果没有用户的批准,跟随重定向将是不安全的。

例如,假设应用程序设置为自动执行客户端认证。用户期望匿名浏览,因为他使用的是 HTTP。但是,如果客户端在没有询问的情况下跟随 HTTPS,则他的身份将被透露给服务器。


65
谢谢。我刚刚找到了确认内容:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571。具体地说:“在Java网络工程师讨论后,我们认为不应自动跟随从一个协议到另一个协议的重定向,例如从HTTP到HTTPS或反之,这样做可能会有严重的安全后果。因此,修复方案是返回服务器的重定向响应。检查响应代码和Location头字段值以获取重定向信息,跟随重定向是应用程序的责任。” - Shcheklein
2
但它是否遵循从HTTP到HTTP或从HTTPS到HTTPS的重定向呢?即使如此也是错误的,不是吗? - Sudarshan Bhat
7
是的,它仅适用于重定向到相同协议的情况。即使设置了重定向标志,HttpURLConnection 也不会自动跟随到不同协议的重定向。 - erickson
10
Java网络工程师可以提供一个setFollowTransProtocol(true)选项,因为如果我们需要它,我们无论如何都会编写它。告诉你一下,Web浏览器、curl和wget等工具会自动跟踪HTTP到HTTPS或反之的重定向。 - supercobra
22
没人会在启用HTTPS自动登录后期待HTTP保持“匿名”。这是毫无意义的。从HTTP重定向到HTTPS是完全安全和正常的(反过来则不行)。这只是一个典型的糟糕Java API。 - Glenn Maynard
显示剩余4条评论

67

design设计的HttpURLConnection不会自动从HTTP重定向到HTTPS(或反之亦然)。跟随重定向可能会产生严重的安全后果。SSL(因此HTTPS)创建了一个对用户唯一的会话。这个会话可以用于多个请求。因此,服务器可以跟踪单个人发出的所有请求。这是一种弱身份验证形式,是可利用的。此外,SSL握手可以要求客户端的证书。如果发送到服务器,则客户端的身份将被提供给服务器。

正如erickson所指出的那样,假设应用程序设置为自动执行客户端身份验证。用户希望匿名浏览,因为他正在使用HTTP。但是,如果他的客户端在未经询问的情况下遵循HTTPS,则他的身份将被揭示给服务器。

程序员必须采取额外的措施,确保在从HTTP重定向到HTTPS之前不会发送凭据、客户端证书或SSL会话ID。默认情况下会发送这些信息。如果重定向对用户造成伤害,请不要遵循重定向。这就是为什么不支持自动重定向的原因。
了解了这一点,以下是将跟随重定向的代码。
  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();
  ...

这只是一个适用于多重重定向的解决方案。谢谢! - Roger Alien
这对于多个重定向(HTTPS API -> HTTP -> HTTP 图像)非常有效!完美的简单解决方案。 - EricH206
1
@Nathan - 感谢您提供的详细信息,但我仍然不信。例如,客户端控制是否发送任何凭据或客户端证书。如果有害,请不要这样做(在这种情况下,不要遵循重定向)。 - Julian Reschke
1
我只是不理解 location = URLDecoder.decode(location... 这部分。这将一个已编码的工作相对路径(在我的情况下,空格= +)解码为一个不起作用的路径。在我删除它之后,对我来说就没问题了。 - Niek
@Niek,我不确定为什么你不需要它,但我需要。 - Nathan
Niek是正确的,必须删除location = URLDecoder.decode(location, "UTF-8");,如果您的URL包含多字节字符,它将导致错误。在我的情况下,文件名「LR-001A-序.mp3」是我原始的下载链接,当执行「location = conn.getHeaderField("Location");」时,它变成了「LR-001B-%E5%BA%8F.mp3」,如果您将该字符串作为下一个连接的URL,则是正确的,但在执行「location = URLDecoder.decode(location, "UTF-8");」之后,它变成了「LR-001B-?.mp3」,这是错误的,最终会得到404错误。 - Eyes Blue

26

有没有类似 HttpURLConnection.setFollowRedirects(false) 的东西呢?

你可以随时调用它。

conn.setInstanceFollowRedirects(true);

如果你想确保不影响应用程序的其余行为。


哦哦...不知道那个...不错的发现...我正想查找类是否有类似逻辑....它返回该标题符合单一职责原则...现在回答C#问题吧 :P [开玩笑的] - monksy
2
请注意,setFollowRedirects() 应该在类上调用,而不是在实例上调用。 - karlbecker_com
3
@dldnh说:虽然karlbecker_com在调用setFollowRedirects时是正确的,但是setInstanceFollowRedirects是一个实例方法,不能在类上调用。 - Jon Skeet
1
唉,我怎么会误读那个。对于错误的编辑感到抱歉。我还试图回滚,但不确定我也弄糟了那个。 - dldnh

7

正如一些人所提到的那样,setFollowRedirect和setInstanceFollowRedirects只有在重定向协议相同时才会自动工作。即从http到http,从https到https。

setFolloRedirect是在类级别上设置所有url连接实例的,而setInstanceFollowRedirects仅针对特定实例。这样我们可以为不同的实例设置不同的行为。

我在这里找到了一个非常好的例子http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/


6

另一个选择是使用Apache HttpComponents Client

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

示例代码:

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("https://media-hearth.cursecdn.com/avatars/330/498/212.png");
CloseableHttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
InputStream is = entity.getContent();

-5

HTTPUrlConnection 不负责处理对象的响应。它的表现如预期,获取所请求的 URL 的内容。如何解释响应取决于功能的使用者。如果没有明确规定,它无法读取开发人员的意图。


8
为什么在这种情况下要设置 setInstanceFollowRedirects? - Shcheklein
我的猜测是这是一个建议的功能,可以在以后添加,这很有道理。我的评论更多地反映了...这个类被设计成去获取Web内容并将其带回来...人们可能想要获取非HTTP 200消息。 - monksy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接