HTTPURLConnection이 HTTP에서 HTTPS 로의 리디렉션을 따르지 않음


97

Java가 HttpURLConnectionHTTP에서 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 로의 리디렉션이 문제임을 지적했습니다. 또한 use bit.ly가 질문에 블랙리스트에 포함되어 있으므로 bit.ly 도메인을 다른 도메인으로 변경했습니다. 괜찮 으시면 언제든지 다시 수정하세요.
sleske

답변:


119

리디렉션은 동일한 프로토콜을 사용하는 경우에만 수행됩니다. ( 소스 followRedirect()방법 을 참조하십시오 .)이 검사를 비활성화 할 수있는 방법은 없습니다.

HTTP를 미러링한다는 것을 알고 있지만 HTTP 프로토콜 관점에서 보면 HTTPS는 완전히 다른 알려지지 않은 프로토콜 일뿐입니다. 사용자 승인없이 리디렉션을 따르는 것은 안전하지 않습니다.

예를 들어 응용 프로그램이 클라이언트 인증을 자동으로 수행하도록 설정되어 있다고 가정합니다. 사용자는 HTTP를 사용하기 때문에 익명으로 서핑을 기대합니다. 그러나 그의 클라이언트가 묻지 않고 HTTPS를 따르면 그의 신원이 서버에 공개됩니다.


60
감사. 방금 confiramtion을 찾았습니다 : bugs.sun.com/bugdatabase/view_bug.do?bug_id=4620571 . 즉 : "Java 네트워킹 엔지니어들 사이에서 논의한 후, http에서 https로 또는 그 반대로 한 프로토콜에서 다른 프로토콜로 리디렉션을 자동으로 따르지 말아야한다고 느꼈습니다. 그렇게하면 심각한 보안 문제가 발생할 수 있습니다. 따라서 수정 사항은 다음과 같습니다. 리디렉션에 대한 서버 응답을 반환합니다. 리디렉션 정보에 대한 응답 코드 및 위치 헤더 필드 값을 확인합니다. 리디렉션을 따르는 것은 애플리케이션의 책임입니다. "
Shcheklein 2009

2
그러나 http에서 http로 또는 https에서 https로 리디렉션됩니까? 그것조차도 틀릴 것입니다. 그렇지 않나요?
Sudarshan Bhat

7
@JoshuaDavis 예, 동일한 프로토콜로의 리디렉션에만 적용됩니다. 는 HttpURLConnection자동으로 리디렉션 플래그가 설정되어있는 경우에도, 다른 프로토콜로 리디렉션을 수행하지 않습니다.
erickson

8
Java 네트워킹 엔지니어는 setFollowTransProtocol (true) 옵션을 제공 할 수 있습니다. 필요한 경우 어쨌든 프로그래밍 할 것이기 때문입니다. FYI 웹 브라우저, curl 및 wget 및 HTTP에서 HTTPS로 또는 그 반대로 리디렉션을 따라갈 수 있습니다.
supercobra 2014 년

18
아무도 HTTPS에서 자동 로그인을 설정 한 다음 HTTP가 "익명"이 될 것으로 기대하지 않습니다. 말도 안 돼요. HTTP에서 HTTPS 로의 리디렉션을 따르는 것은 완벽하게 안전하고 정상입니다 (반대는 아님). 이것은 일반적으로 잘못된 Java API입니다.
글렌 메이 나드

54

설계 상 HttpURLConnection 은 HTTP에서 HTTPS로 (또는 그 반대로) 자동으로 리디렉션되지 않습니다. 리디렉션 후에는 심각한 보안 문제가 발생할 수 있습니다. SSL (따라서 HTTPS)은 사용자에게 고유 한 세션을 생성합니다. 이 세션은 여러 요청에 재사용 할 수 있습니다. 따라서 서버는 한 사람이 만든 모든 요청을 추적 할 수 있습니다. 이것은 약한 형태의 신원이며 악용 될 수 있습니다. 또한 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();
  ...

이것은 둘 이상의 리디렉션에 대해 작동하는 유일한 솔루션입니다. 감사합니다!
로저 외국인

이것은 여러 리디렉션 (HTTPS API-> HTTP-> HTTP 이미지)에서 아름답게 작동합니다! 완벽한 간단한 솔루션.
EricH206

1
@Nathan-세부 사항에 대해 감사하지만 여전히 구매하지 않습니다. 예를 들어 자격 증명이나 클라이언트 인증서가 전송되는지 여부를 클라이언트가 제어하는 ​​경우입니다. 아프면하지 마십시오 (이 경우 리디렉션을 따르지 마십시오).
Julian Reschke

1
나는 location = URLDecoder.decode(location...부분 만 이해하지 못한다 . 이것은 작동하는 인코딩 된 상대 부분 (내 경우에는 space = + 사용)을 작동하지 않는 부분으로 디코딩합니다. 제거한 후에는 괜찮 았습니다.
Niek dec

@Niek 왜 당신이 그것을 필요로하지 않는지 잘 모르겠지만 나는 있습니다.
Nathan

26

혹시 HttpURLConnection.setFollowRedirects(false)뭐라고 불러 ?

당신은 항상 전화 할 수 있습니다

conn.setInstanceFollowRedirects(true);

앱의 나머지 동작에 영향을 미치지 않는지 확인하려는 경우.


Ooo ... 몰랐어요 ... Nice find ... 그런 논리가있을 때를 대비해서 클래스를 찾아 보려고했습니다 .... 단일 책임을주는 헤더를 반환하는 것이 의미가 있습니다. 교장 .... 이제 다시 C # 질문에 답하기 : P [농담입니다]
monksy

2
setFollowRedirects ()는 인스턴스가 아닌 클래스에서 호출되어야합니다.
karlbecker_com 2013

3
@dldnh : karlbecker_com 절대적 권리에 대한 호출 동안 setFollowRedirects유형에, setInstanceFollowRedirects입니다 인스턴스 방법 및 유형에 호출 할 수 없습니다.
Jon Skeet 2013

1
어그, 내가 어떻게 잘못 읽었는지. 잘못된 편집에 대해 죄송합니다. 또한 롤백을 시도했지만 어떻게 bollocks했는지 확실하지 않았습니다.
dldnh 2013-04-13

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

내 생각 엔 나중에 추가 할 기능이 제안 된 것 같고 말이되는데 .. 내 의견은 더 많이 반영되었습니다 ... 수업은 웹 콘텐츠를 가져 와서 다시 가져 오도록 설계되었습니다 ... 사람들은 HTTP 200이 아닌 메시지를받습니다.
monksy 2009
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.