HTTPURLConnection不跟随从HTTP重定向到HTTPS


97

我不明白为什么Java HttpURLConnection不会遵循从HTTP到HTTPS URL的HTTP重定向。我使用以下代码在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 按照预期进行重定向!


嗨,为了清楚起见,我编辑了您的问题,并特别指出了重定向到HTTPS的问题。另外,我将bit.ly域更改为另一个域,因为问题中将使用bit.ly列入了黑名单。希望您不介意,随时重新编辑。
sleske

Answers:


119

仅当重定向使用相同的协议时,才遵循重定向。(参见followRedirect()方法源代码中。)无法禁用此检查。

即使我们知道它是HTTP的镜像,但从HTTP协议的角度来看,HTTPS还是其他完全不同的未知协议。未经用户批准而遵循重定向将是不安全的。

例如,假设将应用程序设置为自动执行客户端身份验证。用户期望匿名访问,因为他正在使用HTTP。但是,如果他的客户不经询问就遵循HTTPS,则他的身份将显示给服务器。


60
谢谢。我刚刚发现了约束:bugs.sun.com/bugdatabase/view_bug.do ? bug_id= 4620571。即:“经过Java Networking工程师的讨论,我们认为我们不应该自动遵循从一个协议到另一个协议(例如,从http到https,反之亦然)的重定向,这样做可能会带来严重的安全后果。因此,此修复程序是返回服务器响应以进行重定向。请检查响应代码和Location标头字段的值以获取重定向信息。应用程序有责任遵循重定向。”
Shcheklein

2
但是,它是否遵循从http重定向到http或从https重定向到https的重定向?即使那样也将是错误的。是不是
Sudarshan Bhat 2012年

7
@JoshuaDavis是的,它仅适用于重定向到同一协议。一个HttpURLConnection不会自动执行重定向到一个不同的协议,即使重定向标志被设置。
erickson

8
Java Networking工程师可以提供setFollowTransProtocol(true)选项,因为如果需要,我们将对其进行编程。FYI Web浏览器,curl和wget,并且可能更多地遵循从HTTP重定向到HTTPS的过程,反之亦然。
超级眼镜蛇2014年

18
没有人在HTTPS上设置自动登录,然后期望HTTP为“匿名”。那是荒谬的。从HTTP重定向到HTTPS(并非相反)是完全安全和正常的。这只是一个典型的不良Java API。
格伦·梅纳德

54

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

这只是一种可用于多个重定向的解决方案。谢谢!
罗杰·阿里恩

对于多次重定向(HTTPS API-> HTTP-> HTTP映像),它可以很好地工作!完美的简单解决方案。
EricH206'1

1
@Nathan-感谢您提供详细信息,但我还是不买。例如,如果在客户端的控制下,是否发送任何凭据或客户端证书。如果很痛,请不要这样做(在这种情况下,请勿遵循重定向)。
朱利安·雷施克

1
我只是不明白那location = URLDecoder.decode(location...部分。这会将有效的已编码相对部分(在我的情况下为space = +)解码为无效部分。我删除它后,对我来说还可以。
尼克

@Niek我不确定您为什么不需要它,但我知道。
弥敦道

26

有东西叫 HttpURLConnection.setFollowRedirects(false)偶然的机会吗?

你总是可以打电话

conn.setInstanceFollowRedirects(true);

如果您想确保自己不影响应用程序的其他行为。


噢...不知道...很好的发现...我要在类有逻辑的情况下查找类...。有意义的是,它将返回该标头以承担单个责任校长....现在回到回答C#问题:P [我在开玩笑]
僧侣

2
请注意,应该在类而不是实例上调用setFollowRedirects()。
karlbecker_com

3
@dldnh:虽然karlbecker_com绝对正确setFollowRedirects,但它setInstanceFollowRedirects是一个实例方法,不能在类型上调用。
乔恩·斯基特

1
哎呀,我是怎么看错的。抱歉,编辑错误。还尝试回滚,并且不确定我该如何支持。
dldnh

7

如上面的某些人所提到的,只有在重定向协议相同的情况下,setFollowRedirect和setInstanceFollowRedirects才自动工作。即从http到http和https到https。

setFolloRedirect在类级别,并为url连接的所有实例设置此值,而setInstanceFollowRedirects仅用于给定实例。这样,我们可以针对不同的实例具有不同的行为。

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


2

另一个选择是使用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);
final HttpEntity entity = response.getEntity();
final InputStream is = entity.getContent();

-4

HTTPUrlConnection不负责处理对象的响应。这是预期的性能,它可以获取所请求URL的内容。解释响应的功能取决于用户。没有规范就无法阅读开发者的意图。


7
为什么在这种情况下有setInstanceFollowRedirects?))
Shcheklein

我的猜测是,这是一个建议的功能,稍后再添加,这是有道理的。.我的评论更多地反映在...该类旨在获取Web内容并将其带回...人们可能想要获取非HTTP 200消息。
僧侣
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.