본문 바로가기
프로그래밍/Kotlin

[Retrofit] HTTPS 통신, 안드로이드 <-> 서버

by Youngs_ 2021. 12. 24.

아래 코드를 사용하기 위해서는 안드로이드의 res - raw - 인증서.crt 파일이 있어야하고, 서버에는 jks 파일이 등록되어있어야한다.

SSL 파일을 구하는 방법은 이전에 포스팅 해놓은 글을 참고하면 될 듯 하다

https://youngsblog.tistory.com/entry/SSL-%EC%9C%88%EB%8F%84%EC%9A%B0%EC%97%90%EC%84%9C-DuckDNS%EC%99%80-Lets-Encrypt%EB%A1%9C-SSL-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%AC%B4%EB%A3%8C%EB%A1%9C%EB%B0%9B%EA%B8%B0

 

[SSL] 윈도우에서 DuckDNS와 Let's Encrypt로 SSL 인증서 무료로받기

1. 도메인 생성 및 wacs 설치 https://www.duckdns.org/domains Duck DNS Duck DNS free dynamic DNS hosted on AWS news: login with Reddit is no more - legal request support us: become a Patreon www.duck..

youngsblog.tistory.com

필자는 fullchain.pem을 확장자만 crt로 바꿔서 사용했다.

 

서버에 적용하는 jks 파일은 아래 게시글을 참고하면된다.

https://youngsblog.tistory.com/entry/SSL-%EC%9C%88%EB%8F%84%EC%9A%B0%EC%97%90%EC%84%9C?category=968021 

 

[SSL] 윈도우에서 SSL 인증서 적용

이전에 작성했던 아래글과 이어지는 포스팅이다. https://youngsblog.tistory.com/entry/SSL-%EC%9C%88%EB%8F%84%EC%9A%B0%EC%97%90%EC%84%9C-DuckDNS%EC%99%80-Lets-Encrypt%EB%A1%9C-SSL-%EC%9D%B8%EC%A6%9D%EC%..

youngsblog.tistory.com

 

 

HTTPS로 서버와 통신하는 함수

@RequiresApi(Build.VERSION_CODES.N)
    suspend fun connectHTTPS(path : String, param : JsonObject
                             , context : Context // 실패했을때 토스트메시지를 띄워주기 위한 컨텍스트
                             , onSuccess : () -> Unit // 성공했을때 실행할 함수(이벤트)
    ){
        val okHttpClient = OkHttpClient.Builder().connectTimeout(30, TimeUnit.SECONDS).readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build() // n초동안 기다리도록 만드는 변수
        val selfSign = SelfSigningHelper(context).setSSLOkHttp(okHttpClient.newBuilder()).build() // SSL인증서를 인증하기 위한 코드, SSL 인증서가 없다면 해당 부분은 없어도된다.
        val RETROFIT = Retrofit.Builder().baseUrl(Define.BASE_URL_HTTPS).client(selfSign).addConverterFactory(GsonConverterFactory.create()).build()
        val SERVER : RetrofitService = RETROFIT.create(RetrofitService::class.java) // RetrofitService에 만든 서비스를 사용하기 위한 변수

        SERVER.connectRequest(path, param).enqueue(object : Callback<ResponseDTO>{
            override fun onResponse(call: Call<ResponseDTO>?, response: Response<ResponseDTO>?) {

                Log.d("HTTPS", response?.code().toString())

                if(response?.isSuccessful ?: false) {
                    resultString = response?.body()?.returnValue.toString()
                    onSuccess()

                }
                else {
                    Toast.makeText(context, "서버와 연결을 시도했으나 실패했습니다.", Toast.LENGTH_SHORT).show()
                }

                NetworkConnect.endProgress()

            }
            override fun onFailure(call: Call<ResponseDTO>?, t: Throwable?) {
                NetworkConnect.endProgress()
                Toast.makeText(context, "인터넷 연결을 확인하여주십시오.", Toast.LENGTH_SHORT).show()
                Log.d("HTTPS", t?.message.toString())

            }
        })
    }

 

SelfSigningHelper.kt -> SSL 인증서를 인증하기 위한 클래스

import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import com.youngsbook.R
import okhttp3.OkHttpClient
import java.io.IOException
import java.io.InputStream
import java.security.KeyManagementException
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

@RequiresApi(Build.VERSION_CODES.N)
class SelfSigningHelper constructor(context: Context
) {
    lateinit var tmf: TrustManagerFactory
    lateinit var sslContext: SSLContext

    init {
        val cf: CertificateFactory
        val ca: Certificate

        val caInput: InputStream

        try {
            cf = CertificateFactory.getInstance("X.509")

            caInput = context.resources.openRawResource(R.raw.youngsbook)

            ca = cf.generateCertificate(caInput)
            println("ca = ${(ca as X509Certificate).subjectDN}")

            // Create a KeyStore containing our trusted CAs
            val keyStoreType = KeyStore.getDefaultType()
            val keyStore = KeyStore.getInstance(keyStoreType)
            with(keyStore) {
                load(null, null)
                keyStore.setCertificateEntry("ca", ca)
            }

            // Create a TrustManager that trusts the CAs in our KeyStore
            val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
            tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
            tmf.init(keyStore)

            // Create an SSLContext that uses our TrustManager
            sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, tmf.trustManagers, java.security.SecureRandom())

            caInput.close()

        } catch (e: KeyStoreException) {
            e.printStackTrace()
        } catch (e: CertificateException) {
            e.printStackTrace()
        } catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        } catch (e: KeyManagementException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    fun setSSLOkHttp(builder: OkHttpClient.Builder): OkHttpClient.Builder {
        builder.sslSocketFactory(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager)

        return builder
    }
}

interface.kt

interface RetrofitService {
    //post1
    // 매개변수를 미리 정해두는 방식
    @FormUrlEncoded
    @POST("/test")
    fun postRequest(
        @Field("id") id: String,
        @Field("pw") pw: String
    ): Call<ResponseDTO>

    //post2
    // 호출하는 곳에서 매개변수를 HashMap 형태로 보내는 방식
    @POST("{path}")
    fun connectRequest(
        @Path("path") retrofitPath: String,
        @Body parameters: JsonObject
    ): Call<ResponseDTO>

//    @GET("/api/users?page=2")
//    fun getTest(): Call<Any>

    @GET("server/status/json?__ts=1640318537873") //
    fun loadNotice(@Query("page") page: String): Call<Baemin>

}

사용법

val enterLogin : JsonObject = JsonObject()
                enterLogin.addProperty("ID", binding.userid!!.text.toString())
                enterLogin.addProperty("PASSWORD", binding.password.text.toString())
                NetworkConnect.startProgress(this) // NetworkConnect 클래스 안에 구현
                CoroutineScope(Dispatchers.Default).launch {
                    NetworkConnect.connectHTTPS("login.do",
                        enterLogin,
                        applicationContext // 실패했을때 Toast 메시지를 띄워주기 위한 Context
                        , onSuccess = { ->
                           

                        }
                    )

                }

이전에 작성한 HTTP 코드와 거의 유사하다.

https://youngsblog.tistory.com/entry/Android-Spring-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%86%B5%EC%8B%A0

 

RetroFit을 이용한 [HTTP] Android -> Spring 데이터 통신

androidManifest.xml <?xml version="1.0" encoding="utf-8"?> ////////////////////////////////// // 아래코드는 선택사항입니다. https뿐만아니라 http도 접속가능하게 해주는 코드 build.gradle dependencies..

youngsblog.tistory.com

 

댓글