【问题标题】:Right way to refresh the token刷新令牌的正确方法
【发布时间】:2019-02-16 15:56:46
【问题描述】:

RequestManager class 中有一个函数getUser 在我的VC 中调用。

func getUser(onCompletion: @escaping (_ result: User?, error: String?) -> Void) {
    Alamofire.request(Router.getUser).responseJSON { (response) in
        // here is the work with response
    }
}

如果此请求返回403,则表示access_token 已过期。我需要刷新令牌并重复来自我的VC 的请求。

现在是问题。

如何刷新token并以正确的方式重复请求?

MyViewControllergetUser 方法中处理错误并刷新令牌不是一个好主意,因为我有很多VCsrequest methods

我需要类似的东西:VC 调用该方法并获取 User,即使令牌已过期且 refreshToken 必须在所有请求方法中。

编辑

refreshToken方法

func refreshToken(onCompletion: @escaping (_ result: Bool?) -> Void) {
    Alamofire.request(Router.refreshToken).responseJSON { (response) in
        print(response)
        if response.response?.statusCode == 200 {
            guard let data = response.data else { return onCompletion(false) }
            let token = try? JSONDecoder().decode(Token.self, from: data)
            token?.setToken()
            onCompletion(true)
        } else {
            onCompletion(false)
        }
    }
}

【问题讨论】:

  • 同时显示刷新令牌 API 调用的代码
  • @AnkitJayaswal 有必要吗?已添加

标签: ios swift alamofire


【解决方案1】:

您可以使用 Alamofire 轻松刷新令牌并重试之前的 API 调用 请求拦截器

NetworkManager.Swift:-

import Alamofire
    class NetworkManager {
        static let shared: NetworkManager = {
            return NetworkManager()
        }()
        typealias completionHandler = ((Result<Data, CustomError>) -> Void)
        var request: Alamofire.Request?
        let retryLimit = 3
        
        func request(_ url: String, method: HTTPMethod = .get, parameters: Parameters? = nil,
                     encoding: ParameterEncoding = URLEncoding.queryString, headers: HTTPHeaders? = nil,
                     interceptor: RequestInterceptor? = nil, completion: @escaping completionHandler) {
            AF.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers, interceptor: interceptor ?? self).validate().responseJSON { (response) in
                if let data = response.data {
                    completion(.success(data))
                } else {
                    completion(.failure())
                }
            }
        }
        
    }

RequestInterceptor.swift :-

    import Alamofire
extension NetworkManager: RequestInterceptor {
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var request = urlRequest
        guard let token = UserDefaultsManager.shared.getToken() else {
            completion(.success(urlRequest))
            return
        }
        let bearerToken = "Bearer \(token)"
        request.setValue(bearerToken, forHTTPHeaderField: "Authorization")
        print("\nadapted; token added to the header field is: \(bearerToken)\n")
        completion(.success(request))
    }
    
    func retry(_ request: Request, for session: Session, dueTo error: Error,
               completion: @escaping (RetryResult) -> Void) {
         guard let statusCode = request.response?.statusCode else {
    completion(.doNotRetry)
    return
}

guard request.retryCount < retryLimit else {
    completion(.doNotRetry)
    return
}
print("retry statusCode....\(statusCode)")
switch statusCode {
case 200...299:
    completion(.doNotRetry)
case 401:
    refreshToken { isSuccess in isSuccess ? completion(.retry) : completion(.doNotRetry) }
    break
default:
    completion(.retry)
} 
    }
    
    func refreshToken(completion: @escaping (_ isSuccess: Bool) -> Void) {
                let params = [
"refresh_token": Helpers.getStringValueForKey(Constants.REFRESH_TOKEN)
        ]
        AF.request(url, method: .post, parameters: params, encoding: JSONEncoding.default).responseJSON { response in
            if let data = response.data, let token = (try? JSONSerialization.jsonObject(with: data, options: [])
                as? [String: Any])?["access_token"] as? String {
                UserDefaultsManager.shared.setToken(token: token)
                print("\nRefresh token completed successfully. New token is: \(token)\n")
                completion(true)
            } else {
                completion(false)
            }
        }
    }
    
}

Alamofire v5 有一个名为 RequestInterceptor 的属性。 RequestInterceptor 有两种方法,一种是分配的 Adapt access_token 到任何网络调用标头,第二个是重试方法。 在 Retry 方法中,我们可以检查响应状态代码并调用 refresh_token 块以获取新令牌并重试以前的 API。

【讨论】:

    【解决方案2】:

    您可以创建通用的刷新类:

    protocol IRefresher {
        associatedtype RefreshTarget: IRefreshing
    
        var target: RefreshTarget? { get }
    
        func launch(repeats: Bool, timeInterval: TimeInterval)
        func invalidate()
    }
    
    class Refresher<T: IRefreshing>: IRefresher {
    
        internal weak var target: T?
        private var timer: Timer?
    
        init(target: T?) {
            self.target = target
        }
    
        public func launch(repeats: Bool, timeInterval: TimeInterval) {
            timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: repeats) { [weak self] (timer) in
                self?.target?.refresh()
            }
        }
    
        public func invalidate() {
            timer?.invalidate()
        }
    }
    

    以及刷新目标协议:

    protocol IRefreshing: class {
        func refresh()
    }
    

    定义新的类型别名:

    typealias RequestManagerRefresher = Refresher<RequestManager>
    

    现在创建刷新并存储它:

    class RequestManager {
        let refresher: RequestManagerRefresher
    
        init() {
            refresher = Refresher(target: self)
            refresher?.launch(repeats: true, timeInterval: 15*60)
        }
    }
    
    

    并展开 RequestManager:

    extension RequestManager: IRefreshing {
        func refresh() {
            updateToken()
        }
    }
    

    每 15 分钟更新一次 RequestManager 的令牌


    更新

    当然,您也可以更改更新时间。创建一个静态变量来存储您需要的更新时间。例如在 RequestManager 中:

    class RequestManager {
        static var updateInterval: TimeInterval = 0
    
        let refresher: RequestManagerRefresher
    
        init() {
            refresher = Refresher(target: self)
            refresher?.launch(repeats: true, timeInterval: updateInterval)
        }
    }
    

    所以现在您可以向令牌提供服务器询问令牌更新间隔并将此值设置为 updateInterval 静态变量:

    backendTokenUpdateIntervalRequest() { interval in
        RequestManager.updateInterval = interval
    }
    

    【讨论】:

    • 我不需要每 15 分钟更新一次我的令牌,但只有在它过期时才更新 @vboyko
    • @aaisataev 已更新,您应该向后端服务器询问过期间隔值
    • @vboyko 你能解释一下这是从哪里来的“”authenticationService.refresher = Refresher(target: self)“”。我很难弄清楚这一点。
    • @fazeelahamed 已修复。此行以 self 作为目标创建 Refresher 并将其设置为属性。
    【解决方案3】:

    为了解决这个问题,我创建了一个类,我们将从中调用每个 API,例如 BaseService.swift

    BaseService.swift :

    import Foundation
    import Alamofire
    import iComponents
    
    struct AlamofireRequestModal {
        var method: Alamofire.HTTPMethod
        var path: String
        var parameters: [String: AnyObject]?
        var encoding: ParameterEncoding
        var headers: [String: String]?
    
        init() {
            method = .get
            path = ""
            parameters = nil
            encoding = JSONEncoding() as ParameterEncoding
            headers = ["Content-Type": "application/json",
                       "X-Requested-With": "XMLHttpRequest",
                       "Cache-Control": "no-cache"]
        }
    }
    
    class BaseService: NSObject {
    
        func callWebServiceAlamofire(_ alamoReq: AlamofireRequestModal, success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void)) {
    
            // Create alamofire request
            // "alamoReq" is overridden in services, which will create a request here
            let req = Alamofire.request(alamoReq.path, method: alamoReq.method, parameters: alamoReq.parameters, encoding: alamoReq.encoding, headers: alamoReq.headers)
    
            // Call response handler method of alamofire
            req.validate(statusCode: 200..<600).responseJSON(completionHandler: { response in
                let statusCode = response.response?.statusCode
    
                switch response.result {
                case .success(let data):
    
                    if statusCode == 200 {
                        Logs.DLog(object: "\n Success: \(response)")
                        success(data as AnyObject?)
    
                    } else if statusCode == 403 {
                        // Access token expire
                        self.requestForGetNewAccessToken(alaomReq: alamoReq, success: success, failure: failure)
    
                    } else {
                        let errorDict: [String: Any] = ((data as? NSDictionary)! as? [String: Any])!
                        Logs.DLog(object: "\n \(errorDict)")
                        failure(errorTemp as NSError?)
                    }
                case .failure(let error):
                    Logs.DLog(object: "\n Failure: \(error.localizedDescription)")
                    failure(error as NSError?)
                }
            })
        }
    
    }
    
    extension BaseService {
    
        func getAccessToken() -> String {
            if let accessToken =  UserDefaults.standard.value(forKey: UserDefault.userAccessToken) as? String {
                return "Bearer " + accessToken
            } else {
                return ""
            }
        }
    
        // MARK: - API CALL
        func requestForGetNewAccessToken(alaomReq: AlamofireRequestModal, success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void) ) {
    
            UserModal().getAccessToken(success: { (responseObj) in
                if let accessToken = responseObj?.value(forKey: "accessToken") {
                    UserDefaults.standard.set(accessToken, forKey: UserDefault.userAccessToken)
                }
    
                // override existing alaomReq (updating token in header)
                var request: AlamofireRequestModal = alaomReq
                request.headers = ["Content-Type": "application/json",
                                   "X-Requested-With": "XMLHttpRequest",
                                   "Cache-Control": "no-cache",
                                   "X-Authorization": self.getAccessToken()]
    
                self.callWebServiceAlamofire(request, success: success, failure: failure)
    
            }, failure: { (_) in
                self.requestForGetNewAccessToken(alaomReq: alaomReq, success: success, failure: failure)
            })
        }
    
    }
    

    为了从这个调用中调用 API,我们需要创建一个 AlamofireRequestModal 的对象并用必要的参数覆盖它。

    例如,我创建了一个文件APIService.swift,其中我们有一个getUserProfileData 的方法。

    APIService.swift :

    import Foundation
    
    let GET_USER_PROFILE_METHOD = "user/profile"
    
    struct BaseURL {
        // Local Server
        static let urlString: String = "http://192.168.10.236: 8084/"
        // QAT Server
        // static let urlString: String = "http://192.171.286.74: 8080/"
    
        static let staging: String = BaseURL.urlString + "api/v1/"
    }
    
    class APIService: BaseService {
    
        func getUserProfile(success: @escaping ((_ responseObject: AnyObject?) -> Void), failure: @escaping ((_ error: NSError?) -> Void)) {
    
            var request: AlamofireRequestModal = AlamofireRequestModal()
            request.method = .get
            request.path = BaseURL.staging + GET_USER_PROFILE_METHOD
            request.headers = ["Content-Type": "application/json",
                               "X-Requested-With": "XMLHttpRequest",
                               "Cache-Control": "no-cache",
                               "X-Authorization": getAccessToken()]
    
            self.callWebServiceAlamofire(request, success: success, failure: failure)
        }
    
    }
    

    说明:

    在代码块中:

    else if statusCode == 403 {
        // Access token expire
        self.requestForGetNewAccessToken(alaomReq: alamoReq, success: success, failure: failure)
    }
    

    我调用 getNewAccessToken API(在你的情况下说是刷新令牌),请求(它可以是基于 APIService.swift 的任何请求)。

    当我们获得新令牌时,我将其保存为用户默认值,然后我将更新请求(我在 refresh-token API 调用中作为参数获取的请求),并将按原样传递成功和失败块。

    【讨论】:

    • 好兄弟。我想要的确切的东西。谢谢:-)
    猜你喜欢
    • 2021-10-02
    • 1970-01-01
    • 2019-04-15
    • 2021-08-31
    • 2016-12-27
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多