【问题标题】:HTTPURLConnection Doesn't Follow Redirect from HTTP to HTTPSHTTPURLConnection 不遵循从 HTTP 到 HTTPS 的重定向
【发布时间】:2010-12-25 10:46:21
【问题描述】:

我不明白为什么 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 在问题中被列入黑名单。希望您不介意,请随时重新编辑。

标签: java redirect https httpurlconnection http-redirect


【解决方案1】:

仅当它们使用相同的协议时才会遵循重定向。 (请参阅源代码中的the followRedirect() method。)无法禁用此检查。

尽管我们知道它反映了 HTTP,但从 HTTP 协议的角度来看,HTTPS 只是另一种完全不同的未知协议。未经用户批准就跟随重定向是不安全的。

例如,假设应用程序设置为自动执行客户端身份验证。用户希望匿名上网,因为他使用的是 HTTP。但是,如果他的客户端在不询问的情况下使用 HTTPS,他的身份就会暴露给服务器。

【讨论】:

  • 谢谢。我刚刚找到了确认:bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571。即:“经过Java网络工程师的讨论,我们认为我们不应该自动从一种协议重定向到另一种协议,例如从http到https,反之亦然,这样做可能会产生严重的安全后果。因此修复是返回服务器响应以进行重定向。检查重定向信息的响应代码和 Location 标头字段值。遵循重定向是应用程序的责任。"
  • 但它是否遵循从 http 到 http 或 https 到 https 的重定向?即使这样也是错误的。不是吗?
  • @JoshuaDavis 是的,它仅适用于重定向到同一协议。即使设置了重定向标志,HttpURLConnection 也不会自动跟随重定向到不同的协议。
  • Java 网络工程师可以提供一个 setFollowTransProtocol(true) 选项,因为如果我们需要它,我们无论如何都会对其进行编程。仅供参考的网络浏览器,curl 和 wget,可能更多地遵循从 HTTP 到 HTTPS 的重定向,反之亦然。
  • 没有人在 HTTPS 上设置自动登录,然后期望 HTTP 是“匿名的”。这很荒谬。遵循从 HTTP 到 HTTPS 的重定向是完全安全和正常的(而不是相反)。这只是一个典型的糟糕 Java API。
【解决方案2】:

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

【讨论】:

  • 这只是一种适用于超过 1 个重定向的解决方案。谢谢!
  • 这适用于多个重定向(HTTPS API -> HTTP -> HTTP 图像)!完美的简单解决方案。
  • @Nathan - 感谢您提供详细信息,但我仍然不买它。例如,是否在客户端的控制下发送任何凭据或客户端证书。如果它很痛,请不要这样做(在这种情况下,不要遵循重定向)。
  • 我只是不明白location = URLDecoder.decode(location... 部分。这会将工作编码的相对部分(在我的情况下为 space=+)解码为非工作部分。删除后,对我来说没问题。
  • @Niek 我不知道你为什么不需要它,但我确实需要。
【解决方案3】:

有什么叫做HttpURLConnection.setFollowRedirects(false)的东西吗?

你总是可以打电话

conn.setInstanceFollowRedirects(true);

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

【讨论】:

  • 哦...不知道那个...很好的发现...我正要查找课程以防有这样的逻辑....这是有道理的将返回该标题并赋予单一责任主体....现在回到回答 C# 问题:P [我在开玩笑]
  • 注意 setFollowRedirects() 应该在类上调用,而不是在实例上。
  • @dldnh:虽然 karlbecker_com 关于在类型上调用 setFollowRedirects 是绝对正确的,但 setInstanceFollowRedirects 是一个 instance 方法,不能在类型上调用。
  • ugh,我怎么看错了。抱歉编辑不正确。还试图回滚,但不知道我是怎么搞砸的。
【解决方案4】:

正如你们上面提到的,setFollowRedirect 和 setInstanceFollowRedirects 仅在重定向协议相同时自动工作。即从http到http和https到https。

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

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

【讨论】:

    【解决方案5】:

    另一种选择是使用 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();
    

    【讨论】:

      【解决方案6】:

      HTTPUrlConnection 不负责处理对象的响应。它的性能符合预期,它抓取了请求的 URL 的内容。解释响应的功能由您决定。没有规范就无法读取开发者的意图。

      【讨论】:

      • 为什么在这种情况下它有 setInstanceFollowRedirects? ))
      • 我的猜测是,这是一个建议稍后添加的功能,这是有道理的..我的评论更多地反映在...该课程旨在获取网络内容并带来它返回...人们可能希望收到非 HTTP 200 消息。
      猜你喜欢
      • 2014-03-13
      • 2015-03-01
      • 2019-05-10
      • 2016-08-24
      • 1970-01-01
      • 2018-12-28
      • 2019-09-10
      • 2017-10-06
      相关资源
      最近更新 更多