【问题标题】:Parallel refresh requests of OAuth2 access token with Swift p2/OAuth2使用 Swift p2/OAuth2 的 OAuth2 访问令牌的并行刷新请求
【发布时间】:2016-04-15 22:12:18
【问题描述】:

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

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

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

确保在第一个刷新令牌仍在运行时不发出第二个刷新令牌请求的最佳方法是什么?

【问题讨论】:

  • 我目前遇到了同样的问题:我需要在多个并行请求中刷新令牌,但是当我开始刷新一个请求时,我用于其他请求的令牌现在无效。我目前正在研究这个方向: 1. 如果令牌已过期,则只刷新一个类,例如 AppDelegate 2. 获取新令牌并将其传递给另一个类 访问该值比我想象的要难:@ 987654322@有消息吗?

标签: ios swift oauth-2.0


【解决方案1】:

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

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

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

嗯,这是非常罕见的情况,但可能会发生(我已经发生过)。如果你像我说的那样正确地实现它,并且不要弄乱 is retrying 标志之类的东西,它只会刷新两次!不是很想要,但它很好,它会像一个魅力一样工作。

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

虽然使用 OAuth 2 规则完全没问题,但您可以这样做来防止错误: - 一旦您收到 401 错误(或任何您标记为身份验证错误),立即删除访问令牌。 - 任何进一步的请求都会注意到没有访问令牌可以请求,它们可以自动直接发送到 requestQueue。 - 所以根本没有更多的请求竞争条件。

- 最后有什么注意事项吗?

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

希望对你有帮助

【讨论】:

    【解决方案2】:

    找到您的令牌生命周期并设置缓冲区,例如 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!
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 2021-12-17
      • 2021-07-08
      • 2019-04-11
      • 2017-04-20
      • 1970-01-01
      相关资源
      最近更新 更多