본문 바로가기

Kotlin

[Kotlin] 명언 API 불러오기 및 Android 인증서 문제 API Failure: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

문제점

 

kotlin 강의를 수강하면서 명언리스트 페이지를 제작했다. 원래 기존에 사용하던 방식은 List 를 만들어서 그냥 내가 원하는 명언을 add 하는 방식으로 제작했는데, 명언 검색하기도 귀찮고 그냥 랜덤으로 명언을 가져다주는 api 를 활용하면 좋을 것 같았다. 

 

        // 기존의 로컬 명언 리스트 (예시)
        val sentenceList = mutableListOf<String>()
        sentenceList.add("검정 화면에 대충 흰글씨 쓰면 명언같다.")

 

그래서 일단 명언을 랜덤으로 가져다주는 api 가 있나 검색해 본 결과, 아래와 같이 랜덤 한국어 명언 api가 있었다.

https://github.com/gwongibeom/korean-advice-open-api

 

GitHub - gwongibeom/korean-advice-open-api: 랜덤 한국어 명언 API

랜덤 한국어 명언 API. Contribute to gwongibeom/korean-advice-open-api development by creating an account on GitHub.

github.com

 

이런식으로 데이터는 author, authorProfile, message 를 String 타입으로 받는다.

 

Gradle Scripts > build.gradle.kts 에 의존성을 다음과 같이 추가해준다.

dependencies {
    // Retrofit 라이브러리
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    // Retrofit Gson 변환기
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
}

 

API 요청을 정의하는 인터페이스, 메서드를 만들어 준 후

 

package com.example.goodword

data class Word(
    val author: String,
    val authorProfile: String,
    val message: String,
)
package com.example.goodword

import retrofit2.Call
import retrofit2.http.GET

interface ApiService {
    // API 요청을 정의하는 메서드
    @GET("advice")
    fun getWords(): Call<Word>
}

 

 

MainActivity 에서 다음과 같이 작성한 후 실행을 돌렸는데 바로 에러가 났다.

val retrofit = Retrofit.Builder()
            .baseUrl("https://korean-advice-open-api.vercel.app/api/") // API의 base URL. 슬래시로 끝나야함
            .addConverterFactory(GsonConverterFactory.create())
            .build()

val apiService = retrofit.create(ApiService::class.java)

apiService.getWords().enqueue(object : Callback<Word> {
    override fun onResponse(call: Call<Word>, response: Response<Word>) {
        if (response.isSuccessful) {
            val word = response.body()
            Log.d("MainActivity", "Word fetched: ${word}")

            // 받아온 명언을 goodwordTextArea에 표시
            word?.let {
                binding.goodwordTextArea.setText(it.message)
            }
        } else {
            Log.e("MainActivity", "Response Error: ${response.code()}")
        }
    }

    override fun onFailure(call: Call<Word>, t: Throwable) {
        Log.e("MainActivity", "API Failure: ${t.message}")
    }
})

 

그래서 찾아보니 이 에러는 SSL 인증서 문제 때문에 발생하고 즉, Retrofit이 서버의 SSL 인증서를 신뢰할 수 없어서 요청을 막고 있는 상태라는 것

 


시도해본 것

 

res > xml 에 network_security_config.xml 를 만들어 사용하고자 하는 해당 도메인에 HTTP 및 HTTPS  트래픽을 모두 허용하는 방식 이라고 한다. 

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">korean-advice-open-api.vercel.app</domain>
    </domain-config>
</network-security-config>

 

AndroidManifest.xml 에서 이 설정을 추가만 해주면 되는데, 다음과 같이 <application> 태그안에 추가 해주면된다.

 android:networkSecurityConfig="@xml/network_security_config"

 

근데 안됐음 .... 이유는 나중에 분석해봐야겠다.


해결방안

 

여러 블로그를 검색해 본 결과, 직접 웹 사이트의 인증서를 다운받아서 추가해주는 방식이 가장 확실한 것 같았다. 따라서 웹 사이트에 접속해서 인증서를 다운받자. 인증서 다운방법은 아래 블로그를 참고했다. (인증서는 영문 소문자로 저장한 후에 res/raw/ 폴더 안에 넣어주자)

 

https://velog.io/@incava/Android-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EB%AC%B8%EC%A0%9C-javax.net.ssl.SSLHandshakeException-java.security.cert.CertPathValidatorExceptionTrust-anchor-for-certification-path-not-found

 

[Android] 인증서 문제 - javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException:Trust anchor for

문제확인 브라우저,IOS에서는 정상 작동 되지만 Android에서는 인증서 문제로 접속이 안되는 경우 "Trust anchor for certification path not found"에 대한 문제가 뜨는 경우 기존에는 정상적으로 작동하였지만

velog.io

 

 

 

이후 위와같이 폴더 및 파일을 생성한 후에

 

RawResourceReader.kt

 

이 파일은 res/raw/ 폴더에 저장된 인증서를 읽는 유틸 클래스로 , 아래의 getSecureOKHttpClient() 를 이용해서 인증서를 불러올 것!

import android.content.Context
import java.io.InputStream

object RawResourceReader {
    fun readRawResource(context: Context, resId: Int): InputStream {
        return context.resources.openRawResource(resId)
    }
}

 

 

getSecureOkHttpClient.kt (SSL 적용된 OkHttpClient)

 

인증서를 OkHttpClient 에 적용하는 함수로 Retrofit에서 getSecureOkHttpClient(context)를 사용해서 안전한 요청을 보낼 수 있음

import android.content.Context
import okhttp3.OkHttpClient
import java.io.InputStream
import java.security.KeyStore
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

fun getSecureOkHttpClient(context: Context): OkHttpClient {
    try {
        // 인증서 로드
        val cf = CertificateFactory.getInstance("X.509")
        val certInputStream: InputStream = RawResourceReader.readRawResource(context, R.raw.my_cert) // 🔥 res/raw/my_cert.cer 읽기
        val ca: X509Certificate = cf.generateCertificate(certInputStream) as X509Certificate
        certInputStream.close()

        // KeyStore에 인증서 추가
        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        keyStore.load(null, null)
        keyStore.setCertificateEntry("ca", ca)

        // TrustManager 설정
        val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        tmf.init(keyStore)
        val trustManagers = tmf.trustManagers
        val trustManager = trustManagers[0] as X509TrustManager

        // SSLContext 설정
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(null, arrayOf(trustManager), null)

        // OkHttpClient 설정
        return OkHttpClient.Builder()
            .sslSocketFactory(sslContext.socketFactory, trustManager)
            .build()
    } catch (e: Exception) {
        throw RuntimeException(e)
    }
}

 

 

RetrofitClient.kt (Retrofit 인스턴스 생성)

 

import android.content.Context
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    fun createRetrofit(context: Context): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://korean-advice-open-api.vercel.app/api/") // API 주소
            .client(getSecureOkHttpClient(context)) // SSL 적용된 OkHttpClient 사용
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

 

이 후 MainActivity 에서 인스턴스 생성을 다음과 같이 해주면 된다.

 

 // Retrofit 인스턴스 생성 (SSL 적용)
        val retrofit = RetrofitClient.createRetrofit(this)
        val apiService = retrofit.create(ApiService::class.java)

 

 

 

데이터가 아주 잘 받아와지는 것을 확인할 수 있다!

다음에는 이걸로 리스트 뷰 아이템을 생성해서 10개의 랜덤명언을 적용시켜봐야겠다.

 

'Kotlin' 카테고리의 다른 글

[Kotlin] 변수와 데이터 타입  (0) 2024.12.16