HTTPURLConnection Doesn't Follow Redirect from HTTP to HTTPS

我不能理解为什么 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();
}
}
}

这个程序的输出是:

Original URL: http://httpstat.us/301
Connected to: http://httpstat.us/301
HTTP response code received: 301
HTTP response message received: Moved Permanently

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威尔将按照预期的方式执行重定向! ?

98268 次浏览

HTTPUrlConnection 不负责处理对象的响应。正如预期的那样,它获取了所请求的 URL 的内容。由功能的用户来解释响应。如果没有规范,它就无法读取开发人员的意图。

是不是有个叫 HttpURLConnection.setFollowRedirects(false)的东西?

你可以随时打电话

conn.setInstanceFollowRedirects(true);

if you want to make sure you don't affect the rest of the behaviour of the app.

只有当它们使用相同的协议时才遵循重定向。(请参阅源代码中的 followRedirect()。)没有办法禁用这个检查。

尽管我们知道它反映了 HTTP,但从 HTTP 协议的角度来看,HTTPS 只是一些其他的、完全不同的、未知的协议。未经用户批准执行重定向是不安全的。

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

正如前面提到的,setFollowRedirect 和 setInstanceFollowRedirect 只有在重定向协议相同时才会自动工作。即从 http 到 http 和 https 到 https。

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

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

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

As Erickson points out, suppose the application is set up to perform client authentication automatically. The user expects to be surfing anonymously because he's using HTTP. But if his client follows HTTPS without asking, his identity is revealed to the server.

程序员必须采取额外的步骤,以确保在从 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();
...

另一种选择是使用 Apache HttpComponent 客户端:

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