HTTPURLConnectionがHTTPからHTTPSへのリダイレクトに従いません


96

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

このプログラムの出力は次のとおりです。

元のURL: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

回答:


118

リダイレクトは、同じプロトコルを使用する場合にのみ実行されます。(ソースfollowRedirect()メソッドを参照してください。)このチェックを無効にする方法はありません。

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

たとえば、クライアント認証を自動的に実行するようにアプリケーションが設定されているとします。ユーザーはHTTPを使用しているため、匿名でサーフィンしていることを期待しています。しかし、彼のクライアントが尋ねずにHTTPSをフォローすると、彼の身元はサーバーに明らかにされます。


60
ありがとう。確認したところ:bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571。つまり、「Javaネットワーキングエンジニアの間で話し合った後、あるプロトコルから別のプロトコルへのリダイレクト(たとえば、httpからhttpsへ、およびその逆)を自動的にたどるべきではないと思われます。そうすると、重大なセキュリティ上の結果が生じる可能性があります。したがって、修正はリダイレクトのサーバー応答を返します。リダイレクト情報の応答コードとLocationヘッダーフィールド値を確認してください。リダイレクトに従うのはアプリケーションの責任です。」
Shcheklein、2009

2
しかし、httpからhttpまたはhttpsからhttpsへのリダイレクトに従いますか?それでも間違っているでしょう。だよね?
Sudarshan Bhat、2012年

7
@JoshuaDavisはい、同じプロトコルへのリダイレクトにのみ適用されます。HttpURLConnection自動的にリダイレクトフラグがセットされていても、異なるプロトコルにリダイレクトに従わないであろう。
エリクソン2013

8
Javaネットワークエンジニアは、必要に応じてプログラムするため、setFollowTransProtocol(true)オプションを提供できます。FYI Webブラウザー、curlおよびwget、さらにHTTPからHTTPSへ、またはその逆へのリダイレクトを追跡する場合があります。
スーパーコブラ2014年

18
HTTPSで自動ログインを設定して、HTTPが「匿名」であると想定する人はいません。それは無意味です。HTTPからHTTPSへのリダイレクトを追跡することは完全に安全で通常のことです(その逆ではありません)。これは、通常は悪いJava APIです。
Glenn Maynard

53

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

以下のようエリクソンが指摘すると、アプリケーションが自動的にクライアント認証を実行するように設定されているとします。ユーザーは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画像)で美しく機能します!完璧なシンプルなソリューション。
EricH206 2017年

1
@ネイサン-詳細をありがとう、しかし私はまだそれを購入しません。たとえば、資格情報またはクライアント証明書が送信されたかどうかがクライアントの制御下にある場合。痛い場合は、行わないでください(この場合、リダイレクトを行わないでください)。
Julian Reschke 2017

1
分からないだけですlocation = URLDecoder.decode(location...。これは、機能しているエンコードされた相対部分(私の場合はspace = +)を機能していない部分にデコードします。取り外した後は大丈夫でした。
Niek

@Niekなぜあなたはそれを必要としないのか分かりませんが、私はそうします。
ネイサン

26

何かがHttpURLConnection.setFollowRedirects(false)偶然呼ばれたことがありますか?

いつでも電話できます

conn.setInstanceFollowRedirects(true);

アプリの他の動作に影響を与えないようにする場合は、


うーん...それは知りませんでした...いいですね...そのようなロジックがあった場合に備えてクラスを調べようとしていました...単一の責任を与えるヘッダーを返すことは理にかなっていますプリンシパル.... C#の質問への回答に戻ります:P [冗談です]
monksy 2009

2
setFollowRedirects()は、インスタンスではなくクラスで呼び出す必要があることに注意してください。
karlbecker_com 2013

3
@dldnh:karlbecker_comはsetFollowRedirects型の呼び出しについて完全に適切でしたが、これsetInstanceFollowRedirectsインスタンスメソッドであり、型で呼び出すことはできません。
Jon Skeet、2013

1
うーん、どうやってそれを誤解したのですか。不正な編集について申し訳ありません。また、ロールバックを試みましたが、私がそれをどのようにブロックしたかわかりません。
dldnh 2013

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 2009

私の推測では、この機能は後で追加することをお勧めします。それは理にかなっています。私のコメントはより反映されていました...クラスはWebコンテンツを取得して取り戻すように設計されています...非HTTP 200メッセージを取得します。
monksy 2009
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.