我有一个奇怪的错误,仅在少数用户iPhone上发生,以下详细信息-
该应用程序使用了一个通用框架(由我们自己开发),可以在成功登录钥匙串后保存accessToken和refreshToken。我们正在使用Locksmith来实现功能-用户注销后保存,加载数据和删除。
每当应用程序被杀死并启动或applicationWillEnterForeground
时,令牌将在服务调用的帮助下刷新并再次保存到钥匙串中。 refreshToken
过期(此令牌的有效期为一个月)时,会通知用户该应用已长时间未使用,并且已注销。
实际的问题是,对于只有很少的用户,即使他们每天使用该应用程序(即在refreshToken
的一个月完成之前),刷新机制也会失败。经过后端团队的验证后,refresh
服务始终处于启动状态,因此我怀疑Locksmith loadDataForUserAccount
但无法重现该问题。此外,可能用户不会面对问题。一切都按预期正常进行。
有人可以帮助我进一步确定原因吗?
以下是刷新accessToken
和refreshToken
的代码
**当应用程序进入前台或被杀死并启动时,从应用程序刷新令牌调用**
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
。