[iOS] 생체인식 인증 시에 생체 데이터가 변경 되었는지 확인하기

생체인식 인증 시에 생체 데이터가 변경 되었는지 확인하는 방법에 대하여 다루어봅니다.
2022.05.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

안녕하세요~ 클래스메소드 CX사업본부 모바일사업부의 정하은입니다?

지난 번 글에 이어서 이번 글도 생체인식 인증에 관한 이야기를 이어서 하고자 하는데요.

지난 번 글에서도 언급했다시피 생체인식 인증 기능에는 간과할 수 있는 이슈가 있습니다. 이번 글에서는 그것이 어떤 이슈인지, 이 이슈를 해결할 수 있는 방법인 생체 데이터 변경 유무를 체크하는 방법에 대해 이야기 해보려합니다.

이런 상황은 위험해!

만약 여러분의 친구가 여러분의 핸드폰을 보고 싶다고 하여 구경하고 있는 상황이라고 가정해봅시다.

핸드폰을 보고 있던 친구가 생체인식 인증을 사용하는 앱에 있는 기능을 사용하려고 하는데, 본인의 생체 데이터가 등록이 되어있는 게 아니다보니 사용이 불가능하죠.

그런데, 그 친구가 갑자기 나쁜 마음을 품고 기존에 등록되어 있던 생체 데이터를 지워버리고 자신의 얼굴로 새로 등록해버렸습니다. (사실 보통의 경우라면 Passcode를 기본으로 설정해두고 있기 때문에 거의 일어나지 않을 상황이긴합니다? )

그러고나서 그 친구가 다시 앱으로 돌아가 생체인식 인증을 시도하면 인증이 통과해버리게 되는 상황이 발생할 가능성이 존재하는 것이죠. iOS 내에서 알아서 이걸 방지해준다면 좋겠지만 안타깝게도 그렇지 않기 때문에 이 상황을 대비하기 위한 대안이 필요합니다.

생체 데이터가 변경 되었는지 확인하기

딱 위의 상황에 활용할 수 있는 대안책이 생체인식 인증을 시도했을 때 생체 데이터가 변경 되었는지 확인하는 처리를 넣어주는 건데요. 여기서 canEvaluatePolicy(_: error:) -> BoolevaluatedPolicyDomainState 라는 새로운 메소드와 프로퍼티가 등장합니다.

위 두 개가 어떤 역할을 하는 먼저 알아보도록 할게요.

canEvaluatePolicy(_: error:) -> Bool

canEvaluatePolicy(_: error:) -> Bool 은 지정된 정책 (policy) 에 관한 인증이 가능한 상태인지 확인하는 메소드입니다. 인증 데이터가 등록되어 유효한 상태일 경우 true를 반환합니다.

이 메소드의 괄호 안 첫 번째 공간에는 이전 블로그에서 사용했던 evaluatePolicy(_: localizedReason: reply:) 와 동일하게 두 가지 정수를 지정할 수 있습니다.

  • deviceOwnerAuthenticationWithBiometrics : 생체인식 인증 데이터가 등록되어 있는 상태인지 확인

  • deviceOwnerAuthentication : 생체인식, 암호 인증 데이터가 등록되어 있는 상태인지 확인

evaluatedPolicyDomainState

evaluatedPolicyDomainState 는 생체인증에 대한 상태를 보존하는 프로퍼티입니다.

canEvaluatePolicy(_: error:) -> Bool 나 evaluatePolicy(_: localizedReason: reply:) 메소드가 생체인증 정책에 대하여 성공한 경우 또는 생체인증이 정상적으로 실행된 경우에 상태값을 반환합니다. (그 외의 경우에는 nil을 반환)

그렇기 때문에 evaluatedPolicyDomainState 값을 받아오기 위해서는 반드시 canEvaluatePolicy(_: error:) -> BoolevaluatePolicy(_: localizedReason: reply:) 먼저 실행 되어야합니다.

사용 방법

그래서 본론인 생체 정보가 바뀌었는지 어떻게 확인하는지 보여드릴게요.

@Published var hasChanges: Bool = false

let context = LAContext()
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)

let domainState = context.evaluatedPolicyDomainState
if domainState != UserDefaults.standard.data(forKey: "domainState") 
  && UserDefaults.standard.data(forKey: "domainState") != nil {
       hasChanges = true
       self.domainState = domainState
       UserDefaults.standard.set(self.domainState, forKey: "domainState")
}

이번에도 매우 짤막한 코드로 구현이 가능합니다. 단말기에 상태값을 미리 저장하여 현재 상태값과 비교하여 생체 정보가 변경 되었는지 확인할 수 있습니다.

활용 예시

위에 사용 예를 적었지만 실제로 앱에 넣고 어떻게 움직이는지 확인해봐야겠죠?

그래서 아래와 같이 버튼을 눌렀을 때, 생체 정보가 이전과 동일하면 생체인식 인증을 시도하도록 하고, 생체 정보가 변경 되었다면 알림창을 띄우도록 하는 처리를 한 번 구현해보았습니다!

실제 앱으로 구현한다면 암호가 변경된 경우에는 앱에 별개로 설정해 둔 비밀번호 등으로 인증을 대체하는 등으로 활용할 수 있을 것 같네요.

(제 초상권 보호를 위해 얼굴 인식하는 장면은 삭제했습니다.)

코드

ViewModel

import Foundation
import LocalAuthentication

class ViewModel: ObservableObject {
    
    @Published
    var loggedIn: Bool = false
    @Published
    var hasChanges: Bool = false
    
    private var domainState: Data?
    
    func auth() {
        let context = LAContext()
        context.evaluatePolicy(.deviceOwnerAuthentication,
                               localizedReason: "인증이 필요합니다") {
            [weak self] (res, err) in
            DispatchQueue.main.async {
                self?.loggedIn = res
            }
        }
    }
    
    func check() {
        let context = LAContext()
        context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
        checkDomainState(context.evaluatedPolicyDomainState)
    }
    
    private func checkDomainState(_ domainState: Data?) {
        if let domainState = domainState {
            if domainState != UserDefaults.standard.data(forKey: "domainState")
                && UserDefaults.standard.data(forKey: "domainState") != nil {
                hasChanges = true
                self.domainState = domainState
                UserDefaults.standard.set(self.domainState, forKey: "domainState")
            } else if UserDefaults.standard.data(forKey: "domainState") == nil {
                self.domainState = domainState
                UserDefaults.standard.set(self.domainState = domainState, forKey: "domainState")
                hasChanges = false
            } else {
                hasChanges = false
            }
        }
    }
}

if문에서 저장된 상태값이 nil인 경우를 조건으로 넣고 있는데, 앱을 설치 후 처음 생체인식 인증을 사용할 때는 생체 정보가 비어있기 때문에 이 때는 생체 정보가 변경된 것으로 처리하지 않도록 구현했습니다.

ContentView

import SwiftUI

struct ContentView: View {
    
    @EnvironmentObject
    var viewModel: ViewModel
    
    var body: some View {
        VStack {
            Spacer()
            Text("Biometric Auth")
                .font(.title)
            Spacer()
                .frame(height: 20)
            Button(action: {
                viewModel.check()
                if !viewModel.hasChanges {
                    self.viewModel.auth()
                }
            }) {
                Text("인증하기")
            }
            // 생체정보가 변경된 경우 알림창을 표시
            .alert(isPresented: $viewModel.hasChanges) {
                Alert(title: Text("생체 정보가 변경되었습니다"), message: nil,
                dismissButton: .default(Text("확인")))
            }
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(ViewModel())
    }
}

마무리하며

이렇게 두 편으로 이어진 블로그로 생체인식 인증에 대하여 알아보았습니다.

시중에 나와있는 앱에서 생체인식 인증을 사용하는 앱이 생각보다 많지는 않다보니 evaluatedPolicyDomainState 에 대한 공식 문서 외의 정보도 적은 편이었는데요. 혹시라도 생체인식 인증에 대해 추가 처리를 할 필요가 있으신 분들께 도움이 되었으면 좋겠습니다?

참고