HTTPURLConnection không tuân theo Chuyển hướng từ HTTP sang HTTPS


96

Tôi không thể hiểu tại sao Java HttpURLConnectionkhông theo chuyển hướng HTTP từ HTTP đến URL HTTPS. Tôi sử dụng mã sau để lấy trang tại 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();
        }
    }
}

Đầu ra của chương trình này là:

URL gốc: http://httpstat.us/301
Đã kết nối với: http://httpstat.us/301
Mã phản hồi HTTP đã nhận: 301
Đã nhận được thông báo phản hồi HTTP: Đã chuyển vĩnh viễn

Yêu cầu tới http://httpstat.us/301 trả về phản hồi (rút gọn) sau (có vẻ hoàn toàn đúng!):

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

Thật không may, Java HttpURLConnectionkhông tuân theo chuyển hướng!

Lưu ý rằng nếu bạn thay đổi URL ban đầu thành HTTPS ( https://httpstat.us/301 ), Java sẽ chuyển hướng như mong đợi !?


Xin chào, tôi đã chỉnh sửa câu hỏi của bạn để rõ ràng hơn và chỉ ra rằng việc chuyển hướng đến HTTPS cụ thể là vấn đề. Ngoài ra, tôi đã thay đổi miền bit.ly thành một miền khác, vì sử dụng bit.ly được đưa vào danh sách đen trong các câu hỏi. Mong các bạn không phiền, thoải mái edit lại.
sleske

Câu trả lời:


118

Chuyển hướng chỉ được tuân theo nếu chúng sử dụng cùng một giao thức. (Xem các followRedirect()phương pháp trong nguồn.) Không có cách nào để vô hiệu hóa việc kiểm tra này.

Mặc dù chúng ta biết rằng nó phản ánh HTTP, nhưng từ quan điểm giao thức HTTP, HTTPS chỉ là một số giao thức khác, hoàn toàn khác, không xác định. Sẽ không an toàn nếu đi theo chuyển hướng mà không có sự chấp thuận của người dùng.

Ví dụ: giả sử ứng dụng được thiết lập để thực hiện xác thực máy khách tự động. Người dùng mong đợi được lướt web ẩn danh vì họ đang sử dụng HTTP. Nhưng nếu khách hàng của anh ta theo dõi HTTPS mà không hỏi, danh tính của anh ta sẽ được tiết lộ cho máy chủ.


60
Cảm ơn. Tôi vừa tìm thấy confiramtion: bug.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . Cụ thể: "Sau khi thảo luận giữa các kỹ sư Mạng Java, chúng tôi cảm thấy rằng chúng ta không nên tự động chuyển hướng từ giao thức này sang giao thức khác, ví dụ: từ http sang https và ngược lại, làm như vậy có thể gây ra hậu quả bảo mật nghiêm trọng. Vì vậy, cách khắc phục là để trả lại phản hồi của máy chủ cho chuyển hướng. Kiểm tra mã phản hồi và giá trị trường tiêu đề Vị trí để biết thông tin chuyển hướng. Ứng dụng có trách nhiệm thực hiện theo chuyển hướng ".
Shcheklein

2
Nhưng nó có chuyển hướng từ http sang http hoặc https sang https không? Ngay cả điều đó sẽ là sai. Phải không?
Sudarshan Bhat,

7
@JoshuaDavis Có, nó chỉ áp dụng cho các chuyển hướng đến cùng một giao thức. An HttpURLConnectionsẽ không tự động thực hiện theo các chuyển hướng đến một giao thức khác, ngay cả khi cờ chuyển hướng được đặt.
erickson

8
Các kỹ sư Mạng Java có thể đưa ra tùy chọn setFollowTransProtocol (true) bởi vì nếu chúng tôi cần, chúng tôi sẽ lập trình nó. Các trình duyệt web FYI, curl và wget và có thể theo sau các chuyển hướng từ HTTP sang HTTPS và ngược lại.
supercobra

18
Không ai thiết lập tự động đăng nhập trên HTTPS và sau đó mong đợi HTTP là "ẩn danh". Điều đó thật vô lý. Hoàn toàn an toàn và bình thường khi thực hiện theo các chuyển hướng từ HTTP sang HTTPS (không phải ngược lại). Đây chỉ là một API Java thường không tốt.
Glenn Maynard

53

HttpURL Kết nối theo thiết kế sẽ không tự động chuyển hướng từ HTTP sang HTTPS (hoặc ngược lại). Việc đi theo chuyển hướng có thể gây ra hậu quả an ninh nghiêm trọng. SSL (do đó HTTPS) tạo một phiên duy nhất cho người dùng. Phiên này có thể được sử dụng lại cho nhiều yêu cầu. Do đó, máy chủ có thể theo dõi tất cả các yêu cầu được thực hiện từ một người duy nhất. Đây là một dạng nhận dạng yếu và có thể bị khai thác. Ngoài ra, quá trình bắt tay SSL có thể yêu cầu chứng chỉ của khách hàng. Nếu được gửi đến máy chủ, thì danh tính của khách hàng sẽ được cấp cho máy chủ.

Như erickson đã chỉ ra, giả sử ứng dụng được thiết lập để thực hiện xác thực ứng dụng khách một cách tự động. Người dùng mong đợi được lướt web ẩn danh vì họ đang sử dụng HTTP. Nhưng nếu khách hàng của anh ta theo dõi HTTPS mà không hỏi, danh tính của anh ta sẽ được tiết lộ cho máy chủ.

Lập trình viên phải thực hiện thêm các bước để đảm bảo rằng thông tin đăng nhập, chứng chỉ ứng dụng khách hoặc id phiên SSL sẽ không được gửi trước khi chuyển hướng từ HTTP sang HTTPS. Mặc định là gửi những thứ này. Nếu chuyển hướng làm tổn thương người dùng, không thực hiện theo chuyển hướng. Đây là lý do tại sao chuyển hướng tự động không được hỗ trợ.

Với điều đó đã hiểu, đây là mã sẽ tuân theo các chuyển hướng.

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

Đây chỉ là một giải pháp hoạt động cho hơn 1 lần chuyển hướng. Cảm ơn bạn!
Roger Alien,

Điều này hoạt động tốt cho nhiều chuyển hướng (API HTTPS -> HTTP -> HTTP hình ảnh)! Giải pháp đơn giản hoàn hảo.
EricH206

1
@Nathan - cảm ơn vì chi tiết, nhưng tôi vẫn chưa mua nó. Ví dụ: nếu nằm dưới sự kiểm soát của khách hàng cho dù bất kỳ thông tin đăng nhập hoặc chứng chỉ khách hàng nào được gửi đi. Nếu nó đau, đừng làm điều đó (trong trường hợp này, không làm theo chuyển hướng).
Julian Reschke

1
Tôi chỉ không hiểu location = URLDecoder.decode(location...một phần. Điều này giải mã một phần tương đối được mã hóa đang hoạt động (với dấu cách = + trong trường hợp của tôi) thành một phần không hoạt động. Sau khi tôi gỡ bỏ nó, nó là OK đối với tôi.
Niek

@Niek Tôi không chắc tại sao bạn không cần nó nhưng tôi thì có.
Nathan

26

Có một cái gì đó được gọi HttpURLConnection.setFollowRedirects(false)bởi bất kỳ cơ hội?

Bạn luôn có thể gọi

conn.setInstanceFollowRedirects(true);

nếu bạn muốn đảm bảo rằng bạn không ảnh hưởng đến phần còn lại của hành vi của ứng dụng.


Ooo ... không biết về điều đó ... Rất vui ... Tôi đã định tra cứu lớp học trong trường hợp có logic như vậy .... Có lý là nó sẽ trả lại tiêu đề đó cho một trách nhiệm duy nhất hiệu trưởng .... bây giờ quay lại trả lời các câu hỏi C #: P [Tôi đùa thôi]
Mony

2
Lưu ý rằng setFollowRedirects () nên được gọi trên lớp chứ không phải trên một cá thể.
karlbecker_com

3
@dldnh: Mặc dù karlbecker_com đã hoàn toàn đúng khi gọi setFollowRedirectskiểu, nhưng setInstanceFollowRedirectslà một phương thức cá thể và không thể gọi trên kiểu.
Jon Skeet

1
uggh, làm thế nào tôi đã đọc sai điều đó. xin lỗi về việc chỉnh sửa không chính xác. cũng đã cố gắng khôi phục và không chắc chắn bằng cách nào tôi cũng đã gắn nó vào.
dldnh

7

Như một số bạn đã đề cập ở trên, setFollowRedirect và setInstanceFollowRedirects chỉ hoạt động tự động khi giao thức được chuyển hướng giống nhau. tức là từ http thành http và https thành https.

setFolloRedirect ở cấp độ lớp và đặt điều này cho tất cả các trường hợp của kết nối url, trong khi setInstanceFollowRedirects chỉ dành cho một trường hợp cụ thể. Bằng cách này, chúng ta có thể có các hành vi khác nhau cho các trường hợp khác nhau.

Tôi đã tìm thấy một ví dụ rất hay tại đây http://www.mkyong.com/java/java-httpurlconnection-follow-redirect-example/


2

Một tùy chọn khác có thể là sử dụng Apache HttpComponents Client :

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

Mã mẫu:

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 không chịu trách nhiệm xử lý phản hồi của đối tượng. Đó là hiệu suất như mong đợi, nó lấy nội dung của URL được yêu cầu. Người sử dụng chức năng giải thích phản hồi tùy thuộc vào bạn. Nó không thể đọc được ý định của nhà phát triển mà không có đặc điểm kỹ thuật.


7
Tại sao nó có setInstanceFollowRedirects trong trường hợp này? ))
Shcheklein

Tôi đoán rằng đó là một tính năng được đề xuất để thêm vào sau này, điều đó có ý nghĩa .. nhận xét của tôi được phản ánh nhiều hơn về phía ... lớp học được thiết kế để lấy nội dung web và đưa nó trở lại ... mọi người có thể muốn nhận thông báo không phải HTTP 200.
tu sĩ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.