안녕하세요.
염치불구하고 부탁드려요~~~
생체인증(face ID)을 시도하는 함수를 호출할 때마다 생체인증 체크하는 화면을 띄우고, 그리고 기기에 등록된 유효한 얼굴인지 체크하게 하고 싶습니다.
현재 문제는 인증이 성공된 후 최소 하루 정도는 무조건 성공으로 처리 해 버립니다. 예를 들어 "인증완료" 라는 세션 같은것을 적용 해 버리는 듯 합니다.
어떻게 해야 할까요?
호출하는 순서는 아래와 같습니다.
1. 웹뷰의 login.php 에서 앱의 call_bioMetricAuth 메소드를 호출합니다.
2. 앱의 ViewController.swift 에서 아래의 메소드가 실행됩니다.
} else if action == "call_bioMetricAuth"
, let cmd = dictionary["cmd"]
, let site_code = dictionary["site_code"]
, let domain_url = dictionary["domain_url"]
, let userid_ori = dictionary["userid_ori"]
, let userid = dictionary["userid"]
, let login_date = dictionary["login_date"]
, let return_url = dictionary["return_url"] { // biometric 20230105
print("called call_bioMetricAuth in ViewController")
bioMetricManager.bioMetricAuth(cmd_para: cmd, site_code_para: site_code, domain_url_para: domain_url, userid_ori_para: userid_ori, userid_para: userid, login_date_para: login_date, return_url_para: return_url) { resultCode, resultMsg, guideMsg, bioDictionary in
if (resultCode > 1) {
if (guideMsg != "") {
self.showToast(message: guideMsg)
}
if (resultCode == 100 ) { // resultMsg is 이동할 url
self.setCookieToWeb(cook_name: "isRegisteredBioAuth", cook_value: "T", expires_date: "15")
self.goto_url(url: resultMsg)
} else {
if (resultMsg == "exec_setToggleBtnDiableToWeb") {
self.setToggleBtnDiableToWeb()
} else if (resultMsg == "isSuccessBioinfoDelete") {
let sql = bioDictionary["sql_delete"]
self.dbManagerMember.sql_query(sql: sql!)
self.setCookieToWeb(cook_name: "isRegisteredBioAuth", cook_value: "", expires_date: "-1")
} else if (resultMsg == "bioAuthFailed") {
let cmd = bioDictionary["cmd"]
if (cmd == "bioInfoRegister") {
self.setToggleBtnDiableToWeb()
}
}
}
}
}
} // end of call_bioMetricAuth
3. 아래는 BioMetricMyManager.swift 입니다.
import Foundation
import LocalAuthentication
class BioMetricMyManager: NSObject { // NSObject
let dbManagerMember = DatabaseManagerMember.shared
var tbl_member: String = ""
var pkg_name: String?
var command: String?
var site_code: String?
var domain_url: String?
var userid_ori: String?
var userid: String?
var return_url: String = ""
var login_date: String?
var auto_login_url: String = ""; // 로그인 후 돌아갈 return_url 값도 이 변수에 저장한다.
var isValidBioDevice: Bool = false;
var errMsgBiometricAvailable: String = ""
var dictForMain: [String: String] = [:]
enum BiometricType {
case none
case touchID
case faceID
case unknown
}
enum BiometricError: LocalizedError {
case authenticationFailed
case userCancel
case userFallback
case biometryNotAvailable
case biometryNotEnrolled
case biometryLockout
case unknown
var errorDescription: String? {
switch self {
case .authenticationFailed: return "사용자의 신원을 확인하는 중 문제가 발생했습니다." // There was a problem verifying your identity."
case .userCancel: return "취소를 눌렀습니다." // You pressed cancel."
case .userFallback: return "인증을 취소하였습니다." // You pressed password."
case .biometryNotAvailable: return "하드웨어를 사용할 수 없기 때문에 사용자가 인증할 수 없습니다. 나중에 다시 시도하십시오." // Face ID/Touch ID is not available."
case .biometryNotEnrolled: return "생체 인식 또는 장치 자격 증명이 등록되지 않았기 때문에 사용자가 인증할 수 없습니다." // Face ID/Touch ID is not set up."
case .biometryLockout: return "생체인증이 잠겨있습니다." // Face ID/Touch ID is locked."
case .unknown: return "생체인증이 구성되지 않을 수 있습니다." // Face ID/Touch ID may not be configured"
}
}
}
private let context = LAContext()
private let policy: LAPolicy
private let localizedReason: String
private var error: NSError?
init(policy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics,
localizedReason: String = "다시 시도",
localizedFallbackTitle: String = "인식되지 않음") {
self.policy = policy
self.localizedReason = localizedReason
context.localizedFallbackTitle = "" // 공백을 넣어야 실패했을 때 암호입력하라는 문구가 나오지 않는다 localizedFallbackTitle
context.localizedCancelTitle = "인증을 취소합니다."
self.tbl_member = dbManagerMember.get_table_name()
}
func bioMetricInit() {
self.isValidBioDevice = true
self.errMsgBiometricAvailable = checkBiometricAvailable() // edit_20221214
}
func checkBiometricAvailable() -> String {
guard context.canEvaluatePolicy(policy, error: &error) else {
//let type = biometricType(for: context.biometryType)
//print("bio type: \(type)")
guard let error = error else {
//return completion(false, type, nil)
return "생체인증을 사용할 수 없습니다."
}
let ErrCode = biometricError(from: error)
return ErrCode.errorDescription ?? "생체인증을 사용할 수 없습니다.(1)"
}
return ""
}
func bioMetricAuth(cmd_para: String, site_code_para: String, domain_url_para: String, userid_ori_para: String, userid_para: String, login_date_para: String, return_url_para: String, onCompletion: @escaping (Int, String, String, [String: String]) -> Void) {
if (isValidBioDevice) {
print("isValidBioDevice is true")
}
var resultCode = 1
var resultMsg = ""
var guideMsg = ""
if (errMsgBiometricAvailable != "") { // 에러 발생
var isViewErrMsg = true
if (cmd_para == "bioInfoRegister") {
onCompletion(resultCode, "exec_setToggleBtnDiableToWeb", guideMsg, dictForMain)
} else if (cmd_para == "bioInfoDelete") {
if (site_code_para != "") {
var sql = "delete from " + tbl_member
sql = sql + " where site_code='" + site_code_para + "' and data_type='biometric'"
dictForMain["sql_delete"] = sql
onCompletion(4, "isSuccessBioinfoDelete", "생체인증 정보가 삭제되었습니다.", dictForMain)
}
}
if (isViewErrMsg) {
onCompletion(3, "onlyViewToast", errMsgBiometricAvailable, dictForMain)
}
} else {
print("cmd_para: " + cmd_para)
command = cmd_para;
site_code = site_code_para;
domain_url = domain_url_para;
userid_ori = userid_ori_para;
userid = userid_para;
login_date = login_date_para;
return_url = return_url_para;
if (cmd_para == "bioInfoRegister") {
if (site_code_para == "") {
guideMsg = "먼저 로그인을 하신 후 등록해 주십시오.";
onCompletion(2, "exec_setToggleBtnDiableToWeb", guideMsg, dictForMain)
} else {
self.evaluate { [weak self] (success, error) in
if success {
self!.bioInfoRegister()
guideMsg = "생체인증 등록이 완료되었습니다."
onCompletion(100, self!.return_url, guideMsg, self?.dictForMain ?? [:])
} else {
guideMsg = error?.localizedDescription ?? "Face ID/Touch ID may not be configured"
self?.dictForMain["cmd"] = cmd_para
onCompletion(3, "bioAuthFailed", guideMsg, self?.dictForMain ?? [:])
}
}
}
} else if (cmd_para == "bioInfoDelete") {
if (site_code_para == "") {
onCompletion(3, "onlyViewToast", "사이트 정보가 없습니다.", dictForMain)
} else {
var sql = "delete from " + tbl_member
sql = sql + " where site_code='" + site_code! + "' and data_type='biometric'"
dictForMain["sql_delete"] = sql
onCompletion(4, "isSuccessBioinfoDelete", "생체인증 정보가 삭제되었습니다.", dictForMain)
}
} else { // loginFromBioInfo
var isGotoNext = true
let isChangedBioAuthInfo = false // isChangedBiometric(); // 인증정보가 바뀌었는지에 대한 flag => true: 바뀌었음
if (isChangedBioAuthInfo) {
}
if (isGotoNext) {
let is_exist = bioInfoCheckFromTable();
if (is_exist) {
// 로그인 시도 시켜라
self.evaluate { [weak self] (success, error) in
print("start self.evaluate")
if success {
guideMsg = "생체인증이 완료되었습니다."
onCompletion(100, self!.auto_login_url, guideMsg, self?.dictForMain ?? [:])
print(guideMsg)
} else {
guideMsg = error?.localizedDescription ?? "Face ID/Touch ID may not be configured"
onCompletion(3, "onlyViewToast", guideMsg, self?.dictForMain ?? [:])
}
}
} else {
onCompletion(3, "onlyViewToast", "생체인증을 먼저 등록해 주십시오.", dictForMain)
}
}
}
}
}
func bioInfoRegister() {
var sql = "delete from " + tbl_member
sql = sql + " where site_code='" + site_code! + "' and data_type='biometric'"
dbManagerMember.sql_query(sql: sql)
dbManagerMember.insertData(data_type: "biometric", site_code: site_code!, domain_url: domain_url!, userid_ori: userid_ori!, userid: userid!, userpwd_ori: "", usertype: "", login_date: login_date!)
}
func bioInfoCheckFromTable() -> Bool {
var is_exist = false
let arr_login: [MyModel] = dbManagerMember.get_from_member_table_login_info(command: "biometric", site_code_para: site_code!)
if arr_login.count > 0 {
let idx = arr_login[0].idx
let data_type = arr_login[0].data_type
let site_code = arr_login[0].site_code;
let domain_url = arr_login[0].domain_url;
let userid_ori = arr_login[0].userid_ori;
let userid = arr_login[0].userid;
let userpwd_ori = arr_login[0].userpwd_ori;
let usertype = arr_login[0].usertype;
let login_date = arr_login[0].login_date;
var url_tmp2 = domain_url;
url_tmp2 = url_tmp2 + "/member/login_auto_for_app.php";
url_tmp2 = url_tmp2 + "?site_code=" + site_code;
url_tmp2 = url_tmp2 + "&userid=" + userid;
url_tmp2 = url_tmp2 + "&userpwd_ori=" + userpwd_ori;
url_tmp2 = url_tmp2 + "&usertype=" + usertype;
url_tmp2 = url_tmp2 + "&login_date=" + (login_date.urlEncoded ?? "");
url_tmp2 = url_tmp2 + "&login_type=bioAuthLater";
url_tmp2 = url_tmp2 + "&return_url=" + (return_url.urlEncoded ?? "");
auto_login_url = url_tmp2;
is_exist = true;
}
return is_exist
}
func canEvaluate(completion: (Bool, BiometricType, BiometricError?) -> Void) {
guard context.canEvaluatePolicy(policy, error: &error) else {
let type = biometricType(for: context.biometryType)
guard let error = error else {
return completion(false, type, nil)
}
return completion(false, type, biometricError(from: error))
}
completion(true, biometricType(for: context.biometryType), nil)
}
func evaluate(completion: @escaping (Bool, BiometricError?) -> Void) {
print("start evaluate") // will_delete
context.evaluatePolicy(policy, localizedReason: localizedReason) { [weak self] success, error in
print("evaluate 1") // will_delete
DispatchQueue.main.async {
if success {
print("evaluate 2 success") // will_delete
completion(true, nil)
} else {
print("evaluate 3 failed") // will_delete
guard let error = error else { return completion(false, nil) }
completion(false, self?.biometricError(from: error as NSError))
}
}
}
}
private func biometricType(for type: LABiometryType) -> BiometricType {
switch type {
case .none:
return .none
case .touchID:
return .touchID
case .faceID:
return .faceID
@unknown default:
return .unknown
}
}
private func biometricError(from nsError: NSError) -> BiometricError {
let error: BiometricError
switch nsError {
case LAError.authenticationFailed:
error = .authenticationFailed
case LAError.userCancel:
error = .userCancel
case LAError.userFallback:
error = .userFallback
case LAError.biometryNotAvailable:
error = .biometryNotAvailable
case LAError.biometryNotEnrolled:
error = .biometryNotEnrolled
case LAError.biometryLockout:
error = .biometryLockout
default:
error = .unknown
}
return error
}
}