아래 코드를 사용하기 위해서는 안드로이드의 res - raw - 인증서.crt 파일이 있어야하고, 서버에는 jks 파일이 등록되어있어야한다.
SSL 파일을 구하는 방법은 이전에 포스팅 해놓은 글을 참고하면 될 듯 하다
필자는 fullchain.pem을 확장자만 crt로 바꿔서 사용했다.
서버에 적용하는 jks 파일은 아래 게시글을 참고하면된다.
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
'프로그래밍 > Kotlin' 카테고리의 다른 글
[Android] Adapter의 아이템 위치찾기 (0) | 2022.03.03 |
---|---|
View의 Visible, Gone, invisible 차이 (0) | 2022.02.09 |
[Kotlin] 카카오 API를 이용해 공유하기 (0) | 2021.12.22 |
[Kotlin] Dialog 종료시 커스텀 리스너 동작 (0) | 2021.12.14 |
[Kotlin] 전체화면으로 DialogFragment 열기 (0) | 2021.12.13 |
댓글