Skip to content
마지막 작성자: hyejung-song
최근 업데이트: 25. 7. 8. 오후 1:09

App2App Android 연동 가이드 ​

App2App 방식으로 모바일 신분증과 연동하려는 Android 앱을 위한 단계별 구현 가이드예요.

1. 인터페이스 방식 ​

App2App direct mode ​

  • 서비스 앱이 직접 모바일 신분증 앱으로 M200 메시지를 전달하고 결과를 SP 서버로 직접 전송해요.

App2App 연동 흐름도

App2App indirect mode ​

  • 서비스 앱이 M200 메시지를 모바일 신분증 앱에 전달하면, 신분증 앱이 결과를 다시 서비스 앱으로 반환해요. 이후 서비스 앱이 SP 서버에 VP를 전송해요.

App2App 연동 흐름도

2. Android 개발 가이드 ​

indirect mode ​

모바일 신분증 앱 설치 여부 확인 ​

AndroidManifest.xml에 아래 요소를 추가해요.

xml
<manifest>
    <queries>
        <package android:name="kr.go.mobileid" />
        <package android:name="com.samsung.android.ssiframework" />
    </queries>
</manifest>

앱 설치 여부를 확인하는 코드예요.

kotlin
private const val SCHEME_CA = "mobileid://"
private const val SCHEME_FW = "mobileidfw://"
private var scheme = SCHEME_FW

val intent = Intent("verify", Uri.parse("mobileidfw://verify")).apply {
    setPackage("com.samsung.android.ssiframework")
}

val component = intent.resolveActivity(context.packageManager)

if (component == null) {
    Toast.makeText(this, "지갑24 앱 미설치", Toast.LENGTH_SHORT).show()
} else {
    Toast.makeText(this, "지갑24 앱 설치되어 있음", Toast.LENGTH_SHORT).show()
    scheme = SCHEME_FW
}

서비스 앱에서 SP 서버로 M200 메시지 요청 ​

kotlin
private fun getM200() {
    val t530 = T530("indirect", "mdl.1")

    httpService.getM200(t530).enqueue(object : Callback<DataWrap> {
        override fun onResponse(call: Call<DataWrap>, response: Response<DataWrap>) {
            val encodedData = response.body()?.data ?: return
            val resM200 = String(Base64.decode(encodedData, Base64.URL_SAFE))
            val result = Gson().fromJson(resM200, ResM200::class.java)
            callMobileIdApp(result.m200Base64)
        }

        override fun onFailure(call: Call<DataWrap>, t: Throwable) {
            Log.e(TAG, "서버에서 프로필을 가져오는데 실패했습니다.\n${t.message}")
        }
    })
}

모바일 신분증 앱 호출 ​

kotlin
private fun callMobileIdApp(m200: M200) {
    val m200Json = gson.toJson(m200)
    val m200Base64 = Base64.encodeToString(m200Json.toByteArray(), Base64.URL_SAFE)
    val returnScheme = URLEncoder.encode("url scheme", "UTF-8")
    val strScheme = "$scheme verify?data_type=byte&data=$m200Base64&clientScheme=$returnScheme"

    try {
        val intent = Intent("verify", Uri.parse(strScheme)).apply {
            if (scheme == SCHEME_FW) {
                setPackage("com.samsung.android.ssiframework")
            }
        }
        idApp.launch(intent)
    } catch (e: ActivityNotFoundException) {
        Toast.makeText(this, "호출 앱 미설치", Toast.LENGTH_SHORT).show()
    }
}

모바일 신분증 앱에서 신원 제출 결과 처리 ​

kotlin
private var idApp = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
when (it.resultCode) {
404 -> {
binding.textMessage.text = "FW에 연결된 CA가 없음, 모바일신분증앱도 미설치 상태"
}

        Activity.RESULT_OK -> {
            val success = it.data?.getBooleanExtra("success", false)
            if (success == true) {
                val dataType = it.data?.getStringExtra("data_type")
                val m400Json = if (dataType == "byte") {
                    String(it.data?.getByteArrayExtra("data") ?: byteArrayOf())
                } else {
                    it.data?.getStringExtra("data") ?: ""
                }
                sendVp(m400Json)
            } else {
                val dataType = it.data?.getStringExtra("data_type")
                val m900Json = if (dataType == "byte") {
                    String(it.data?.getByteArrayExtra("data") ?: byteArrayOf())
                } else {
                    it.data?.getStringExtra("data") ?: ""
                }
                Log.d(TAG, "M900 = ${String(Base64.decode(m900Json, Base64.URL_SAFE))}")
            }
        }

        Activity.RESULT_CANCELED -> {
            binding.textMessage.text = "검증이 사용자 취소로 인해 중단되었습니다."
        }
    }
}

SP 서버로 VP 제출 또는 결과 확인 ​

kotlin
private fun sendVp(m400Json: String) {
    // TODO: SP 서버에 제출할 로직 구현
}

private fun checkVp(trxcode: String) {
    // TODO: SP 서버에서 결과 확인할 로직 구현
}