관리 메뉴

나만을 위한 블로그

[Android] SMS 인증번호 자동입력 구현하는 법 본문

Android

[Android] SMS 인증번호 자동입력 구현하는 법

참깨빵위에참깨빵_ 2023. 11. 26. 18:12
728x90
반응형

※ 이 글에선 실제 서버를 통하지 않고 에뮬레이터를 써서 인증번호를 전송한다

※ 인증번호 입력 화면 코드는 생략한다

 

흔히 앱을 사용하다 보면 SMS로 인증번호가 전달되고, 난 그 인증번호를 앱에 입력해야 하는 경우가 있다.

이 때 유저가 SMS를 확인하지 않아도 자동으로 입력되게 하려면 아래와 같이 구현하면 된다. 먼저 앱 gradle에 라이브러리를 하나 추가한다.

 

implementation "com.google.android.gms:play-services-auth-api-phone:18.0.1"

 

그리고 BroadcaseReceiver를 상속하는 아래 클래스를 구현한다.

 

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.google.android.gms.auth.api.phone.SmsRetriever
import com.google.android.gms.common.api.CommonStatusCodes
import com.google.android.gms.common.api.Status

class AuthOtpReceiver : BroadcastReceiver() {

    companion object {
        private const val PATTERN = "^<#>.*\\[Sample\\].+\\[(\\d{6})\\].+\$"
    }

    private var otpReceiver: OtpReceiveListener? = null

    override fun onReceive(context: Context, intent: Intent) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            intent.extras?.let { bundle ->
                val status = bundle.get(SmsRetriever.EXTRA_STATUS) as Status
                when (status.statusCode) {
                    CommonStatusCodes.SUCCESS -> {
                        val otpSms = bundle.getString(SmsRetriever.EXTRA_SMS_MESSAGE, "")
                        if (otpReceiver != null && otpSms.isNotEmpty()) {
                            val otp = PATTERN.toRegex().find(otpSms)?.destructured?.component1()
                            if (!otp.isNullOrEmpty()) {
                                otpReceiver!!.onOtpReceived(otp)
                            }
                        }
                    }
                }
            }
        }
    }

    fun setOtpListener(receiver: OtpReceiveListener) {
        this.otpReceiver = receiver
    }

    fun doFilter() = IntentFilter().apply {
        addAction(SmsRetriever.SMS_RETRIEVED_ACTION)
    }

    interface OtpReceiveListener {
        fun onOtpReceived(otp: String)
    }

}

 

에물레이터에서 인증번호를 보낼 때 정규식대로 보내면, 상태값에 따라 인증번호(otp)를 처리하는 코드다.

지금은 성공했을 경우의 처리만 존재하며 그 외의 상태인 경우 어떻게 처리할지는 추가 작성이 필요하다.

그리고 아래 클래스를 작성한다. 에뮬레이터에서 인증문자를 보내려면 맨 마지막에 기기의 해시값을 입력해야 하는데, 이 해시값이 없으면 SMS 인증번호를 자동으로 읽어올 수가 없는데, 이 해시값을 생성해서 로그로 출력하는 클래스가 필요하다.

 

class AppSignatureHelper(context: Context) : ContextWrapper(context) {
    val TAG = AppSignatureHelper::class.java.simpleName
    private val HASH_TYPE = "SHA-256"
    val NUM_HASHED_BYTES = 9
    val NUM_BASE64_CHAR = 11

    init {
        getAppSignatures()
    }

    /**
     * Get all the app signatures for the current package
     * @return
     */
    fun getAppSignatures(): ArrayList<String>? {
        val appCodes = ArrayList<String>()
        try {
            // Get all package signatures for the current package
            val packageName = packageName
            val packageManager = packageManager
            val signatures: Array<Signature> = packageManager.getPackageInfo(
                packageName,
                PackageManager.GET_SIGNATURES
            ).signatures

            // For each signature create a compatible hash
            for (signature in signatures) {
                val hash = hash(packageName, signature.toCharsString())
                if (hash != null) {
                    appCodes.add(String.format("%s", hash))
                }
                Log.e(TAG, "Hash $hash")
            }
        } catch (e: PackageManager.NameNotFoundException) {
            Log.e(TAG, "Unable to find package to obtain hash.", e)
        }
        return appCodes
    }

    private fun hash(packageName: String, signature: String): String? {
        val appInfo = "$packageName $signature"
        try {
            val messageDigest = MessageDigest.getInstance(HASH_TYPE)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8))
            }
            var hashSignature = messageDigest.digest()

            // truncated into NUM_HASHED_BYTES
            hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES)
            // encode into Base64
            var base64Hash: String =
                Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP)
            base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR)
            Log.e(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash))
            return base64Hash
        } catch (e: NoSuchAlgorithmException) {
            Log.e(TAG, "hash:NoSuchAlgorithm", e)
        }
        return null
    }
}

 

그리고 앱 어딘가에 이 클래스를 통해 해시값을 확인하는 코드를 작성한다.

 

val helper = AppSignatureHelper(this)
val hash = helper.getAppSignatures()?.get(0)

 

이후 앱을 실행하면 "Hash ~~~~" 형태로 "Hash" 뒤에 해시값이 출력된다. 이 해시값을 복사해둔다.

그리고 에뮬레이터로 앱을 실행하면 아래와 비슷한 화면이 표시될 것이다.

 

 

옵션 버튼을 누르면 아래 화면이 표시된다.

 

 

정규식에 맞게 인증번호를 작성하고 아까 복사했던 해시값도 뒤에 붙여넣은 다음, 하단의 Send Message를 누르면 에뮬레이터로 SMS가 전송된다.

그러면 아래와 같이 자동으로 editText에 인증번호가 입력된다.

 

 

추가해야 하는 예외처리가 많지만 이 코드를 바탕으로 인증번호 자동입력 기능을 구현해볼 수 있다.

 

참고한 사이트)

 

https://techblog.woowahan.com/2618/

 

구글플레이 권한 정책 지키기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요. 우아한형제들 배민앱개발팀에서 배달의민족 안드로이드 앱을 개발을 하고 있는 나동호입니다. 구글플레이에서 보안 및 개인 정보 보호 정책을 강화하면서 민감한 권한

techblog.woowahan.com

 

 

https://stackoverflow.com/a/54052163

 

Android - SMS Retriever API - Computing app's hash string problem

I am really new in Android and I am trying to implement the SMS Retriever API for using OTP in my app. I am following this guide: https://developers.google.com/identity/sms-retriever/verify#

stackoverflow.com

 

반응형
Comments