HTTPURLConnection Tidak Mengikuti Pengalihan dari HTTP ke HTTPS

97

Saya tidak mengerti mengapa Java HttpURLConnectiontidak mengikuti pengalihan HTTP dari HTTP ke URL HTTPS. Saya menggunakan kode berikut untuk mendapatkan halaman di 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();
        }
    }
}

Output dari program ini adalah:

URL Asli: http://httpstat.us/301
Terhubung ke: http://httpstat.us/301
Kode tanggapan HTTP diterima: 301
Pesan tanggapan HTTP diterima: Dipindahkan Secara Permanen

Permintaan ke http://httpstat.us/301 mengembalikan respons (dipersingkat) berikut (yang tampaknya benar!):

HTTP/1.1 301 Moved Permanently
Cache-Control: private
Content-Length: 21
Content-Type: text/plain; charset=utf-8
Location: https://httpstat.us

Sayangnya, Java HttpURLConnectiontidak mengikuti pengalihan!

Perhatikan bahwa jika Anda mengubah URL asli ke HTTPS ( https://httpstat.us/301 ), Java akan mengikuti pengalihan seperti yang diharapkan !?

Shcheklein
sumber
Hai, saya mengedit pertanyaan Anda untuk kejelasan dan untuk menunjukkan pengalihan ke HTTPS khususnya adalah masalahnya. Juga, saya mengubah domain bit.ly ke yang lain, karena penggunaan bit.ly masuk daftar hitam dalam pertanyaan. Harap Anda tidak keberatan, silakan edit ulang.
sleske

Jawaban:

119

Pengalihan diikuti hanya jika mereka menggunakan protokol yang sama. (Lihat yang followRedirect()metode dalam sumber.) Tidak ada cara untuk menonaktifkan cek ini.

Meskipun kita tahu itu mencerminkan HTTP, dari sudut pandang protokol HTTP, HTTPS hanyalah beberapa protokol lain yang sama sekali berbeda dan tidak dikenal. Tidak aman untuk mengikuti pengalihan tanpa persetujuan pengguna.

Misalnya, aplikasi disiapkan untuk melakukan otentikasi klien secara otomatis. Pengguna mengharapkan untuk menjelajah secara anonim karena dia menggunakan HTTP. Tetapi jika kliennya mengikuti HTTPS tanpa bertanya, identitasnya akan diungkapkan ke server.

erickson
sumber
60
Terima kasih. Saya baru saja menemukan confiramtion: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . Yaitu: "Setelah berdiskusi di antara para insinyur Java Networking, dirasa bahwa kami tidak boleh secara otomatis mengikuti pengalihan dari satu protokol ke protokol lain, misalnya, dari http ke https dan sebaliknya, melakukan hal itu dapat menimbulkan konsekuensi keamanan yang serius. Jadi perbaikannya adalah untuk mengembalikan tanggapan server untuk pengalihan. Periksa kode tanggapan dan nilai bidang tajuk Lokasi untuk informasi pengalihan. Aplikasi bertanggung jawab untuk mengikuti pengalihan. "
Shcheklein
2
Tetapi apakah itu mengikuti pengalihan dari http ke http atau https ke https? Bahkan itu salah. Bukan?
Sudarshan Bhat
7
@JoshuaDavis Ya, ini hanya berlaku untuk pengalihan ke protokol yang sama. Sebuah HttpURLConnectiontidak akan secara otomatis mengikuti pengalihan ke protokol yang berbeda, meskipun bendera pengalihan disetel.
erickson
8
Insinyur Java Networking dapat menawarkan opsi setFollowTransProtocol (true) karena jika kami membutuhkannya, kami akan tetap memprogramnya. FYI browser web, curl dan wget dan mungkin lebih mengikuti pengalihan dari HTTP ke HTTPS dan sebaliknya.
supercobra
18
Tidak ada yang menyiapkan login otomatis di HTTPS dan kemudian mengharapkan HTTP menjadi "anonim". Itu tidak masuk akal. Sangat aman dan normal untuk mengikuti pengalihan dari HTTP ke HTTPS (bukan sebaliknya). Ini hanyalah API Java yang biasanya buruk.
Glenn Maynard
54

HttpURLConnection menurut desain tidak akan secara otomatis mengalihkan dari HTTP ke HTTPS (atau sebaliknya). Mengikuti pengalihan mungkin memiliki konsekuensi keamanan yang serius. SSL (karenanya HTTPS) membuat sesi yang unik untuk pengguna. Sesi ini dapat digunakan kembali untuk beberapa permintaan. Dengan demikian, server dapat melacak semua permintaan yang dibuat dari satu orang. Ini adalah bentuk identitas yang lemah dan dapat dieksploitasi. Juga, jabat tangan SSL dapat meminta sertifikat klien. Jika dikirim ke server, maka identitas klien diberikan ke server.

Seperti yang ditunjukkan oleh erickson , misalkan aplikasi diatur untuk melakukan otentikasi klien secara otomatis. Pengguna mengharapkan untuk menjelajah secara anonim karena dia menggunakan HTTP. Tetapi jika kliennya mengikuti HTTPS tanpa bertanya, identitasnya akan diungkapkan ke server.

Pemrogram harus mengambil langkah ekstra untuk memastikan bahwa kredensial, sertifikat klien, atau id sesi SSL tidak akan dikirim sebelum mengalihkan dari HTTP ke HTTPS. Defaultnya adalah mengirim ini. Jika pengalihan merugikan pengguna, jangan ikuti pengalihan. Inilah mengapa pengalihan otomatis tidak didukung.

Dengan memahami itu, inilah kode yang akan mengikuti pengalihan.

  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();
  ...
Nathan
sumber
Ini hanya satu solusi yang berfungsi untuk lebih dari 1 pengalihan. Terima kasih!
Roger Alien
Ini berfungsi dengan baik untuk beberapa pengalihan (HTTPS API -> HTTP -> HTTP image)! Solusi sederhana yang sempurna.
EricH206
1
@ Nathan - terima kasih untuk detailnya, tapi saya masih belum membelinya. Misalnya, jika di bawah kendali klien apakah ada kredensial atau sertifikat klien yang dikirim. Jika sakit, jangan lakukan (dalam kasus ini, jangan ikuti arahan ulang).
Julian Reschke
1
Saya hanya tidak mengerti location = URLDecoder.decode(location...bagiannya. Ini menerjemahkan bagian relatif yang dikodekan yang berfungsi (dengan spasi = + dalam kasus saya) menjadi yang tidak berfungsi. Setelah saya menghapusnya, tidak masalah bagi saya.
Niek
@Niek Saya tidak yakin mengapa Anda tidak membutuhkannya tetapi saya membutuhkannya.
Nathan
26

Apakah ada yang menelepon HttpURLConnection.setFollowRedirects(false)secara kebetulan?

Anda selalu bisa menelepon

conn.setInstanceFollowRedirects(true);

jika Anda ingin memastikan bahwa Anda tidak memengaruhi perilaku aplikasi lainnya.

Jon Skeet
sumber
Ooo ... tidak tahu tentang itu ... Penemuan yang bagus ... Saya akan mencari kelas jika ada logika seperti itu .... Masuk akal bahwa itu akan mengembalikan tajuk itu memberikan tanggung jawab tunggal kepala sekolah .... sekarang kembali menjawab pertanyaan C #: P [I'm kidding]
monksy
2
Perhatikan bahwa setFollowRedirects () harus dipanggil di kelas, dan bukan pada sebuah instance.
karlbecker_com
3
@dldnh: Meskipun karlbecker_com benar tentang memanggil setFollowRedirectstipe, setInstanceFollowRedirectsmerupakan metode instan dan tidak bisa dipanggil pada tipe.
Jon Skeet
1
uggh, bagaimana saya salah membaca itu. maaf tentang hasil edit yang salah. juga mencoba untuk memutar kembali dan tidak yakin bagaimana saya membuat kesalahan itu juga.
dldnh
7

Seperti yang disebutkan oleh beberapa dari Anda di atas, setFollowRedirect dan setInstanceFollowRedirects hanya bekerja secara otomatis jika protokol yang dialihkan sama. yaitu dari http ke http dan https ke https.

setFolloRedirect berada di tingkat kelas dan menyetelnya untuk semua instance koneksi url, sedangkan setInstanceFollowRedirects hanya untuk instance tertentu. Dengan cara ini kita dapat memiliki perilaku yang berbeda untuk contoh yang berbeda.

Saya menemukan contoh yang sangat bagus di sini http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/

Shalvika
sumber
2

Pilihan lainnya adalah menggunakan Apache HttpComponents Client :

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

Kode sampel:

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();
Koray Tugay
sumber
-4

HTTPUrlConnection tidak bertanggung jawab untuk menangani respons objek. Ini adalah kinerja seperti yang diharapkan, ini mengambil konten dari URL yang diminta. Terserah Anda pengguna fungsi tersebut untuk menafsirkan respons. Itu tidak dapat membaca maksud pengembang tanpa spesifikasi.

biksu
sumber
7
Mengapa setInstanceFollowRedirects dalam kasus ini? ))
Shcheklein
Dugaan saya adalah bahwa itu adalah fitur yang disarankan untuk ditambahkan nanti, masuk akal .. komentar saya lebih mencerminkan ... kelas dirancang untuk pergi dan mengambil konten web dan membawanya kembali ... orang mungkin ingin dapatkan pesan non HTTP 200.
monksy