* apache httpclient 4.4 이상 버전
import org.apache.http.impl.client.HttpClientBuilder; |
* apache httpclient 4.3 이하 버전
import javax.net.ssl.SSLContext; |
===========================================================================================
출처 : http://comphy.iptime.org/user/wordpress/?p=81
Apache Commons HttpClient는 JDK 1.4부터 등장한 Java Secure Socket Extension (JSSE)를 기반으로 SSL (또는 TLS) 상의 HTTP (HTTP/S) 통신에 대한 지원을 제공한다. Commons HttpClient를 이용한 HTTP/S 통신 방법을 살펴보자.
1. Commons HttpClient 사용하기
일반적으로 Commons HttpClient의 HTTP 통신은 아래와 같다.
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; // import org.apache.commons.httpclient.methods.PostMethod; public class HttpClientSample { public static void main(String[] args) { HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("http://www.java2go.net/"); // PostMethod httppost = new // PostMethod("https://www.java2go.net/nopage.html"); try { int statusCode = httpclient.executeMethod(httpget); System.out.println("Response Status Code: " + statusCode); System.out.println("Response Status Line: " + httpget.getStatusLine()); System.out.println("Response Body: \n" + httpget.getResponseBodyAsString()); if (statusCode == HttpStatus.SC_OK) { // if (statusCode >= 200 && statusCode < 300) { System.out.println("Success!"); } else { System.out.println("Fail!"); } } catch (Exception e) { e.printStackTrace(); } finally { httpget.releaseConnection(); } } } |
정상적인 경우 결과는 아래와 같다.
Response Status Code: 200 Response Status Line: HTTP/1.1 200 OK Response Body: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Java2go.net</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> ... 생략 ... </body> </html> Success! |
실패한 경우 결과는 아래와 같다.
Response Status Code: 404 Response Status Line: HTTP/1.1 404 Not Found Response Body: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <HTML><HEAD> <TITLE>404 Not Found</TITLE> </HEAD><BODY> <H1>Not Found</H1> The requested URL /nopage.html was not found on this server.<P> <HR> <ADDRESS>Apache/1.3.37p3 Server at java2go.net Port 80</ADDRESS> </BODY></HTML> Fail! |
2. SSL 통신과 Trusted CA 인증서 등록하기
JSSE가 올바르게 설치되었다면, 기본적으로 HTTP/S 통신도 일반 HTTP 통신과 같이 위와 같은 코드를 그대로 사용할 수 있다. 단, 이 경우에 서버 싸이트 인증서가 클라이언트쪽에 신뢰하는 인증서로서 인식될 수 있어야 한다. 그렇지 않으면 아래와 같은 SSL handshake 오류가 발생한다.
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SunJSSE_ax.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.j(DashoA12275) at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275) at com.sun.net.ssl.internal.ssl.AppOutputStream.write(DashoA12275) ... 생략 ... Caused by: sun.security.validator.ValidatorException: No trusted certificate found at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:304) at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:107) at sun.security.validator.Validator.validate(Validator.java:202) at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(DashoA12275) at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(DashoA12275) ... 17 more |
JDK에 의해 제공되어지는 Java Standard Trust Keystore는 ${JAVA_HOME}/jre/lib/security/cacerts에 위치한다. 이 cacerts 키스토어 파일에 대상 서버의 SSL 싸이트 인증서를 발행한 기관의 CA 인증서가 신뢰하는 인증서로 등록되어 있어야 한다. 다음과 같이 keytool.exe를 사용하여 키스토어에 등록된 신뢰하는 인증서 목록을 조회할 수 있다.
C:\jdk1.4.2\jre\lib\security>keytool -list -v -keystore cacerts Enter keystore password: changeit Keystore type: jks Keystore provider: SUN Your keystore contains 52 entries Alias name: verisignclass3g2ca Creation date: Jun 16, 2004 Entry type: trustedCertEntry Owner: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorized use only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign, Inc.", C=US Issuer: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorized use only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign, Inc.", C=US Serial number: 7dd9fe07cfa81eb7107967fba78934c6 Valid from: Mon May 18 09:00:00 KST 1998 until: Wed Aug 02 08:59:59 KST 2028 Certificate fingerprints: MD5: A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9 SHA1: 85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F ******************************************* ******************************************* Alias name: entrustclientca Creation date: Jan 10, 2003 Entry type: trustedCertEntry Owner: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.net Limited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., O=Entrust.net, C=US Issuer: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.net Limited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., O=Entrust.net, C=US Serial number: 380391ee Valid from: Wed Oct 13 04:24:30 KST 1999 until: Sun Oct 13 04:54:30 KST 2019 Certificate fingerprints: MD5: 0C:41:2F:13:5B:A0:54:F5:96:66:2D:7E:CD:0E:03:F4 SHA1: DA:79:C1:71:11:50:C2:34:39:AA:2B:0B:0C:62:FD:55:B2:F9:F5:80 ... 생략 ... |
다음과 같은 방법으로 키스토어 파일에 Trusted CA 인증서를 추가로 등록할 수 있다. CA 인증서는 웹브라우저에서 열쇠모양의 아이콘을 누르면 해당 싸이트 인증서를 볼 수 있고, 거기에서 인증서를 복사할 수 있다. 아래 예시는 Trusted CA 인증서를 ${JAVA_HOME}\jre\lib\secutiry\cacerts에 등록을 하는 방법이다.
C:\j2sdk1.4.2\jre\lib\security>keytool -import -keystore cacerts -file c:\certs\TradeSignCA.cer -alias tradesignca Enter keystore password: changeit Owner: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KR Issuer: CN=KISA RootCA 1, OU=Korea Certification Authority Central, O=KISA, C=KR Serial number: 2764 Valid from: Tue Nov 15 11:14:59 KST 2005 until: Sun Nov 15 11:14:59 KST 2015 Certificate fingerprints: MD5: C2:E0:27:3D:36:4B:86:29:74:4D:6B:9F:5A:B5:01:26 SHA1: A0:CD:6A:6D:A4:7B:73:15:F5:8A:CB:1F:C6:FD:C2:14:C9:3B:5D:BE Trust this certificate? [no]: y Certificate was added to keystore |
이렇게 서버 CA 인증서가 신뢰하는 인증서로 등록이 되면, 일반 HTTP 통신과 같이 URL이 https://인 주소로 SSL 통신을 정상적으로 할 수 있다. 키스토어 파일에서 인증서를 제거하는 방법은 아래와 같다.
C:\jdk1.4.2\jre\lib\security>keytool -delete -keystore cacerts -alias tradesignca Enter keystore password: changeit |
아래처럼 서버 싸이트 인증서를 바로 등록할 수도 있다. 그러나 싸이트 인증서는 보통 Trusted CA 인증서보다 유효기간이 짧아 매번 갱신을 해줘야 할 것이다.
C:\jdk1.4.2\jre\lib\security>keytool -import -keystore cacerts -file c:\certs\www.java2go.net.cer -alias mykey Enter keystore password: changeit Owner: CN=www.java2go.net, OU=KTNET, OU=AccreditedCA, O=TradeSign, C=KR Issuer: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KR Serial number: 596e9cf0 Valid from: Tue May 12 13:37:20 KST 2009 until: Wed May 12 14:07:20 KST 2010 Certificate fingerprints: MD5: EF:EB:11:66:BD:CC:B1:D4:88:35:AB:25:9F:2F:79:8B SHA1: DC:C4:31:20:46:25:72:68:8B:96:AC:92:EE:F3:8D:15:EF:A7:46:2D Trust this certificate? [no]: y Certificate was added to keystore |
3. Commons HttpClient의 SSL 커스터마이징
기본적인 사용법 이외에 자기서명(self-signed)되었거나 또는 신뢰되지 않은(untrusted) SSL 인증서를 사용하는 경우처럼 SSL 통신을 커스터마이징할 필요가 있을 수 있다.
기본 커스터마이징 방법은 org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory을 구현해서 org.apache.commons.httpclient.protocol.Protocol를 생성하여 등록해주면 된다. 다음과 같은 코드 한 줄을 추가해 주면 된다. 자세한 내용은 이곳을 참조한다.
Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); |
Commons HttpClient의 contribution 패키지에서 사용할 수 있는 EasySSLProtocolSocketFactory를 사용하면 신뢰되지 않은 자기서명(self-signed)된 인증서를 가진 서버와도 바로 SSL 통신을 할 수 있다. 즉, cacerts 키스토어에 서버 인증서를 별도로 등록할 필요가 없다. 다음과 같이 사용할 수 있다.
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.protocol.Protocol; public class HttpClientSample2 { public static void main(String[] args) { HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.java2go.net/"); try { Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(), 443)); int statusCode = httpclient.executeMethod(httpget); System.out.println("Response Status Code: " + statusCode); System.out.println("Response Status Line: " + httpget.getStatusLine()); System.out.println("Response Body: \n" + httpget.getResponseBodyAsString()); if (statusCode == HttpStatus.SC_OK) { System.out.println("Success!"); } else { System.out.println("Fail!"); } } catch (Exception e) { e.printStackTrace(); } finally { httpget.releaseConnection(); } } } |
호출 로그
{DEBUG} [2009-06-18 23:29:31,062] <org.apache.commons.httpclient.HttpConnection> () : Open connection to www.java2go.net:443 |