Commons HttpClient로 SSL 통신하기 ::

[출처] http://www.java2go.net/blog/197?TSSESSION=1202a1a23fa67bae15ce3ab15a5a0cea

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: 200Response Status Line: HTTP/1.1 200 OKResponse 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: 404Response Status Line: HTTP/1.1 404 Not FoundResponse 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 foundat 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 foundat 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 cacertsEnter keystore password: changeitKeystore type: jksKeystore provider: SUNYour keystore contains 52 entriesAlias name: verisignclass3g2caCreation date: Jun 16, 2004Entry type: trustedCertEntryOwner: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorizeduse only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign,Inc.", C=USIssuer: OU=VeriSign Trust Network, OU="(c) 1998 VeriSign, Inc. - For authorizeduse only", OU=Class 3 Public Primary Certification Authority - G2, O="VeriSign,Inc.", C=USSerial number: 7dd9fe07cfa81eb7107967fba78934c6Valid from: Mon May 18 09:00:00 KST 1998 until: Wed Aug 02 08:59:59 KST 2028Certificate fingerprints:MD5: A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9SHA1: 85:37:1C:A6:E5:50:14:3D:CE:28:03:47:1B:DE:3A:09:E8:F8:77:0F**************************************************************************************Alias name: entrustclientcaCreation date: Jan 10, 2003Entry type: trustedCertEntryOwner: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.netLimited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab.,O=Entrust.net, C=USIssuer: CN=Entrust.net Client Certification Authority, OU=(c) 1999 Entrust.netLimited, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab.,O=Entrust.net, C=USSerial number: 380391eeValid from: Wed Oct 13 04:24:30 KST 1999 until: Sun Oct 13 04:54:30 KST 2019Certificate fingerprints:MD5: 0C:41:2F:13:5B:A0:54:F5:96:66:2D:7E:CD:0E:03:F4SHA1: 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 tradesigncaEnter keystore password:  changeitOwner: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KRIssuer: CN=KISA RootCA 1, OU=Korea Certification Authority Central, O=KISA, C=KRSerial number: 2764Valid from: Tue Nov 15 11:14:59 KST 2005 until: Sun Nov 15 11:14:59 KST 2015Certificate 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:BETrust this certificate? [no]:  yCertificate was added to keystore

이렇게 서버 CA 인증서가 신뢰하는 인증서로 등록이 되면, 일반 HTTP 통신과 같이 URL이 https://인 주소로 SSL 통신을 정상적으로 할 수 있다. 키스토어 파일에서 인증서를 제거하는 방법은 아래와 같다.
C:\jdk1.4.2\jre\lib\security>keytool -delete -keystore cacerts -alias tradesigncaEnter keystore password:  changeit

아래처럼 서버 싸이트 인증서를 바로 등록할 수도 있다. 그러나 싸이트 인증서는 보통 Trusted CA 인증서보다 유효기간이 짧아 매번 갱신을 해줘야 할 것이다.
C:\jdk1.4.2\jre\lib\security>keytool -import -keystore cacerts -filec:\certs\www.java2go.net.cer -alias mykeyEnter keystore password:  changeitOwner: CN=www.java2go.net, OU=KTNET, OU=AccreditedCA, O=TradeSign, C=KRIssuer: CN=TradeSignCA, OU=AccreditedCA, O=TradeSign, C=KRSerial number: 596e9cf0Valid from: Tue May 12 13:37:20 KST 2009 until: Wed May 12 14:07:20 KST 2010Certificate 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:2DTrust this certificate? [no]:  yCertificate 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

참조: http://hc.apache.org/httpclient-3.x/sslguide.html

+ Recent posts