[Java] HttpsURLConnection
- -
[Java] HttpsURLConnection
안녕하세요. 갓대희 입니다. 이번 포스팅은 [ [Java] HttpsURLConnection ] 입니다. : )
0.HttpsURLConnection
▶ 0. HttpsURLConnection란?
- JAVA 소스 내에서 SSL 적용된 사이트에 접근하기 위해, REST Api를 호출하기 위해 사용하게 되며, 결과 데이터를 스트림 형식으로 제공받아 이용이 가능하다.
- 데이터의 타입이나 길이는 거의 제한이 없으며, 주로 미리 길이를 알지 못하는 스트리밍 데이터를 주고 받는데 사용된다.
- 더 자세한 내용은 현 시점에는 java8을 가장 많이 쓸 것으로 생각하고, java8 기준으로 HttpsURLConnection doc을 확인 해보자.
- docs.oracle.com/javase/8/docs/api/javax/net/ssl/HttpsURLConnection.html
- 기본 골격은 https://goddaehee.tistory.com/161 과 비슷하니 참고.(이번 포스팅에선 바로 사용할 만한 간단한 예제를 기술하려 한다.)
※ 무시해도 좋으나 참고하려면 참고 하자.
- 조금더 알아보자면, 위 javadoc을 확인해보면 다음과 같이 설명하고 있다.
java.lang.Object
└ java.net.URLConnection
└ java.net.HttpURLConnection
└ javax.net.ssl.HttpsURLConnection
public abstract class HttpsURLConnection extends HttpURLConnection
Fields inherited from class java.net.URLConnection
allowUserInteraction, connected, doInput, doOutput, ifModifiedSince, url, useCaches
- HttpURLConnection을 상속 받고 있음을 볼 수 있다. (URLConnection은 이미 이전 포스팅에서 설명하였듯이 주로 URL 내용을 읽어오거나, URL 주소에 GET / POST로 데이터를 전달 할 때 사용한다.)
- URLConnection에서 상속 받은 필드는 다음과 같다고 나와있다. 당연히 관련 필드 및 관련 메서드들도 참고로 짚고 넘어가면 좋을 것 같다.
(allowUserInteraction, connected, doInput, doOutput, ifModifiedSince, url, useCaches)
(docs.oracle.com/javase/8/docs/api/java/net/URLConnection.html)
- URLConnection은 리소스에 연결하기 전에 구성 되어야 한다.
- URLConnection 인스턴스는 재사용 될 수 없다. 각 리소스에 대한 커넥션 마다 다른 인스턴스를 사용해야 한다.
※ URLConnection 관련 필드 및 메서드
● getAllowUserInteraction() : 연결 된 곳에 사용자가 서버와 통신 할 수 있는 환경 여부를 확인한다.(boolean), in/output이 해당 서버, 연결 포트로 가능한지 확인한다.
● getDefaultAllowUserInteraction(): 기본적으로 User와 통신 가능한 상태인지 확인한다.(boolean)
● connect() : 해당 url에 연결 된 곳에 접속 할때 사용한다. false인 경우, 연결 객체는 지정된 URL로의 통신 링크를 작성하고 true인 경우엔 통신 링크가 설정되이 있다.
● setDoInput() :
- URLConnection에 대한 doInput 필드값을 지정된 값으로 설정한다. URL 연결은 입출력에 사용될 수 있다. true로 설정시 서버통신에서 입력 가능한 상태로 설정 한다.(응답 헤더와 메시지 등을 Read) (default : true)
● setDoOutput() :
- URLConnection에 대한 doOutput 필드값을 지정된 값으로 설정한다. true로 설정시 서버통신에서 출력 가능한 상태로 설정 한다.(outputstream으로 데이터 처리 등) (default : false)
● getDoInput() : Server에서 온 데이터를 입력 받을 수 있는 상태인지 여부를 확인한다.(default : true)
● getDoOutput(): Server에서 온 데이터를 출력 할수 있는 상태인지 여부를 확인한다.(default : false)
● ifModifiedSince : 일부 프로토콜은 개체가 특정 시간보다 더 최근에 수정되지 않은 경우 개체 가져 오기 건너 뛰기를 지원한다. 0이 아닌 값은 GMT 1970 년 1 월 1 일 이후의 밀리 초 수로 시간을 제공한다. (default : 0)
● useCaches : true프로토콜은 가능할 때마다 캐싱을 사용할 수 있다. 즉 이전에 다운로드 받은 데이터를 사용할지에대한 여부. DefaultUseCaches에서 가져 오며 기본값은 true이다.
▶ 1. HttpsURLConnection 사용 예제
1) 기본 예제
URL url = new URL("https://google.co.kr");
HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
// 결과 Stream Data를 Stream객체에 할당하여 활용 가능하다.
httpsConn.getInputStream();
HttpUrlConnection 주요 설정
httpConn.setRequestMethod("GET"); //요청 방식 설정 (GET/POST 등)
httpConn.setRequestProperty("key", "value"); // request Header 설정 key-value 형식으로 다양한 요청 설정이 가능하다.
httpConn.setConnectTiomeOut(1000); //서버 연결 제한 시간
httpConn.setReadTimeOut(1000); // 서버 연결 후 데이터 read 제한 시간
// URL 호출시 발생하는 무한 대기 상태에 빠지지 않도록 connectionTimeOut 발생시, ReadTimeOut 발생시 시간 설정을 꼭 해두자.
- 아주 간단히는 위와 같은 방법으로 사용 가능하지만, URLConnection, 그리고 HttpsURLConnection을 적절히 사용하자면 다음과 같이 사용할 수 있을 것 같다.
ex) 구글
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
public class Test {
public static void main(String[] args) throws Exception {
String urlString = "https://www.google.com";
String line = null;
InputStream in = null;
BufferedReader reader = null;
HttpsURLConnection httpsConn = null;
try {
// Get HTTPS URL connection
URL url = new URL(urlString);
httpsConn = (HttpsURLConnection) url.openConnection();
// Set Hostname verification
httpsConn.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Ignore host name verification. It always returns true.
return true;
}
});
// Input setting
httpsConn.setDoInput(true);
// Output setting
//httpsConn.setDoOutput(true);
// Caches setting
httpsConn.setUseCaches(false);
// Read Timeout Setting
httpsConn.setReadTimeout(1000);
// Connection Timeout setting
httpsConn.setConnectTimeout(1000);
// Method Setting(GET/POST)
httpsConn.setRequestMethod("GET");
// Header Setting
httpsConn.setRequestProperty("HeaderKey","HeaderValue");
int responseCode = httpsConn.getResponseCode();
System.out.println("응답코드 : " + responseCode);
System.out.println("응답메세지 : " + httpsConn.getResponseMessage());
// SSL setting
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null); // No validation for now
httpsConn.setSSLSocketFactory(context.getSocketFactory());
// Connect to host
httpsConn.connect();
httpsConn.setInstanceFollowRedirects(true);
// Print response from host
if (responseCode == HttpsURLConnection.HTTP_OK) { // 정상 호출 200
in = httpsConn.getInputStream();
} else { // 에러 발생
in = httpsConn.getErrorStream();
}
reader = new BufferedReader(new InputStreamReader(in));
while ((line = reader.readLine()) != null) {
System.out.printf("%s\n", line);
}
reader.close();
} catch (UnknownHostException e) {
System.out.println("UnknownHostException : " + e);
} catch (MalformedURLException e) {
System.out.println(urlString + " is not a URL I understand");
} catch (IOException e) {
System.out.println("IOException :" + e);
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if (reader != null) {
reader.close();
}
if (httpsConn != null) {
httpsConn.disconnect();
}
}
}
}
- 결과
응답코드 : 200
응답메세지 : OK
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ko">
...
▶ 2. HttpsURLConnection 사용시 자주 발생하는 인증서 문제
- httpsURLConnection을 사용하다보면 다음과 같은 인증서 문제를 많이 겪게 될 것이다.
1) 오류메세지 : SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: ...
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
2) 오류 발생 상황
- SSL 인증서가 신뢰하는 기관 인증서가 없거나 SSL/TLS암호화 버전이 맞지 않는 경우 발생
- 연결하려는 서버의 인증서가 신뢰하는 인증기관 인증서 목록(keystore)에 없을 경우 - 사설 인증서일 경우.
- 서버/클라이언트 사이에 사용하려는 TLS 버전이 맞지 않을 때(TLS 1.0 만 지원하는 서버에 1.2로 hand shaking 요청등)
- TLS 통신에 사용하려는 cipher suite 가 오래되거나 지원하지 않음. (JDK 1.8 부터는 sha1 지원 안되고 sha256 이상을 사용해야 한다고 한다.)
3) 해결 가이드 - 수동 SSL 인증서 등록 방법
- http://www.javased.com/index.php?api=javax.net.ssl.TrustManager 참고
- 다음 방법들 중 적용 가능한 방법을 선택하여 사용 하였다.
3.1) 시스템 프로퍼티 추가
System.setProperty("javax.net.ssl.keyStore",context.getKeyFile().getAbsolutePath());
System.setProperty("javax.net.ssl.keyStorePassword", context.getKeyFilePassword());
System.setProperty("javax.net.ssl.keyStoreType", "PKCS12");
3.2) 공인 인증된 인증서로 서버 인증
- InputStream에서 특정 CA를 가져와 그 CA를 사용해 KeyStore를 만든 후 이 KeyStore를 사용하여 TrustManager를 만들고 초기화합니다. 시스템에서는 하나 이상의 CA가 있는 KeyStore에서 TrustManager를 만들고 이를 사용하여 서버 인증서를 검증합니다.
(https://developer.android.com/training/articles/security-ssl?hl=ko#java) 참고
- SSLContext.init() 함수에 인증서를 선언하여 호출 한다.
(기본적으로 [JRE 경로]/lib/security/cacerts 라는 파일명의 공인 인증된 인증서 저장소 파일을 사용하여 서버 인증서 검사가 가능 하다.)
KeyStore clientStore = KeyStore.getInstance("PKCS12");
clientStore.load(new FileInputStream("cert.p12"), "testPass".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, "pass".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(new FileInputStream(System.getProperty("java.home") + "/lib/security/cacerts"), "changeit".toCharArray());
// Get Trust Manager
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] tms = tmf.getTrustManagers();
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, tms, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
3.3) 아무 작업도 하지 않는 TrustManager를 설치하여 우회 하는 방법
- 사실 많은 구글링을 통해 위와 같은 대안을 제시하는경우를 많이 보았다. 물론 나도 위와 같이 처리하여 임시로 인증서 오류를 우회하여 처리하기도 하였다.
- 이와 같이 처리하는 경우 보안 이슈도 있으며, 앱의 경우 배포 리젝도 발생할 수 있으니 조심 하도록 하자. 사용하는 방법은 매우 간단하다.
- 다음 내용을 추가해주면 된다.
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType){
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
ex) naver.com 실습
- 네이버와 같은 경우 위의 예시를 그대로 사용하였을 경우 인증서 오류가 발생하는 것을 볼 수 있다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
public class Test {
public static void main(String[] args) throws Exception {
String urlString = "https://www.naver.com/";
String line = null;
InputStream in = null;
BufferedReader reader = null;
HttpsURLConnection httpsConn = null;
try {
// Get HTTPS URL connection
URL url = new URL(urlString);
httpsConn = (HttpsURLConnection) url.openConnection();
// Set Hostname verification
httpsConn.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Ignore host name verification. It always returns true.
return true;
}
});
// Input setting
httpsConn.setDoInput(true);
// Output setting
//httpsConn.setDoOutput(true);
// Caches setting
httpsConn.setUseCaches(false);
// Read Timeout Setting
httpsConn.setReadTimeout(1000);
// Connection Timeout setting
httpsConn.setConnectTimeout(1000);
// Method Setting(GET/POST)
httpsConn.setRequestMethod("GET");
// Header Setting
httpsConn.setRequestProperty("HeaderKey","HeaderValue");
int responseCode = httpsConn.getResponseCode();
System.out.println("응답코드 : " + responseCode);
System.out.println("응답메세지 : " + httpsConn.getResponseMessage());
// SSL setting
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null); // No validation for now
httpsConn.setSSLSocketFactory(context.getSocketFactory());
// Connect to host
httpsConn.connect();
httpsConn.setInstanceFollowRedirects(true);
// Print response from host
if (responseCode == HttpsURLConnection.HTTP_OK) { // 정상 호출 200
in = httpsConn.getInputStream();
} else { // 에러 발생
in = httpsConn.getErrorStream();
}
reader = new BufferedReader(new InputStreamReader(in));
while ((line = reader.readLine()) != null) {
System.out.printf("%s\n", line);
}
reader.close();
} catch (UnknownHostException e) {
System.out.println("UnknownHostException : " + e);
} catch (MalformedURLException e) {
System.out.println(urlString + " is not a URL I understand");
} catch (IOException e) {
System.out.println("IOException :" + e);
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if (reader != null) {
reader.close();
}
if (httpsConn != null) {
httpsConn.disconnect();
}
}
}
}
- 결과
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
- 3.3 적용하여 호출시
String urlString = "https://www.naver.com/";
String line = null;
InputStream in = null;
BufferedReader reader = null;
HttpsURLConnection httpsConn = null;
try {
// Get HTTPS URL connection
URL url = new URL(urlString);
httpsConn = (HttpsURLConnection) url.openConnection();
// Set Hostname verification
httpsConn.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Ignore host name verification. It always returns true.
return true;
}
});
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType){
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Input setting
httpsConn.setDoInput(true);
// Output setting
//httpsConn.setDoOutput(true);
// Caches setting
httpsConn.setUseCaches(false);
// Read Timeout Setting
httpsConn.setReadTimeout(1000);
// Connection Timeout setting
httpsConn.setConnectTimeout(1000);
// Method Setting(GET/POST)
httpsConn.setRequestMethod("GET");
// Header Setting
httpsConn.setRequestProperty("HeaderKey","HeaderValue");
int responseCode = httpsConn.getResponseCode();
System.out.println("응답코드 : " + responseCode);
System.out.println("응답메세지 : " + httpsConn.getResponseMessage());
// SSL setting
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null); // No validation for now
httpsConn.setSSLSocketFactory(context.getSocketFactory());
// Connect to host
httpsConn.connect();
httpsConn.setInstanceFollowRedirects(true);
// Print response from host
if (responseCode == HttpsURLConnection.HTTP_OK) { // 정상 호출 200
in = httpsConn.getInputStream();
} else { // 에러 발생
in = httpsConn.getErrorStream();
}
reader = new BufferedReader(new InputStreamReader(in));
int rank = 0;
while ((line = reader.readLine()) != null) {
System.out.printf("%s\n", line);
}
reader.close();
} catch (UnknownHostException e) {
System.out.println("UnknownHostException : " + e);
} catch (MalformedURLException e) {
System.out.println(urlString + " is not a URL I understand");
} catch (IOException e) {
System.out.println("IOException :" + e);
} catch (Exception e) {
System.out.println("error : " + e);
} finally {
if (reader != null) {
reader.close();
}
if (httpsConn != null) {
httpsConn.disconnect();
}
}
- 결과
응답코드 : 200
응답메세지 : OK
<!doctype html> <html lang="ko" data-dark="false"> <head> <meta charset="utf-8"> <title>NAVER</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=1190"> <meta name="apple-mobile-web-app-title" content="NAVER"/> <meta name="robots" content="index,nofollow"/> <meta name="description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요"/> <meta property="og:title" content="네이버"> <meta property="og:url" content="https://www.naver.com/"> <meta property="og:image" content="https://s.pstatic.net/static/www/mobile/edit/2016/0705/mobile_212852414260.png"> <meta property="og:description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요"/> <meta name="twitter:card" content="summary"> <meta name="twitter:title" content=""> <meta name="twitter:url" content="https://www.naver.com/"> <meta name="twitter:image" content="https://s.pstatic.net/static/www/mobile/edit/2016/0705/mobile_212852414260.png"> <meta name="twitter:description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요"/> <link rel="stylesheet" href="https://pm.pstatic.net/dist/css/nmain.20200806.css"> <link rel="stylesheet" href="https://ssl.pstatic.net/sstatic/search/pc/css/api_atcmp_200709.css"> <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?1"/> <script type="text/javascript" src="https://pm.pstatic.net/dist/lib/nelo.20200617.js" defer="defer"></script> <script>document.domain="naver.com",window.nmain=window.nmain||{},window.nmain.supportFlicking=!1;var nsc="navertop.v4",ua=navigator.userAgent;window.nmain.isIE=navigator.appName&&0<navigator.appName.indexOf("Explorer")&&ua.toLocaleLowerCase().indexOf("msie 10.0")<0,document.getElementsByTagName("html")[0].setAttribute("data-useragent",ua),window.nmain.isIE&&(Object.create=function(n){function a(){}return a.prototype=n,new a})</script> <script>var darkmode= false;window.naver_corp_da=window.naver_corp_da||{main:{}},window.naver_corp_da.main=window.naver_corp_da.main||{},window.naver_corp_da.main.darkmode=darkmode</script> <script> window.nmain.gv = { isLogin: false,
...
- 정상 응답이 온 것을 볼 수 있다.
- 왠만하면 올바른 인증서를 사용하여 인증하도록 하자.
'2. 웹개발 > JAVA' 카테고리의 다른 글
[Java] 실수할 수 있는 날짜 형식(YYYY vs yyyy) (0) | 2020.12.30 |
---|---|
[Java] URLConnection & HttpURLConnection (2) | 2018.11.18 |
[Java] java 메모리 구조 (0) | 2018.09.19 |
[Java] Eclipse 단축키 정리 (3) | 2018.07.03 |
[Java] html 제너레이션 (html 젠) (1) | 2018.07.01 |
소중한 공감 감사합니다