使用Swift p2 / OAuth2并行刷新OAuth2访问令牌的请求

时间:2016-01-11 14:48:08

标签: ios swift oauth-2.0

我正在使用https://github.com/p2/OAuth2通过OAuth2连接到我应用的后端,效果非常好。

我遇到的问题是当访问令牌过期并且多个请求同时发生时,其中一些请求失败。

可以从应用的不同部分触发并行请求。例如,当应用程序启动时,当前位置将被发送到服务器并下载事件列表。

确保在第一次刷新令牌请求仍在运行时没有第二次刷新令牌请求的最佳方法是什么?

2 个答案:

答案 0 :(得分:6)

找到令牌寿命并设置缓冲区,例如1-2分钟,然后 如果令牌需要刷新,请在令牌刷新时保存所有请求。之后,执行所有保存的所有请求。您可以使用DispatchQueue和DispatchWorkItem做到这一点。

下面的示例代码。

final class Network: NSObject {

    static let shared = Network()

    private enum Constants {
        static let tokenRefreshDiffrenceMinute = 1
        static let tokenExpireDateKey = "tokenExpireDate"
    }

    private(set) var tokenExpireDate: Date! {
        didSet {
            UserDefaults.standard.set(tokenExpireDate, forKey: Constants.tokenExpireDateKey)
        }
    }

    public override init() {
        super.init()

        if let date = UserDefaults.standard.object(forKey: Constants.tokenExpireDateKey) as? Date {
            tokenExpireDate = date
            print("Token found!")
        }
        else {
            print("Token not found!")
            isTokenRefreshing = true
            getToken {
                self.isTokenRefreshing = false
                self.executeAllSavedRequests()
            }
        }
    }


    private var isTokenRefreshing = false
    private var savedRequests: [DispatchWorkItem] = []

    func request(url: String, params: [String: Any], result: @escaping (String?, Error?) -> Void) {

        // isTokenRefreshing save all requests
        if isTokenRefreshing {

            saveRequest {
                self.request(url: url, params: params, result: result)
            }

            return
        }

        // if token expire
        if getMinutesFrom2Dates(Date(), tokenExpireDate) < Constants.tokenRefreshDiffrenceMinute {

            // open this flag for we need wait refresh token
            isTokenRefreshing = true

            // save current request too
            saveRequest {
                self.request(url: url, params: params, result: result)
            }


            // get token
            self.getToken { [unowned self] in
                self.isTokenRefreshing = false
                self.executeAllSavedRequests()
            }
        } else {
            //Alamofire.request ...

            DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
                DispatchQueue.main.async(execute: {
                    result(url, nil)
                })
            }
        }
    }

    private func saveRequest(_ block: @escaping () -> Void) {
        // Save request to DispatchWorkItem array
        savedRequests.append( DispatchWorkItem {
            block()
        })
    }

    private func executeAllSavedRequests() {
        savedRequests.forEach({ DispatchQueue.global().async(execute: $0) })
        savedRequests.removeAll()
    }

    private func getToken(completion: @escaping () -> Void) {
        print("Token needs a be refresh")
        // Goto server and update token
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [unowned self] in
            DispatchQueue.main.async(execute: { [unowned self] in
                self.tokenExpireDate = Date().addingTimeInterval(120)
                print("Token refreshed!")
                completion()
            })
        }
    }

    private func getMinutesFrom2Dates(_ date1: Date, _ date2: Date) -> Int {
        return Calendar.current.dateComponents([.minute], from: date1, to: date2).minute!
    }
}

答案 1 :(得分:0)

您应该将失败请求排入队列401。由于您没有提供用于刷新令牌的代码,因此我向您解释了应该如何做并让您自己实现它:

  • 为身份验证请求创建共享的重试器
  • 它应该具有一个重试请求的共享队列。数组[Request]例如
  • 当由于 accessToken 而导致请求失败时,该请求将附加到 requestQueue 并等待刷新访问令牌。
  • 因此,请观察队列,如果队列为空,现在有一个新项,则表示令牌最近过期,应该执行刷新逻辑
  • 同时另一个请求由于401错误而失败。
  • 将其添加到 requestQueue
  • 观察者会注意到有关的信息,它不会尝试刷新令牌!因为在添加此请求之前,它不是为空。所以它只是追加并在那里等待。
  • 所以一段时间后,新的访问令牌到了
  • 然后,您可以使用新的访问令牌重试队列中的所有请求。 (从0开始保持订单顺序或一次全部同步,以提高响应速度)

-如果有一个已执行但没有到达服务器的请求,除非队列为空,该怎么办?

好吧,这是非常罕见的情况,但是有可能发生(对我来说已经发生了)。如果您像我说的那样正确实施了该代码,并且不要弄乱正在重试标志之类的东西,它将刷新两次!并不是很理想,但是很好,它会像魅力一样工作。

-如果我们不想在这种罕见情况下刷新两次或更多次怎么办?

尽管使用OAuth 2规则完全可以,但是您可以这样做以防止发生错误: -一旦您收到401错误(或标记为auth错误的任何错误),请立即删除访问令牌。 -任何其他请求都会注意到没有可请求的访问令牌,它们可以自动直接发送到 requestQueue 。 -因此根本没有其他要求竞争条件。

-最后有笔记吗?

如果刷新逻辑失败,请不要忘记清除队列。另外,如果用户再次登录,您可以保留它们并重试它们,但是您必须检查新登录用户的身份与前一个填充队列的用户的身份。

希望有帮助