Alamofire 4 重试器和适配器无法看到更改的 accessToken

Alamofire 4 retrier and adaptor unable to see the changed accessToken

我正在使用 alamofire 的重试器和适应协议获取新的访问令牌。我能够获取一个新令牌,但有时当另一个线程正在调用相同的方法时,它不起作用,即使生成了新的访问令牌,请求也会失败。

我刚刚更改了示例,现在我正在使用同步请求来获取访问令牌,因为如果我知道令牌无效,我不想在 adapt 中发送额外的请求。

奇怪的是,当我打印失败请求的响应时,我看到该请求在 header 中仍然有旧令牌。我在这里错过了什么?

func isTokenValid() -> Bool {
    return Date() < self.expiryTime
}

func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
    var urlRequest = urlRequest
    urlRequest = processRequest(urlRequest: urlRequest)
    return urlRequest
}

func processRequest(urlRequest: URLRequest) -> URLRequest {
    DDLogInfo("******access token : \(self.accessToken)***************")
    DDLogInfo("***** expiry Time: \(self.expiryTime)***************")
    var urlRequest = urlRequest
    lock.lock(); defer {   DDLogInfo( "Thread UnLocked ⚡️: \(Thread.current)\r" + ": \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
        lock.unlock()
    }
    DDLogInfo( "Thread Locked ⚡️: \(Thread.current)\r" + ": \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")

    if !isTokenValid() {
        let _ = self.refreshAccessToken()
    }

    urlRequest = self.appendToken(urlRequest: urlRequest)
    DDLogInfo("here \(urlRequest)")
    return urlRequest
}

func appendToken(urlRequest: URLRequest) -> URLRequest {
    if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
        var urlRequest = urlRequest
        DDLogInfo("token appended : \(self.accessToken)")
        urlRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
    }
    return urlRequest
}

// MARK: - RequestRetrier

 func handleFailedRequest(_ completion: @escaping RequestRetryCompletion) {
    requestsToRetry.append(completion)
    if !isRefreshing {
        lock.lock()
        print( "Thread Locked⚡️: \(Thread.current)\r" + ": \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")

        let succeeded = self.refreshAccessToken()
        self.requestsToRetry.forEach {
            print("token fetched \(succeeded): \(self.accessToken)")
            [=10=](succeeded, 0.0)
        }
        self.requestsToRetry.removeAll()
        DDLogInfo( "Thread UnLocked⚡️: \(Thread.current)\r" + ": \(OperationQueue.current?.underlyingQueue?.label ?? "None")\r")
        lock.unlock()
    }
}


func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {

    if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401, request.retryCount < 3 {
        handleFailedRequest(completion)
    } else {
        completion(false, 0.0)
    }
}

func updateTokens (accessToken: String, refreshToken: String, accessTokenExpiresIn: Double) {
    self.accessToken = accessToken
    self.refreshToken = refreshToken
    let expiryDate = Date(timeIntervalSinceNow: accessTokenExpiresIn - Constants.KeychainKeys.expirationBuffer)
    AppSettings.sharedInstance.tokenExpiryTime = expiryDate
    self.expiryTime = expiryDate
    do {try keychainWrapper.save(values: ["accessToken": self.accessToken, "refreshToken": self.refreshToken])} catch {
        DDLogError("unable to save accessToken")
        }
}

// MARK: - Private - Refresh Tokens

  fileprivate func refreshAccessToken() -> Bool {
    DDLogInfo("^^^^^^^^")
    Thread.callStackSymbols.forEach {  DDLogInfo([=10=]) }

    var success = false
    guard !isRefreshing else { return success }
    let refreshRequest = URLRequestConfigurations.configRefreshProviderAgent(refreshToken: self.refreshToken)
    let result = URLSession.shared.synchronousDataTask(with: refreshRequest)

    self.isRefreshing = false
    do {
        if let data = result.0, let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] {
            if let accessToken = json["accessToken"] as? String, let refreshToken = json["refreshToken"] as? String, let time = json["accessTokenExpiresIn"] as? Double {
                updateTokens(accessToken: accessToken, refreshToken: refreshToken, accessTokenExpiresIn: time)
                success = true
            } else {
                DDLogError("unable to find tokens/expiryInterval from refresh request")
            }
        } else {
            DDLogError("unable to receive data from refresh request")
        }
    } catch {
        DDLogError("unable to parse json response from refersh token request")
    }
    return success
}

您可以通过为网络 activity 创建单例 class 来检查吗?单例模式保证只有 class 的一个实例被实例化。像这样:-

open class NetworkHelper {

class var sharedManager: NetworkHelper {
    struct Static{
        static let instance: NetworkHelper = NetworkHelper()
    }
    return Static.instance
}

.... Put you network call method here


}

不幸的是,我已经遇到了与AFNetworking.相同的问题,背后的原因非常愚蠢。经过 2-3 天的研发,我发现它是由于 coockiescaches.

而发生的

让我们了解一下基础知识 Alamofire

Alamofire 基本上是 NSURLSession 的包装器。它的管理器通过调用 `defaultSessionConfiguration()

使用默认 NSURLSessionConfiguration

NSURLSessionConfiguration defaultSessionConfiguration() 的参考说:

The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain. It also stores cookies (by default) in the same shared cookie store as the NSURLConnection and NSURLDownload classes.

只需使用以下代码禁用 API 的缓存。

禁用 URLCache

let manager: Manager = {
    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    configuration.URLCache = nil
    return Manager(configuration: configuration)
}()

或者您也可以配置请求缓存策略

let manager: Manager = {
    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    configuration.requestCachePolicy = .ReloadIgnoringLocalCacheData
    return Manager(configuration: configuration)
}()

解决方案:只需为您的 API 禁用 chaching。它会为我工作。在你的情况下可能会适合你。

我找到了问题的答案。

在 adapt 方法中,我不得不为 urlRequest 使用不同的变量,因为当使用相同的变量名时它不会修改请求。正如您在下面看到的,我将变量更改为 "mutableRequest"

func appendToken(urlRequest: URLRequest) -> URLRequest {
    var mutableRequest = urlRequest
    if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
        DDLogInfo("token appended : \(self.accessToken)")
        mutableRequest.setValue(self.accessToken, forHTTPHeaderField: Constants.KeychainKeys.accessToken)
    }
    return mutableRequest
}