Android

[Android SSL]Retrofit2로 SSL을 이용한 HTTPS 통신하기 ( Okhttp3 )

남잭슨 2018. 2. 12. 21:26

Android Retrofit2에서 SSL을 사용하여, HTTPS 통신을 진행해보자!



우선, SSL을 구현하기전에

HTTPS란?  SSL이란? SSL인증서란? 암호화 통신방법은? HandShake란 뭘까? 

등의 내용은 간략하게 아래 글(링크)에 소개해놨다.

(  무작정 SSL 을 구현하기보다, 약간의 이해와 함께하면 좋을것 같으니,

SSL 인증서발급이나 Tomcat의 SSL 설정이 필요한 서버단 개발자가 아니라도 아래의 글을 읽어보자!

사실 방문자수를 늘리려는 음흉한 목적이...   )


2018/01/31 - [개발일지] - [SSL]HTTPS통신을 위한 SSL인증서 발급하기(OpenSSL)

2018/02/06 - [개발일지] - [SSL]HTTPS 통신을 위한 Tomcat SSL 설정하기


자 그럼 이제, Retrofit2란 무엇일까?

안타깝게도 아직 Retrofit2에 대한 글을 작성하지 않았기때문에

(추후 Retrofit 소개 및 사용에 대한 글을 쓰겟습니당)

간단하게 소개를 하겠다.


Android에 관심이 많은 개발자라면 Retrofit 라이브러리를 모르진 않을것이다.

여러종류의 통신 라이브러리가 있지만, 레스토핏 라이브러리는 아마도 가장 많이 사용되는 대표 라이브러리이다.


통신 라이브러리를 사용하지 않고,

통신기능을 구현한다면, 매우 번거롭고 짜증나는 작업이 된다.


이런 통신기능을 더쉽게 사용하기위해 여러 종류의 라이브러리들이 존재한다.

대표적으로 Android SDK에서 공식으로 지원했던 HttpUrlConnection (현재는 Deprecated된..) , 구글의 Volley ,

Square의 OKHttp , Retrofit 등이 있다.


현재 HttpUrlConnection는 Deprecated되었고,

그에따라서 HttpUrlConnection를 사용하던 Volley도 내부적으로는 OKHttp를 많이 사용한다.

Retrofit은 Okhttp를 더 쉽게 사용할수있게하는 라이브러리다 .

( OKHTTP의 래퍼클래스라고 생각하면된다. 내부적으로는 OKHTTP를 사용한다. )


Retrofit의 장점으로는

1. 구현하기 쉬운편이다.

2. 모듈화되어 재사용및 유지보수에 용이하다.

3. 인터페이스 정의로 가독성이 좋다.

4. 성능이 좋은편이다.


이쯤되면 Retrofit을 사용하지 않을 이유가 없다!

이글이 Retrofit2 소개와 사용법에 대한 글이 아니기때문에 소개는 여기까지하겠다.

필요시 다른 Retrofit2글을 참조해보자


Android Retrofit2에서 SSL을 사용하여 Https 통신을 구현해보자


오늘 구현할 방법은 2가지이다.

1. 인증서없이 Https사이트를 우회접속 통신 구현하기.

2. 정상적으로 SSL 인증서를 가지고 SSL을 이용한 HTTPS통신을 구현하기/



먼저 첫번쨰 방법인

1. 인증서없이 Https사이트를 우회접속 통신 구현하기.

이 방법은 Android App내에 인증서를 가지고 있지않을때 임시적으로 사용하는방법이다.

사실상 SSL이 적용되지 않는 통신을 한다.


아래의 스크린샷처럼,  인증서 없이도, SSL에 적용된 웹사이트에 접근이 가능하다 .

( 이 웹사이트를 계속 탐색합니다. (권장하지 않음)). 으로 우회 접근이 가능하다.

이방법도 위의 우회접속과 같은 기능이다 .



먼저 Retrofit2에서 OkHTTP3를 사용하기때문에

SSL 우회접속이 가능한 OkhttpClient를 만들어줍니다.


   // 안전하지 않음으로 HTTPS를 통과합니다.
    public static OkHttpClient.Builder getUnsafeOkHttpClient() {
        try {
            final TrustManager[] trustAllCerts = new TrustManager[] {
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
            return builder;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


선언 후 , Retrofit2를  생성해줄때, 아래와같이 해당 OkhttpClinet를 선언해주면 된다.


public static Retrofit createRetrofit(Context context) {
        return new Retrofit.Builder()
                .baseUrl(SERVER_URL)
                .client(getUnsafeOkHttpClient().build())
                .build();
    }


이것은 인증을 하지않고, 안전한지않은 통신 방법이다.


두번째 방법은 인증서로

2. 정상적으로 SSL 인증서를 가지고 SSL을 이용한 HTTPS 통신 구현하기

먼저 서버와 같은 SSL 인증서가 필요하다. 


res 디렉토리 아래에 raw라는 디렉토리를 만들고

확장자가 crt인 인증서를 넣어준다.

그리고 아래의 코드처럼 R.raw.localhost를 가지고와서 작업을한다.

 /**
     * localhost라는 인증서 정보로 ssl 연결을시도합니다.
     * @param context
     * @return
     */
    public static SSLSocketFactory getPinnedCertSslSocketFactory(Context context) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = context.getResources().openRawResource(R.raw.localhost);
            Certificate ca = null;
            try {
                ca = cf.generateCertificate(caInput);
                System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
            } catch (CertificateException e) {
                e.printStackTrace();
            } finally {
                caInput.close();
            }

            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            if (ca == null) {
                return null;
            }
            keyStore.setCertificateEntry("ca", ca);

            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);

            SSLContext sslContext= SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);

            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


마찬가지로 위의 메소드 선언 후 ,

Retrofit2를  생성해줄때, 아래와같이 해당 OkhttpClinet를 선언해주면 된다.

public static Retrofit createRetrofit(Context context) {
        return new Retrofit.Builder()
                .baseUrl(SERVER_URL)
                .client(
                        new OkHttpClient.Builder()
                        .sslSocketFactory(SSLUtil.getPinnedCertSslSocketFactory(context))   //ssl 
                        .hostnameVerifier(new NullHostNameVerifier())                       //ssl HostName Pass
                        .build())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                  // Rxandroid 
                .addConverterFactory(GsonConverterFactory.create())                         // JSON
                .build();
    }


SSL 적용이 끝났다! 테스트를 진행해보자 !!



※ 단, 위의 코드 중

 .hostnameVerifier(new NullHostNameVerifier())  

의 경우는 SSL인증서가 테스트용이기때문에 추가해주었다.

( SSL의 인증서의 경우 도메인값이 필수기 때문에, 해당 도메인별 SSL인증서가 필요하지만

 나는 테스트용 SSL인증서를 발급받았기때문에 도메인이름 검증을 생략하기위함이다.

궁금하다면, 아래 링크 참조!

2018/01/31 - [개발일지] - [SSL]HTTPS통신을 위한 SSL인증서 발급하기(OpenSSL))


정식 SSL 인증서일 경우에는 생략하는게 좋다.


*필요시 아래의 

javax.net.ssl.HostnameVerifier

를 상속받은 커스텀 클래스를 선언한뒤 사용하면된다.

public class NullHostNameVerifier implements HostnameVerifier {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
}


전체적인 코드는 아래 링크( 깃허브 )에 있습니다.

https://github.com/namjackson/MyUtility/tree/master/app/src/main/java/com/namjackson/util/myutility/http


혹시 도움이되셧다면!

해당링크의 를 눌러주세요!


GitHub의 Star를 남겨주세요