사비성(sabisung)의 컴퓨터

40개의 항목
 

생체인식 사용하기

왜?

요즘 같이 다수의 사이트에 로그인을 할려고 하면 각각 사이트의 아이디와 비밀번호를 기억해야 한다.
해킹 방지를 위해서 각각 사이트마다 아이디와 비밀번호를 다르게 지정하는 경우도 있고,
시간이 지남에 따라서 자주 사용하지 않는 사이트인 경우에는 아이디와 비밀번호를 잊어먹기가 빈번하다.
(얼마나 많은 인증 문자를 받았던가~)
심지어는 사이트 이용을 하지 않는 경우도 있다.
다른 대체 사이트를 이용하는 유저도 많다…

이러한 불편함과 해킹으로 인한 정보 유출 사고를 방지하고자 사용자 개별의 고유한 생체 정보를 사용하여 인증을 하여 사용자 정보 보호가 필요한 상태다.

iPhone의 생체 정보

iPhone에서 사용이 가능한 생체 정보는 지문과 안면 인식 두 가지가 존재한다.

Xcode 설정

Info.plist 파일에 아래 내용을 추가해야 한다.

<key>NSFaceIDUsageDescription</key>
<string>안면인식 로그인을 위해 필요합니다.</string>

BiometricAuth.swift

//
//  BiometricAuth.swift
//  BiometricAuth
//
//  Created by sabisung on 2021/07/14.
//

import Foundation
import LocalAuthentication

/// 생체 인증 유형
enum BiometricType {
  case none     // 없음
  case touchID  // 지문
  case faceID   // 안면
}

@available(iOS 11.0, *)
class BiometricAuth {
    private let localizedReason4TouchID = "Touch ID를 활성화하려면 사용자 암호가 필요합니다."
    private let localizedReason4FaceID = "Face ID를 활성화하려면 사용자 암호가 필요합니다."
    private let passcodeInterval: TimeInterval = 0.3
    
    private var biometricPolicy: LAPolicy
    private let completionHandler: (Bool, Error?) -> Void
    private var isPasscode: Bool = true
    
    private var laContext = LAContext()
    
    /// 생성자
    /// - Parameters:
    ///   - biometricPolicy: 생체인증 정책
    ///   - isPasscode: 생체인증 실패시 passcode 사용여부
    ///   - completionHandler: 핸들러
    init(biometricPolicy: LAPolicy, isPasscode: Bool, completionHandler: @escaping (Bool, Error?) -> Void) {
        self.biometricPolicy = biometricPolicy
        self.isPasscode = isPasscode
        self.completionHandler = completionHandler
    }
    
    /// 생성자
    /// - Parameters:
    ///   - isPasscode: 생체인증 실패시 passcode 사용여부
    ///   - completionHandler: 핸들러
    convenience init(isPasscode: Bool, completionHandler: @escaping (Bool, Error?) -> Void) {
        self.init(biometricPolicy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, isPasscode: isPasscode, completionHandler: completionHandler)
    }
    
    /// 생성자
    /// - Parameter completionHandler: 핸들러
    convenience init(_ completionHandler: @escaping (Bool, Error?) -> Void) {
        self.init(biometricPolicy: LAPolicy.deviceOwnerAuthenticationWithBiometrics, isPasscode: true, completionHandler: completionHandler)
    }
    
    /// 소멸자
    deinit {
        self.laContext.invalidate()
    }
    
    /// 생체인증 가능 여부
    /// - Returns: 가능여부
    func canEvaluatePolicy() -> Bool {
        return laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
    }
    
    /// 기기가 지원하는 생체인증 유형
    /// - Returns: 생체인증 유형
    func biometricType() -> BiometricType {
        if canEvaluatePolicy() {
            switch laContext.biometryType {
            case .none:
                return .none
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            default:
                return .none
            }
        }
        return .none
    }
    
    /// 생체인증 진행
    func authenticateBiometric() {
        guard canEvaluatePolicy() else {
            completionHandler(false, NSError(domain: "", code: LAError.biometryNotAvailable.rawValue, userInfo: [NSLocalizedDescriptionKey: "biometry not available"]))
            return
        }
        let bioType = biometricType()
        guard bioType != .none else {
            completionHandler(false, NSError(domain: "", code: LAError.biometryNotAvailable.rawValue, userInfo: [NSLocalizedDescriptionKey: "biometry not available"]))
            return
        }
        
        if !isPasscode {
            laContext.localizedFallbackTitle = "" // 생체 인증 실패시 passcode 입력 불가 처리
        }
        let localizedReason = bioType == .touchID ? localizedReason4TouchID : (bioType == .faceID ? localizedReason4FaceID : " ")
        laContext.evaluatePolicy(self.biometricPolicy, localizedReason: localizedReason) { (success, error) in
            if success {
                self.completionHandler(true, nil)
            } else {
                switch error {
                case LAError.userFallback?: // passcode 입력
                    print("[BIO] userFallback...")
                    self.laContext.invalidate()
                    DispatchQueue.main.asyncAfter(deadline: .now() + self.passcodeInterval) {
                        self.laContext = LAContext()
                        self.biometricPolicy = .deviceOwnerAuthentication
                        self.authenticateBiometric()
                    }
                default:
                    self.completionHandler(false, error)
                }
            }
        }
    }
}

ViewController.swift

//
//  ViewController.swift
//  BiometricAuth
//
//  Created by sabisung on 2021/07/14.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func biometricAuthWithPasscodeTapped(_ sender: UIButton) {
        if #available(iOS 11.0, *) {
            // 생체인증 실패시 passcode 사용
            BiometricAuth { (success, error) in
                print("[BIO] success: \(success)")
                print("[BIO] error: \(String(describing: error))")
            }.authenticateBiometric()
        }
    }
    
    @IBAction func biometricAuthTapped(_ sender: UIButton) {
        if #available(iOS 11.0, *) {
            // 생체인증 실패시 passcode 미사용
            BiometricAuth(isPasscode: false) { success, error in
                print("[BIO] success: \(success)")
                print("[BIO] error: \(String(describing: error))")
            }.authenticateBiometric()
        }
    }
}

실행 샘플 이미지

이미지 #1
이미지 #2
이미지 #3
이미지 #4
이미지 #5

샘플 소스 다운로드

Sample Source Download (BiometricAuth.zip)