Swift - Locksmith loadDataForUserAccount 有时会失败?

Swift - Locksmith loadDataForUserAccount fails sometimes?

我有一个奇怪的错误,它只发生在少数用户的 iPhone 上,详情如下 -

应用使用通用框架(自研)保存keychain登录成功后的accessToken和refreshToken。我们正在使用 Locksmith 来实现功能 - 在用户注销时保存、加载数据和删除。

每次应用程序被终止并启动或 applicationWillEnterForeground 时,令牌都会在服务调用的帮助下刷新并再次保存到钥匙串中。当refreshToken过期时(此令牌有效期为一个月),用户会收到通知,该应用程序已长时间未使用并已注销。

实际问题在于,只有少数用户即使每天使用该应用程序(即在 refreshToken 完成一个月之前)刷新机制也会失败。与后端团队核实后,refresh 服务始终可用,所以我怀疑锁匠 loadDataForUserAccount 但无法重现该问题。另外,可能用户不会遇到这个问题。一切正常,如预期。

谁能帮助我进一步了解如何确定原因?

下面是刷新accessTokenrefreshToken

的代码

** 当应用程序进入前台或终止并启动时从应用程序调用刷新令牌**

if let mySession = ServiceLayer.sharedInstance.session {

            mySession.refresh {  result in

                switch result {
                case .failure(.authenticationFailure):

                    if isBackgroundFetch {
                        print("⚠️ Session refresh failed, user is now logged out.")
                        self.myService.logoutCurrentUser()

                        // Logout Current user
                        mySession.invalidate()

                        self.showLoginUI()
                    }
                    else {
                        // user accessToken is invalid but provide access to QR
                        // on the home screen. disable all other actions except logout button

                        self.showHomeScreen()
                    }

                default:
                    mySession.getAccessToken { result in
                        switch result {

                        case let .success(value):
                            print("Access Token from App Delegate \(value)")
                            myAccessToken = value


                        case let .failure(error):
                            print("❌ Failed to fetch AccessToken: \(error)")                            

                        }
                    }
                }
            }
        }

来自实现刷新机制的框架

public func refresh(_ completion: @escaping (MyResult<String, MyError>) -> (Void)) {

        guard isValid else {
            completion(.failure(.invalidSession))
            return
        }

        getRefreshToken { result in

            switch result {
            case let .success(refreshToken):

                // Get new tokens.
                ServiceClient.requestJSON(ServiceRequest.refreshToken(refreshToken: refreshToken)) { result in

                    switch result {
                    case let .success(dictionary):
                        var newAccessToken: String?
                        var newRefreshToken: String?

                        for (key, value) in dictionary {
                            if key as! String == "access_token" {
                                newAccessToken = value as? String
                            }
                            if key as! String == "refresh_token" {
                                newRefreshToken = value as? String
                            }
                        }

                        guard newAccessToken != nil && newRefreshToken != nil else {
                            completion(.failure(.general))
                            return
                        }

                        print("Renewed session tokens.")

                        do {
                            try Locksmith.updateData(data: [MySession.accessTokenKeychainKey: newAccessToken!, MySession.refreshTokenKeychainKey: newRefreshToken!],
                                                     forUserAccount: MySession.myKeychainAccount)
                        }
                        catch {
                            completion(.failure(.general))
                        }

                        completion(.success(newAccessToken!))

                    case let .failure(error):
                        if error == MyError.authenticationFailure {
                            print(“Session refresh failed due to authentication error; invalidating session.")
                            self.invalidate()
                        }

                        completion(.failure(error))
                    }

                }

            case let .failure(error):
                completion(.failure(error))
            }
        }
    }

应用程序可能在设备锁定时在后台启动(用于应用程序刷新或您配置的其他后台模式)。受保护的数据(包括钥匙串)此时不一定可用。您可以检查 UIApplication.isProtectedDataAvailable 以检查它是否可用,并且您可以将项目的保护降低到 kSecAttrAccessibleAfterFirstUnlock 以便更可靠地进行后台访问(尽管不是 100% 承诺,即使在该模式下)。锁匠称之为 AfterFirstUnlock.