web-dev-qa-db-ja.com

URLConnectionがリダイレクトに従いません

JavaのHttpURLConnectionがリダイレクトに従わない理由を理解できません。次のコードを使用して このページ を取得します。

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 bitlyUrl = "http://bit.ly/4hW294";
            URL resourceUrl = new URL(bitlyUrl);
            HttpURLConnection conn = (HttpURLConnection)resourceUrl.openConnection();
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(15000);
            conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)");
            conn.connect();
            is = conn.getInputStream();
            String res = conn.getURL().toString();
            if (res.toLowerCase().contains("bit.ly"))
                System.out.println("bit.ly is after resolving: "+res);
       }
       catch (Exception e) {
           System.out.println("error happened: "+e.toString());
       }
       finally {
            if (is != null) is.close(); 
        }
    }
}

さらに、次の応答が返されます(絶対に正しいようです!):

GET /4hW294 HTTP/1.1
Host: bit.ly
Connection: Keep-Alive
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; ru-RU; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)
HTTP/1.1 301 Moved
Server: nginx/0.7.42
Date: Thu, 10 Dec 2009 20:28:44 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Location: https://www.myganocafe.com/CafeMacy
MIME-Version: 1.0
Content-Length: 297

残念ながら、res変数には同じURLが含まれ、ストリームには次のものが含まれます(明らかに、JavaのHttpURLConnectionはリダイレクトに従いません!):

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML>
<HEAD>
<TITLE>Moved</TITLE>
</HEAD>
<BODY>
<H2>Moved</H2>
<A HREF="https://www.myganocafe.com/CafeMacy">The requested URL has moved here.</A>
<P ALIGN=RIGHT><SMALL><I>AOLserver/4.5.1 on http://127.0.0.1:7400</I></SMALL></P>
</BODY>
</HTML>
90
Shcheklein

HTTPからHTTPS(またはその逆)に自動的にリダイレクトされるとは思わない。

HTTPをミラーリングしていることはわかっていますが、HTTPプロトコルの観点からすると、HTTPSは他のまったく異なる未知のプロトコルです。ユーザーの承認なしにリダイレクトに従うのは安全ではありません。

たとえば、クライアント認証を自動的に実行するようにアプリケーションが設定されているとします。ユーザーはHTTPを使用しているため、匿名でサーフィンすることを期待しています。しかし、彼のクライアントが確認せずにHTTPSに従う場合、彼のIDはサーバーに公開されます。

109
erickson

design によるHttpURLConnectionは、HTTPからHTTPS(またはその逆)に自動的にリダイレクトしません。リダイレクトに従うと、深刻なセキュリティ上の問題が生じる可能性があります。 SSL(したがってHTTPS)は、ユーザーに固有のセッションを作成します。このセッションは、複数のリクエストに再利用できます。したがって、サーバーは1人の人物からのすべての要求を追跡できます。これはアイデンティティの弱い形式であり、悪用可能です。また、SSLハンドシェイクはクライアントの証明書を要求できます。サーバーに送信される場合、クライアントのIDはサーバーに与えられます。

erickson が指摘しているように、クライアント認証を自動的に実行するようにアプリケーションが設定されているとします。ユーザーはHTTPを使用しているため、匿名でサーフィンすることを期待しています。しかし、彼のクライアントが確認せずにHTTPSに従う場合、彼のIDはサーバーに公開されます。

プログラマーは、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();
  ...
50
Nathan

HttpURLConnection.setFollowRedirects(false)と呼ばれるものはありますか?

いつでも電話できます

conn.setInstanceFollowRedirects(true);

アプリの残りの動作に影響を与えないようにしたい場合。

26
Jon Skeet

上記の一部の人が述べたように、setFollowRedirectとsetInstanceFollowRedirectsは、リダイレクトされたプロトコルが同じ場合にのみ自動的に機能します。つまり、httpからhttpおよびhttpsからhttpsへ。

setFolloRedirectはクラスレベルであり、url接続のすべてのインスタンスに対してこれを設定しますが、setInstanceFollowRedirectsは特定のインスタンスに対してのみです。これにより、インスタンスごとに異なる動作を実現できます。

ここで非常に良い例を見つけました http://www.mkyong.com/Java/java-httpurlconnection-follow-redirect-example/

6
Shalvika

別のオプションは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();
2
Koray Tugay