본문 바로가기

Android/Utility

[Android] Biometric을 사용하여 생체 인증 추가하기.

728x90

생체 인증에 대하여 찾아볼 기회가 생겨 Biometric 라이브러리를 사용하여 생체 인증을 추가해 보았다.

필자가 한 2~3년 전에 생체 인증을 적용했을 때는 Biometric이 아닌 FingerPrint를 사용했던 기억이 있는데, Biometric을 사용하도록 바뀐 지 꽤 시간이 지난 것 같다.

 

따라서, 생체 인증에 대하여 찾아보고 적용한 것에 대하여 간단하게 정리하는 글을 작성하고자 한다.


우선,

Biometric 라이브러리를 추가해주도록 한다.

 

// BioMetric
implementation 'androidx.biometric:biometric:1.1.0'

 

안드로이드 공식 페이지에서는 1.2.0-alpha04 버전이 최신 버전이지만 정식 배포된 1.1.0 버전과 큰 차이점을 보이지 않는 것으로 판단하여 정식 버전을 사용하도록 하였다.

 

간단하게 jetpack 라이브러리를 추가해주면 생체 인증을 사용할 수 있게 되므로, 추가적으로 코드를 작성해주도록 하자.

 

생체 인증을 사용하는 것에 대한 Flow는 다음과 같다.

 

  1. 생체 인증을 사용할 수 있는지 체크
  2. 생체 인증을 사용 여부
    1. 생체 인증을 사용하지 못할 때의 처리
    2. 생체 인증을 사용할 수 있으나, 생체 인증 정보가 없을 때의 처리
  3. 생체 인증 화면 설정
  4. 생체 인증 Callback 설정
  5. 생체 인증 호출

위와 같은 Flow로 실행이 되므로, 코드에 대한 설명도 위와 같은 순서대로 진행하겠다.

 

처음으로,

생체 인증을 사용할 수 있는지 체크를 해주어야 한다.

 

private fun checkUseBiometric() {
    val biometricManager = BiometricManager.from(this@MainActivity)
    when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) {

        BiometricManager.BIOMETRIC_SUCCESS -> {
            Log.d("BiometricLog", "BIOMETRIC_SUCCESS :: 사용 가능")
        }

        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
            Log.d("BiometricLog", "BIOMETRIC_ERROR_NO_HARDWARE :: 지원하지 않는 기기")
        }

        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
            Log.d("BiometricLog", "BIOMETRIC_ERROR_HW_UNAVAILABLE :: 사용할 수 없는 상태")
        }

        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
            Log.d("BiometricLog", "BIOMETRIC_ERROR_NONE_ENROLLED :: 생체 인증 정보가 없음")

            val dialogBuilder = AlertDialog.Builder(this@MainActivity)
            dialogBuilder.setTitle("지문이 등록되어있지 않습니다.")
                .setMessage("지문 등록이 필요합니다. 지문등록 설정화면으로 이동하시겠습니까?")
                .setPositiveButton("확인") { _, _ -> startSettingPage() }
                .setNegativeButton("취소") { dialog, _ -> dialog.cancel() }
            dialogBuilder.show()
        }

        else -> {
            Log.d("BiometricLog", "알 수 없는 오류")
        }
    }

    startAuthenticate()
}

 

 

When절에 들어가는 Case들은 공식 페이지에서 확인할 수 있는 값들을 사용하였다.

각 Case마다 어떤 경우에 발생하는지는 Log로 확인할 수 있도록 추가해 두었으며, 공식 페이지와 다른 부분은 when절에 들어가는 인자 값과, 생체 인증 정보가 없을 경우 두 부분이다.

 

우선, 공식 페이지에서 확인할 수 있는 부분의 When 절은 다음과 같다.

 

when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL))

 

BIOMETRIC_STRONG or DEVICE_CREDENTIAL 로 되어있는데, 필자는 BIOMETRIC_WEAK을 사용하였다.

그 이유는 예제에 나와있는 두 가지 경우, Android 10 이하에서는 지원하지 않기 때문이다.

필자는 Android 10 이하의 버전에서도 사용이 가능하도록 구현하기 위하여 공식 페이지에서와 다르게, 모든 버전에서 사용이 가능한 WEAK을 사용하도록 하였다.

 

생체 인증 정보가 없을 경우는, 위의 Flow에서 2-2에 해당하는 부분으로 공식 페이지에서는 바로 생체 인증을 등록하는 화면으로 이동하도록 하였지만, 필자는 Dialog를 띄우고 OK를 눌렀을 경우에만 넘어가도록 추가해두었다.

 

private fun startSettingPage() {
    // R 버전 이하에서는 FingerPrint를 사용하기 때문에, 다른 Setting 화면 호출 필요.
    val enrollIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
            putExtra(
                Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
                BIOMETRIC_STRONG or DEVICE_CREDENTIAL
            )
        }
    } else {
        // 설정 화면까지만 이동.
        Intent(Settings.ACTION_SECURITY_SETTINGS)
    }

    biometricResult.launch(enrollIntent)
}

 

Android 10 이하 버전에서는 BIOMETRIC_ENROLL을 사용할 수 없으므로, 설정 화면까지만 이동할 수 있도록 ACTION_SECURITY_SETTINGS를 사용하도록 하였다.

 

Flow의 2-1인 생체 인증을 사용할 수 없는 경우는, 별도로 필요에 따라 처리할 수 있겠지만 필자는 우선 아무런 동작을 하지 않고 Log만 찍도록 추가해두었다.

 

Flow의 3번째인 생체 인증 화면 설정에 대한 코드를 확인해보자.

 

private lateinit var promptInfo: BiometricPrompt.PromptInfo
private fun setPromptInfo(usePattern: Boolean): BiometricPrompt.PromptInfo {
    val promptBuilder = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Biometric Title")
        .setSubtitle("Biometric subTitle")
        .setDescription("Biometric description")

    if (usePattern) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            promptBuilder.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.DEVICE_CREDENTIAL)
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            promptBuilder.setNegativeButtonText("Biometric negativeBtnText")
        }
    } else {
        promptBuilder.setNegativeButtonText("취소")
    }

    promptInfo = promptBuilder.build()
    return promptInfo
}

 

BiometricPromt를 통해 생체 인증에 사용할 인증 종류와 생체 인증 화면에서 보여줄 값들을 설정해주게 된다.

여기서, setDescription의 경우 설정하지 않으면 Default 값으로 나오게 되는데 별도의 텍스트가 필요하지 않으면 Default 값으로 사용하는 것이 편하다.

지문, 얼굴 인증 두 가지를 사용할 수 있는 경우, 탭의 이동에 따라 별도의 텍스트를 알아서 설정해 주기 때문이다. 이 경우 수동으로 설정할 수 있을 것으로 보여 여러모로 찾아보았지만, 해당 Description을 인증 종류에 따라서 변경하는 방법을 찾지 못하였다.

 

 

usePattern의 경우, 생체 인증 대신 패턴을 사용하여 인증할 수 있도록 하는 경우를 구분 짓기 위하여 사용한 변수이다.

위 생체 인증 사용 여부를 확인할 때도 언급했던 DEVICE_CREDENTIAL 값이 이 경우를 나타낸다. 해당 값으로 설정하는 경우 생체 인증 화면에서 취소 버튼 대신 패턴으로 인증할 수 있도록 변경되게 된다.

 

Caused by: java.lang.IllegalArgumentException: Negative text must not be set if device credential authentication is allowed.

 

그에 따라, negativeButtonText를 설정하면 위와 같은 오류가 발생하게 되는데 이것을 해결하기 위하여 usePattern이라는 변수를 사용하여 분기 처리를 하였다.

 

여기서 중요한 부분은, DEVICE_CREDENTIAL은 Android 10 이하에서 사용할 수 없기 때문에 Android 10 이하에서는 자동으로 해당 값을 사용하지 않게 된다.

따라서 setNegativeButtonText는 Android 10 이하의 버전에서 반드시 설정해주어야 하기 때문에 OS 버전이 이에 해당하는 경우에는 설정하도록 조건을 추가해 주었다.

 

또한, 공식 페이지에서는 DEVICE_CREDENTIAL과 BIOMETRIC_STRONG을 사용하였는데 BIOMETRIC_STRONG 또한 Android 10 이하의 버전에서는 사용할 수 없으며, 해당 값으로 설정하는 경우 얼굴 인식은 사용할 수 없다.

따라서, 조건에 따라 패턴 인증과 얼굴 인증을 사용하고 싶은 경우, BIOMETRIC_STRONG 대신 BIOMETRIC_WEAK를 선언하여 사용하도록 한다.

 

 

Flow의 4번째인 생체 인증 결과 Callback의 경우,

공식 페이지와 동일하게 사용하므로 설명은 하지 않고 넘어가도록 하겠다.

 

private fun setBiometricPrompt(): BiometricPrompt {
    executor = ContextCompat.getMainExecutor(this)

    biometricPrompt =
        BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {

            // 인증 에러
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                super.onAuthenticationError(errorCode, errString)
                Toast.makeText(
                    this@MainActivity,
                    "errorCode: $errorCode, errString: $errString",
                    Toast.LENGTH_SHORT
                ).show()
            }

            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                super.onAuthenticationSucceeded(result)
                Toast.makeText(this@MainActivity, "생체 인증 성공", Toast.LENGTH_SHORT).show()
            }

            override fun onAuthenticationFailed() {
                super.onAuthenticationFailed()
                Toast.makeText(this@MainActivity, "생체 인증 실패", Toast.LENGTH_SHORT).show()
            }

        })

    return biometricPrompt
}

 

 

 

 

override 한 각 Callback에 따라서 처리를 해주면 된다.

 

마지막으로

생체 인증 호출 부분을 확인해보자.

 

private fun startAuthenticate() {
    promptInfo = setPromptInfo(usePattern)
    promptInfo.let {
        biometricPrompt.authenticate(it)  //인증 실행
    }
}

 

아주 간단하다.

위의 생체 인증 화면 설정이 되어 있으면, BiometricPromt 객체를 통해 authenticate를 호출하여 생체 인증 화면을 호출하면 된다.

 

여기서, 코드를 보면 알 수 있겠지만 인증 실행을 하기 전에 4번까지의 항목이 모두 완료되어야지 오류 없이 실행이 될 수 있다.

따라서, 보통 onCreate를 할 때

 

biometricPrompt = setBiometricPrompt()
promptInfo = setPromptInfo()

 

위와 같이 초기화를 한 후에 버튼을 클릭하면 인증 실행하는 코드만 실행하도록 하면 된다.

하지만 필자는 패턴의 사용 여부에 따른 케이스를 별도로 추가하였기 때문에 setPromtInfo에 대한 설정을 버튼을 클릭했을 때 실행되는 부분에 추가하여, 생체 인증을 호출할 때마다 usePattern 값에 따라 다른 설정을 하도록 구현하였다.

이처럼 2가지 경우를 고려하지 않는다면, 해당 설정 값들은 한 번만 호출되도록 구현하는 것이 바람직하다.


안드로이드 공식 페이지에서 제공하는 가이드만 보고 어느정도 구현이 가능했었는데, 버전에 따른 처리는 나와있지 않아 해당 부분을 확인하다 시간이 좀 더 걸렸던 것 같다.

Android 10 버전 이하의 경우 분기처리가 필요한데 Android 10 버전은 2020년 이후 추가적으로 제공하는 기기가 없기 때문에 크게 신경 쓸 필요는 없지 않을까 생각하지만, 해당 os의 기기를 사용하는 사람이 아예 없는 것은 아니기 때문에 필자는 분기 처리를 하였다.

 

필자는 간단하게 생체 인증을 추가하는 것에 대해서만 작성하였는데, 공식 페이지를 보면 암호화 같은 내용도 나와있다.

해당 부분에 대해서는 추후 필요하게 되면 확인 후 글을 작성해보도록 하겠다.

 

해당 게시글에 사용된 예제 코드는 Github에 올려두었다.

https://github.com/HeeGyeong/ModuleArchitecture

 

GitHub - HeeGyeong/ModuleArchitecture

Contribute to HeeGyeong/ModuleArchitecture development by creating an account on GitHub.

github.com

 

728x90