JSP/SERVLET
2018.03.31 / 16:24

[Java] Apache Commons HttpClient로 SSL 통신하기

봄이
추천 수 45

* apache httpclient 4.4 이상 버전

import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.SSLContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;

HttpClientBuilder httpClientBuilder = HttpClients.custom();

SSLContext sslcontext = SSLContexts.custom()
    .useProtocol("SSL")
// .loadTrustMaterial(null, new TrustSelfSignedStrategy())
    .loadTrustMaterial(null, new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException {
            return true;
        }
    }).build();

httpClientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier()).setSSLContext(sslcontext);

CloseableHttpClient httpClient = httpClientBuilder.build();
httpClient.execute();



* apache httpclient 4.3 이하 버전

import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

SSLContext sslcontext = SSLContexts.custom()
    .useSSL()
    .loadTrustMaterial(null, new TrustSelfSignedStrategy())
/*
    .loadTrustMaterial(null, new TrustStrategy() {
        @Override
        public boolean isTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString) throws java.security.cert.CertificateException {
            return true;
        }
    })
*/
    .build();

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

CloseableHttpClient httpClient = HttpClients.custom()
    .setSSLSocketFactory(sslsf)
    .build();

httpClient.execute();




===========================================================================================


출처 : 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


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