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.