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

Posted

技术标签:

【中文标题】Alamofire 4 重试器和适配器无法看到更改的 accessToken【英文标题】:Alamofire 4 retrier and adaptor unable to see the changed accessToken 【发布时间】:2018-10-17 23:31:18 【问题描述】:

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

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

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

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)")
            $0(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($0) 

    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

【问题讨论】:

【参考方案1】:

我找到了问题的答案。

在适应方法中,我必须为 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

【讨论】:

【参考方案2】:

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

让我们了解一下Alamofire的基本知识

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

使用默认的NSURLSessionConfiguration

NSURLSessionConfiguration 参考 defaultSessionConfiguration() 说:

默认会话配置使用基于持久磁盘的缓存 (除非将结果下载到文件中)并存储 用户钥匙串中的凭据。它还存储 cookie(通过 默认)在与 NSURLConnection 相同的共享 cookie 存储中,并且 NSURLDownload 类。

只需使用以下代码为您的 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。它会为我工作。在您的情况下,它可能适合您。

【讨论】:

【参考方案3】:

您能否通过为网络活动创建单例类来检查。单例模式保证只实例化一个类的一个实例。像这样:-

open class NetworkHelper 

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


.... Put you network call method here



【讨论】:

已经为网络适配器执行此操作,但使用最新令牌创建此类 authTokenHandler

以上是关于Alamofire 4 重试器和适配器无法看到更改的 accessToken的主要内容,如果未能解决你的问题,请参考以下文章

修改有效载荷的 Alamofire 重试请求

如何使用 Alamofire 重试请求?

Alamofire 重试请求 - 反应方式

AuthenticationInterceptor(Alamofire5.2) 重试过程不能正常工作

Alamofire/RxSwift 如何在状态码 401 上自动刷新令牌和重试请求

Alamofire 5.0.0-rc.3 RequestInterceptor Adapt 方法没有被调用 Alamofire 虽然在响应中有任何错误时会调用重试