HTTP简介

HTTP基础结构

HTTP恳求体

HTTP 加载恳求

HTTP 模仿测验

HTTP 链式加载器

HTTP 动态修改恳求

HTTP 恳求选项

HTTP 重置

HTTP 撤销

HTTP 限流

HTTP 重试

HTTP 基础鉴权

HTTP 自动鉴权设置

HTTP 自动鉴权

HTTP 复合加载器

HTTP 头脑风暴

HTTP 总结

上一篇文章介绍了 OAuth 流程的基础知识:咱们怎么检查令牌、咱们怎么要求用户登录、咱们怎么改写令牌等等。 在这篇文章中,咱们将采用该状况机并将其集成到 HTTPLoader 子类中。

加载器

咱们现已为授权流程定义了状况机,但咱们需求另一个简略的状况机来描绘加载器将怎么与之交互。 让咱们考虑一下咱们的加载器在被要求加载使命时或许处于的各种“状况”:

  • 闲暇(什么都没有产生,或许状况机失败)→咱们应该发动状况机并开始运转授权流程
  • 授权(状况机正在运转)→咱们应该让使命等待状况机完成
  • 授权(咱们有有用的凭证)→加载使命
  • 授权(咱们的凭证已过期)→ 咱们需求改写令牌 假如咱们仔细观察一下,咱们会发现“闲暇”状况实际上与“授权 + 过期令牌”状况是一回事:在任何一种状况下,咱们都需求发动状况机,以便 咱们可以获得新的令牌(回想一下,状况机现已具有改写过期令牌的逻辑)。 考虑到这一点,让咱们存根咱们的加载器:
public class OAuth: HTTPLoader {
    private var stateMachine: OAuthStateMachine?
    private var credentials: OAuthCredentials?
    private var pendingTasks = Array<HTTPTask>()
    public override func load(task: HTTPTask) {
        // TODO: make everything threadsafe
        if stateMachine != nil {
            // "AUTHORIZING" state
            // we are running the state machine; load this task later
            self.enqueueTask(task)
        } else if let tokens = credentials {
            // we are not running the state machine
            // we have tokens, but they might be expired
            if tokens.expired == true {
                // "AUTHORIZED+EXPIRED" state
                // we need new tokens
                self.enqueueTask(task)
                self.runStateMachine()
            } else {
                // "AUTHORIZED+VALID" state
                // we have valid tokens!
                self.authorizeTask(task, with: tokens)
                super.load(task: task)
            }
        } else {
            // "IDLE" state
            // we are not running the state machine, but we also do not have tokens
            self.enqueueTask(task)
            self.runStateMachine()
        }
    }
}

咱们可以看到 if 语句中编码的四种或许状况。 咱们遗漏了一些部分,所以让咱们看一下:

public class OAuth: HTTPLoader {
    ... // the stuff above
    private func authorizeTask(_ task: HTTPTask, with credentials: OAuthCredentials) {
        // TODO: create the "Authorization" header value
        // TODO: set the header value on the task
    }
    private func enqueueTask(_ task: HTTPTask) {
        self.pendingTasks.append(task)
        // TODO: how should we react if the task is cancelled while it's pending?
    }
    private func runStateMachine() {
        self.stateMachine = OAuthStateMachine(...)
        self.stateMachine?.delegate = self
        self.stateMachine?.run()
    }
}
extension OAuth: OAuthStateMachineDelegate {
    // TODO: the OAuth loader itself needs a delegate for some of these to work
    func stateMachine(_ machine: OAuthStateMachine, wantsPersistedCredentials: @escaping (OAuthCredentials?) -> Void) {
        // The state machine is asking if we have any credentials
        // TODO: if self.credentials != nil, use those
        // TODO: if self.credentials == nil, ask a delegate
    }
    func stateMachine(_ machine: OAuthStateMachine, persistCredentials: OAuthCredentials?) {
        // The state machine has found new tokens for us to save (nil = delete tokens)
        // TODO: save them to self.credentials
        // TODO: also pass them on to our delegate
    }
    func stateMachine(_ machine: OAuthStateMachine, displayLoginURL: URL, completion: @escaping (URL?) -> Void) {
        // The state machine needs us to display a login UI
        // TODO: pass this on to our delegate
    }
    func stateMachine(_ machine: OAuthStateMachine, displayLogoutURL: URL, completion: @escaping () -> Void) {
        // The state machine needs us to display a logout UI
        // This happens when the loader is reset. Some OAuth flows need to display a webpage to clear cookies from the browser session
        // However, this is not always necessary. For example, an ephemeral ASWebAuthenticationSession does not need this
        // TODO: pass this on to our delegate
    }
    func stateMachine(_ machine: OAuthStateMachine, didFinishWithResult result: Result<OAuthCredentials, Error>) {
        // The state machine has finished its authorization flow
        // TODO: if the result is a success
        //       - save the credentials to self.credentials (we should already have gotten the "persistCredentials" callback)
        //       - apply these credentials to everything in self.pendingTasks
        // 
        // TODO: if the result is a failure
        //       - fail all the pending tasks as "cannot authenticate" and use the error as the "underlyingError"
        self.stateMachine = nil
    }
}

大多数对状况机的反响都涉及将信息转发给另一个代表。 这是因为咱们的加载器(正确!)不知道怎么显现登录/刊出 UI,咱们的加载器也不知道凭证怎么保存或保存在何处。 这是应该的。 显现 UI 和耐久化信息与咱们的加载器“验证恳求”的使命无关。

重置

除了 TODO: 分散在咱们代码周围的项目之外,咱们短少的终究一个主要难题是“重置”逻辑。 乍一看,咱们或许会认为是这样的:

public func reset(with group: DispatchGroup) {
    self.stateMachine?.reset(with: group)
    super.reset(with: group)
}

正如上一篇文章中所讨论的,状况机中的每个状况都可以被 reset() 调用中断,这便是产生这种状况的方法。 因而,假如咱们的机器当时正在运转,这便是咱们可以中断它的方法。

……但是假如它没有运转呢? 假如咱们现已经过身份验证并拥有有用令牌,然后咱们收到对 reset() 的调用怎么办? (这实际上是常见的状况,因为“重置”在很大程度上类似于“刊出”,通常只要在身份验证成功时才会产生)

在这种状况下,咱们需求修改咱们的状况机。 回想一下咱们上次描绘这个 OAuth 流程:

Swift中的HTTP(十五) 自动鉴权

此流程中没有任何内容可处理“刊出”场景。 咱们需求略微修改一下,以便咱们也有办法使令牌无效。 此刊出状况已在之前的“注意事项”部分中列出。 包括它后,状况流程图现在大致如下所示:

Swift中的HTTP(十五) 自动鉴权

关于这件事需求注意的两点是:

  • 从一切从前状况到新“刊出”状况的虚线表明在状况机运转时经过调用 reset() 来“中断”该状况

  • 新的“刊出”状况是状况机的或许进口点。 也便是说,咱们可以在这个状况下发动机器。 我将把“刊出”状况的实现留给你,但它需求做一些事情:

  • 它需求结构 URL 来显现“刊出”页面以显现给用户(之前说到的从浏览器会话中清除 cookie 的页面)

  • 它需求联系服务器并告诉他们凭证已被撤销

  • 它需求告诉其委托人清除任何耐久化的凭证 有了这个,咱们的 OAuth 加载器应该可以完全正常作业:

public func reset(with group: DispatchGroup) {
    if let currentMachine = self.stateMachine {
       // we are currently authorizing; interrupt the flow
       currentMachine.reset(with: group)
    } else {
        // TODO: you'll want to pass the "group" into the machine here
        self.stateMachine = OAuthStateMachine(...)
        self.stateMachine?.delegate = self
        // "running" the state machine after we gave it the DispatchGroup should start it in the LogOut state
        self.stateMachine?.run()
    }
    super.reset(with: group)
}

定论

我希望这两篇文章说明 OAuth 不必是这么可怕的东西。 咱们有状况机来授权(或撤销授权)用户,它有六种或许的状况。 这不是许多,咱们可以把它记在脑子里。 同样,加载程序自身只要少量几种或许的状况,详细取决于状况机的状况。 经过将各自的逻辑封装在不同的笼统层中,咱们可以将全体复杂性保持在相当低的水平。 咱们机器的每个状况子类都是直截了当的; 咱们的 StateMachine 类中几乎没有代码; 甚至咱们的 OAuth 加载程序也只要几十行。

但由此,咱们终究得到了一个功能完全的 OAuth 流程:

  • 咱们保证一次只运转一个 OAuth 授权 UI
  • 咱们答应客户显现他们想要的 OAuth UI
  • 咱们答应客户以他们想要的方法保留令牌
  • 咱们答应中断授权
  • 咱们答应经过重置撤销授权 太棒了!

在下一篇文章中,咱们将把 BasicAuth 和 OAuth 加载器组合成一个复合身份验证加载器。