HTTPURLConnection ไม่ติดตามการเปลี่ยนเส้นทางจาก HTTP ไปยัง HTTPS


99

ฉันไม่เข้าใจว่าเหตุใด Java HttpURLConnectionจึงไม่ติดตามการเปลี่ยนเส้นทาง HTTP จาก HTTP ไปยัง HTTPS URL ฉันใช้รหัสต่อไปนี้เพื่อรับหน้าเว็บที่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 จะเปลี่ยนเส้นทางตามที่คาดไว้!?


1
สวัสดีฉันแก้ไขคำถามของคุณเพื่อความชัดเจนและเพื่อชี้ให้เห็นว่าการเปลี่ยนเส้นทางไปยัง HTTPS นั้นเป็นปัญหาโดยเฉพาะ นอกจากนี้ฉันเปลี่ยนโดเมน bit.ly เป็นโดเมนอื่นเนื่องจากใช้ bit.ly อยู่ในบัญชีดำในคำถาม หวังว่าคุณจะไม่รังเกียจอย่าลังเลที่จะแก้ไขใหม่
— sleske

คำตอบ:


120

การเปลี่ยนเส้นทางจะตามมาก็ต่อเมื่อใช้โปรโตคอลเดียวกัน (ดูวิธีการในแหล่งที่มา.) มีวิธีที่จะปิดการใช้งานการตรวจสอบนี้ไม่เป็นfollowRedirect()

แม้ว่าเราจะรู้ว่ามันสะท้อน HTTP แต่จากมุมมองของโปรโตคอล HTTP แต่ HTTPS ก็เป็นเพียงโปรโตคอลอื่นที่แตกต่างกันโดยสิ้นเชิงและไม่รู้จัก การทำตามการเปลี่ยนเส้นทางโดยไม่ได้รับการอนุมัติจากผู้ใช้จะไม่ปลอดภัย

ตัวอย่างเช่นสมมติว่าแอปพลิเคชันได้รับการตั้งค่าให้ทำการพิสูจน์ตัวตนไคลเอ็นต์โดยอัตโนมัติ ผู้ใช้คาดว่าจะท่องเว็บโดยไม่เปิดเผยตัวตนเนื่องจากใช้ HTTP แต่ถ้าลูกค้าของเขาติดตาม HTTPS โดยไม่ถามตัวตนของเขาจะถูกเปิดเผยต่อเซิร์ฟเวอร์


60
ขอบคุณ. ฉันได้พบเพียง confiramtion: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 กล่าวคือ: "หลังจากพูดคุยกันระหว่างวิศวกร Java Networking เราไม่ควรติดตามการเปลี่ยนเส้นทางจากโปรโตคอลหนึ่งไปยังอีกโปรโตคอลหนึ่งโดยอัตโนมัติตัวอย่างเช่นจาก http ไปยัง https และในทางกลับกันการทำเช่นนั้นอาจส่งผลกระทบด้านความปลอดภัยที่ร้ายแรงดังนั้นการแก้ไขคือ เพื่อส่งคืนการตอบกลับของเซิร์ฟเวอร์สำหรับการเปลี่ยนเส้นทางตรวจสอบโค้ดตอบกลับและค่าฟิลด์ส่วนหัวตำแหน่งสำหรับข้อมูลการเปลี่ยนเส้นทางแอปพลิเคชันมีหน้าที่ติดตามการเปลี่ยนเส้นทาง
— Shcheklein

2
แต่มันทำตามการเปลี่ยนเส้นทางจาก http เป็น http หรือ https ไปยัง https? แม้นั่นจะผิด ไม่ใช่เหรอ?
— Sudarshan Bhat

7
@JoshuaDavis ใช่มันใช้กับการเปลี่ยนเส้นทางไปยังโปรโตคอลเดียวกันเท่านั้น HttpURLConnectionจะไม่โดยอัตโนมัติติดตามการเปลี่ยนเส้นทางไปยังโปรโตคอลที่แตกต่างกันแม้ว่าธงเปลี่ยนเส้นทางเป็นชุด
— erickson

8
วิศวกร Java Networking สามารถเสนอตัวเลือก setFollowTransProtocol (true) ได้เพราะถ้าเราต้องการเราก็จะตั้งโปรแกรมให้ เว็บเบราว์เซอร์ FYI curl และ wget และอาจมากขึ้นตามการเปลี่ยนเส้นทางจาก HTTP ไปยัง HTTPS และในทางกลับกัน
— supercobra

18
ไม่มีใครตั้งค่าการเข้าสู่ระบบอัตโนมัติบน HTTPS แล้วคาดว่า HTTP จะ "ไม่ระบุตัวตน" นั่นเป็นเรื่องไร้สาระ การติดตามการเปลี่ยนเส้นทางจาก HTTP ไปยัง HTTPS นั้นปลอดภัยและเป็นเรื่องปกติ (ไม่ใช่วิธีอื่น) นี่เป็นเพียง Java API ที่ไม่ดีโดยทั่วไป
— Glenn Maynard

55

HttpURLConnection by designจะไม่เปลี่ยนเส้นทางจาก HTTP ไปยัง HTTPS โดยอัตโนมัติ (หรือในทางกลับกัน) การทำตามการเปลี่ยนเส้นทางอาจส่งผลกระทบด้านความปลอดภัยที่ร้ายแรง SSL (ด้วยเหตุนี้ HTTPS) จะสร้างเซสชันที่ไม่ซ้ำกันสำหรับผู้ใช้ เซสชันนี้สามารถใช้ซ้ำได้สำหรับหลายคำขอ ดังนั้นเซิร์ฟเวอร์สามารถติดตามคำขอทั้งหมดที่ทำจากบุคคลเดียวได้ นี่เป็นรูปแบบที่อ่อนแอของอัตลักษณ์และถูกใช้ประโยชน์ได้ นอกจากนี้ SSL handshake สามารถขอใบรับรองของลูกค้าได้ หากส่งไปยังเซิร์ฟเวอร์ข้อมูลประจำตัวของไคลเอ็นต์จะถูกมอบให้กับเซิร์ฟเวอร์

ดังที่ericksonชี้ให้เห็นสมมติว่ามีการตั้งค่าแอปพลิเคชันให้ทำการตรวจสอบสิทธิ์ไคลเอ็นต์โดยอัตโนมัติ ผู้ใช้คาดว่าจะท่องเว็บโดยไม่เปิดเผยตัวตนเนื่องจากใช้ HTTP แต่ถ้าลูกค้าของเขาติดตาม HTTPS โดยไม่ถามตัวตนของเขาจะถูกเปิดเผยต่อเซิร์ฟเวอร์

โปรแกรมเมอร์ต้องดำเนินการเพิ่มเติมเพื่อให้แน่ใจว่าจะไม่มีการส่งข้อมูลรับรองใบรับรองไคลเอ็นต์หรือรหัสเซสชัน SSL ก่อนที่จะเปลี่ยนเส้นทางจาก HTTP ไปยัง HTTPS ค่าเริ่มต้นคือการส่งสิ่งเหล่านี้ หากการเปลี่ยนเส้นทางทำให้ผู้ใช้เสียหายอย่าทำตามการเปลี่ยนเส้นทาง นี่คือสาเหตุที่ไม่รองรับการเปลี่ยนเส้นทางอัตโนมัติ

เมื่อเข้าใจแล้วนี่คือรหัสที่จะทำตามการเปลี่ยนเส้นทาง

  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 ครั้ง ขอขอบคุณ!
— Roger Alien

สิ่งนี้ทำงานได้อย่างสวยงามสำหรับการเปลี่ยนเส้นทางหลายครั้ง (HTTPS API -> HTTP -> ภาพ HTTP) วิธีง่ายๆที่สมบูรณ์แบบ
— EricH206

1
@ นาธาน - ขอบคุณสำหรับรายละเอียด แต่ยังไม่ซื้อ ตัวอย่างเช่นหากอยู่ภายใต้การควบคุมของลูกค้าไม่ว่าจะมีการส่งข้อมูลรับรองหรือใบรับรองไคลเอ็นต์ใด ๆ ถ้าเจ็บอย่าทำ (ในกรณีนี้อย่าทำตามการเปลี่ยนเส้นทาง)
— Julian Reschke

1
ฉันไม่เข้าใจlocation = URLDecoder.decode(location...บางส่วนเท่านั้น สิ่งนี้จะถอดรหัสส่วนสัมพัทธ์ที่เข้ารหัสที่ใช้งานได้ (มีช่องว่าง = + ในกรณีของฉัน) เป็นส่วนที่ไม่ทำงาน หลังจากที่ฉันลบมันก็โอเคสำหรับฉัน
— Niek

@Niek ฉันไม่แน่ใจว่าทำไมคุณไม่ต้องการ แต่ฉันทำ
— นาธาน

27

มีสิ่งที่เรียกว่าHttpURLConnection.setFollowRedirects(false)บังเอิญหรือไม่?

คุณสามารถโทร

conn.setInstanceFollowRedirects(true);

หากคุณต้องการให้แน่ใจว่าคุณจะไม่ส่งผลกระทบต่อพฤติกรรมที่เหลือของแอป


โอว ... ไม่รู้เรื่องนั้น ... ยินดีพบ ... ฉันกำลังจะค้นหาชั้นเรียนในกรณีที่มีตรรกะแบบนั้น .... มันสมเหตุสมผลแล้วที่จะส่งคืนส่วนหัวที่ให้ความรับผิดชอบเดียว ครูใหญ่ .... กลับไปตอบคำถาม C #: P [ล้อเล่น]
— พระ

2
โปรดสังเกตว่า setFollowRedirects () ควรถูกเรียกใช้บนคลาสไม่ใช่ในอินสแตนซ์
— karlbecker_com

3
@dldnh: ในขณะที่ karlbecker_com ถูกต้องอย่างยิ่งเกี่ยวกับการเรียกsetFollowRedirectsประเภท แต่setInstanceFollowRedirectsเป็นวิธีการอินสแตนซ์และไม่สามารถเรียกประเภท
— Jon Skeet

1
ฮึฉันอ่านผิดได้อย่างไร ขออภัยเกี่ยวกับการแก้ไขที่ไม่ถูกต้อง ยังพยายามย้อนกลับและไม่แน่ใจว่าฉันล้มเหลวได้อย่างไรเช่นกัน
— dldnh

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 ที่ร้องขอ ขึ้นอยู่กับคุณผู้ใช้ฟังก์ชันในการตีความการตอบสนอง มันไม่สามารถอ่านความตั้งใจของผู้พัฒนาที่ไม่มีข้อกำหนด


8
เหตุใดจึงมี setInstanceFollowRedirects ในกรณีนี้ ))
— Shcheklein

ฉันเดาว่ามันเป็นคุณสมบัติที่แนะนำให้เพิ่มในภายหลังมันสมเหตุสมผลแล้ว .. ความคิดเห็นของฉันสะท้อนให้เห็นมากกว่า ... ชั้นเรียนได้รับการออกแบบมาเพื่อคว้าเนื้อหาเว็บและนำกลับมา ... ผู้คนอาจต้องการ รับข้อความที่ไม่ใช่ HTTP 200
— พระสงฆ์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.