我有一个奇怪的错误,仅在少数用户iPhone上发生,以下详细信息-

该应用程序使用了一个通用框架(由我们自己开发),可以在成功登录钥匙串后保存accessToken和refreshToken。我们正在使用Locksmith来实现功能-用户注销后保存,加载数据和删除。

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

实际的问题是,对于只有很少的用户,即使他们每天使用该应用程序(即在refreshToken的一个月完成之前),刷新机制也会失败。经过后端团队的验证后,refresh服务始终处于启动状态,因此我怀疑Locksmith 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

10-08 08:05