【译】iOS 用 ASWebAuthenticationSession 实现 OAuth 登录(三)- 连接 GitHub App

原文链接:Implementing OAuth with ASWebAuthenticationSession | Kodeco


iOS 用 ASWebAuthenticationSession 实现 OAuth 登录

使用 ASWebAuthenticationSession 连接 GitHub App

点击该指南顶部或底部的 Download Materials 按钮下载项目资源。在 XCode 中打开 starter project ,编译运行。

现在,点击 Sign In 不会做任何处理。你需要在工程中实现登录特性。 在做这些之前,可以先浏览一下 starter 项目。

现在是有一个基础的项目,它有两个画面:一个用于登录、一个用于查看代码仓库。然后有数据模型 UserRepositoryNetworkRequest

这里最有趣的数据模型是 NetworkRequest 。它作为 URLSession 的封装使用。

打开 NetworkRequest.swift 。你会发现枚举为网络层概括了支持的 HTTP 方法 和错误。 这里更有趣的 enumRequestType, 它列出了能够支持的 APP 向 GitHub 发起的请求。 在枚举中,还有辅助方法为指定的请求类型构建 NetworkRequest (网络请求)。 非常顺手!

更多信息查看 GitHub's documentation on its REST API

用 GitHub APP 的设置值升级项目

要想让 GitHub 知道你的 APP ,需要将 GitHub APP 信息加到项目中。

打开 NetworkRequest.swift ,在 // MARK: Private Constants 下面,你会找到三个静态常量:

csharp 复制代码
static let callbackURLScheme = "YOUR_CALLBACK_SCHEME_HERE"
static let clientID = "YOUR_CLIENT_ID_HERE"
static let clientSecret = "YOUR_CLIENT_SECRET_HERE"

将这些替换为你刚刚创建的 GitHub APP 的值。

回调的 URL Scheme 并不需要是在创建 GitHub APP 时输入的完整 URL ,它只需要是 scheme 。 就本例来说,使用 authhub 作为 callbackURLScheme (回调Scheme)。

然后继续,NetworkRequest 中的 start(responseType:completionHandler:) 是实际发出网络请求的地方。 在这里,你需要为带有授权 HTTP 头部的 URL 请求定义一些参数,你的 APP 应该有可用的访问令牌。

GitHub API 会期望你通过Authorization HTTP 头部为需要授权的请求发送访问令牌。该头部的值会是下面的格式:

Bearer YOUR_TOKEN_HERE

此外,该方法会使用完成处理程序处理任何错误,并将JSON数据解析为参数中指定的原生Swift类型。

至今为止,很棒!

视图概览

接下来,就是视图。这是一个非常简单的 APP,所以只有两个必要的视图:SignInView.swiftRepositoriesView.swift 。不需要过于担心,有趣的内容在视图模型里。

最后是视图模型。

视图模型概览

打开 RepositoriesViewModel.swift。在这里你会找到请求已登录 GitHub 用户的代码仓库列表和提供视图以显示列表的代码。

APP 中的另外一个视图模型在 SignInViewModel.swift 中。在这里你会添加你的 ASWebAuthenticationSession ,马上就会用到它。

理解 ASWebAuthenticationSession

ASWebAuthenticationSession 是 Apple 认证服务 框架 的一个API ,可用来通过 Web 服务认证用户。

你创建了该类的一个实例,可用于在 App 的认证页面利用开箱即用的方案。它允许用户进行认证,然后在应用中接收一个带用户认证令牌的回调方法。

该 API 很酷的是它会适配所运行的原生平台。 对于 iOS ,这意味着嵌入式的安全浏览器;在 MacOS 上,是默认浏览器(如果该浏览器支持 Web 认证 Session)或 Safari 。

只需要很少的参数,就可以马上运行你的(或第三方)认证服务,而不需要用Web视图进行临时处理来实现。

添加 ASWebAuthenticationSession

ASWebAuthenticationSession 的工作方式是它会期望一个认证 URL (带有认证提供方所需要的所有参数),你的 App 的回调 URL Scheme (为了登录成功时返回),和用于操作和管理认证令牌的完成处理器。

打开 SignInViewModel.swift 。添加代码到 signInTapped() 中:

swift 复制代码
guard let signInURL =
  NetworkRequest.RequestType.signIn.networkRequest()?.url 
else {
  print("Could not create the sign in URL .")
  return
}

你需要用来登录的 URL ,所以你使用 RequestType 来获取它。 如果因为某些原因处理失败了,控制台会打印出错误,该方法不会做任何事情并返回。

接下来,在相同的方法中,添加以下内容:

swift 复制代码
let callbackURLScheme = NetworkRequest.callbackURLScheme
let authenticationSession = ASWebAuthenticationSession(
  url: signInURL,
  callbackURLScheme: callbackURLScheme) { [weak self] callbackURL, error in
  // Code will be added here next! :)
}

这里,你首先创建一个常量来存储你的 回调 URL scheme ,然后继续创建一个新的 ASWebAuthenticationSession 。 Session初始化器需要登录URL和回调 scheme 作为完成后处理的参数。

The callback URL scheme is the one you just replaced inside NetworkRequest, but what about the sign-in URL?

回调 URL scheme 是你刚刚在 NetworkRequest 中替换的,但是什么是 登录 URL ?

打开 NetworkRequest.swift 。看一下 url().signIn 处理。 在这里,你能看到创建成功的登录请求所需的 主机、路径和参数。 值得注意的是 client_id ,之后需要你添加到该文件中。

检查错误

打开 SignInViewModel.swift , 用下面的内容替换 // Code will be added here next! :)

swift 复制代码
// 1
guard 
  error == nil,
  let callbackURL = callbackURL,
  // 2
  let queryItems = URLComponents(string: callbackURL.absoluteString)?
    .queryItems,
  // 3
  let code = queryItems.first(where: { $0.name == "code" })?.value,
  // 4
  let networkRequest =
    NetworkRequest.RequestType.codeExchange(code: code).networkRequest() 
else {
  // 5
  print("An error occurred when attempting to sign in.")
  return
}

这是一个相当有分量的守护声明,但仍然是必要的。 事情是这样的:

  1. 检查错误然后确认这是一个有效的回调 URL 。
  2. 从这里,通过抽取回调 URL 中的组成部分获取到 URL 的查询项目。 查询项目会帮助你检查响应是否带有你需要用令牌换取的授权码。
  3. 当回调 URL 加载时,它包含作为查询参数的授权码。
  4. 接下来,获得一个授权码兑换的 NetworkRequest
  5. 如果其中某个检查失败了,你会打印错误并从方法返回。

编译运行。

点击 Sign In 按钮。然后确认结果... 没反应?!

设置 Presentation Context Provider

在认证 Session 生效之后,还有两件事需要做。 第一个就是设置一个 presentation context provider (展示上下文提供者)。

首先要实现必须的协议。 现在需要在 SignInViewModel.swift 的末尾添加下面的扩展。

swift 复制代码
extension SignInViewModel: ASWebAuthenticationPresentationContextProviding {
  func presentationAnchor(for session: ASWebAuthenticationSession)
  -> ASPresentationAnchor {
    let window = UIApplication.shared.windows.first { $0.isKeyWindow }
    return window ?? ASPresentationAnchor()
  }
}

这里,你实现了 ASWebAuthenticationPresentationContextProviding 来告诉你的权限 Session 如果呈现自身。 在该场景中,ASWebAuthenticationSession 会使用浏览器、Cookie、SESSION和其它展示给用户一个登录页面,之后会重定向到你的 APP 。

现在在 signInTapped() 的末尾添加以下代码:

swift 复制代码
authenticationSession.presentationContextProvider = self

为授权视图设定位置。这负责展示内容。

启用权限 SESSION

第二件要做的事是启用权限SESSION。

SignInViewModelsignInTapped() 里添加以下代码:

swift 复制代码
if !authenticationSession.start() {
  print("Failed to start ASWebAuthenticationSession")
}

这会验证 SESSION 是否能够开始。如果不能的话,会向控制台打印另外的错误。

编译运行。

点击 Sign In 按钮,如果是在 12.4 之前的 iOS 上运行,你会看到一个警告信息(这是认证SESSION API 的一部分,会指明你的 App 要使用 GitHub 来登录)。

点击 Continue

你会看到 GitHub 登录页面的一个模态控制器。成功登录之后,模态控制器会消失,再来一次则不会有任何响应。

但这已经很棒了!是的,真的,已经很棒了。

如果你输入了非法凭证,你会看到 GitHub 页面自身的认证错误。 如果模态控制器消失了,然后控制台没有打印任何错误,那是因为 GitHub 已经向你的 App 响应了授权码。

处理授权码

这一点上,你需要为访问令牌和刷新令牌交换授权码。

打开 SignInViewModel.swift 。在 signInTapped()authenticationSession 的完成处理器中,添加以下代码:

swift 复制代码
self?.isLoading = true
networkRequest.start(responseType: String.self) { result in
  switch result {
  case .success:
    self?.getUser()
  case .failure(let error):
    print("Failed to exchange access code for tokens: (error)")
  }
}

该部分执行后,会告诉视图正在加载一些内容,这会用一个活动视图替换掉 Sign In 按钮。 这会阻止用户在处理现有 SESSION 的过程中直接越过整个流程。 你使用已获取的网络请求作为守护语句的一部分来实行令牌交换。

NetworkRequeststart(responseType:completionHandler:) 也有一个完成处理器。 在这里可以检查请求结果成功或失败。 如果成功了,会调用 getUser() 。如果失败了,会向控制台打印错误。

显示结果

再次运行 APP 之前,向 getUser() 添加以下代码:

swift 复制代码
isLoading = true
NetworkRequest
  .RequestType
  .getUser
  .networkRequest()?
  .start(responseType: User.self) { [weak self] result in
    switch result {
    case .success:
      self?.isShowingRepositoriesView = true
    case .failure(let error):
      print("Failed to get user, or there is no valid/active session: (error)")
    }
    self?.isLoading = false
  }

该方法和使用 NetworkRequest 所做的类似,预期这会获得已登录用户的信息。 如果请求成功了,你会设置一个布尔值为 true ,以告诉视图显示仓库。 否则,会向控制台打印一条错误信息。

先不管结果,你已经告诉视图,加载已经结束。

编译运行。然后,登录会和之前一样。

会有两件预期外的事情发生:

  1. 模态窗口显示并结束,不会给你输入 Github 凭证的机会。
  2. XCode的控制台会显示一条错误信息。

因为在该场景的背后, ASWebAuthenticationSession 使用WEB视图、cookie 和 WEB session,它已经自动缓存了授权码。注意,这只会持续一段不确定的时间,并不是一直持续,但这仍然是你所期望的。

你可以之后在 创建短期 SESSION 部分之后进行处理。现在先集中解决令牌交换时失败的错误。

在你能解决之前,你需要了解一些令牌本身的理论。


相关推荐
DisonTangor6 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -6 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_865854881 天前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom1 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈1 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403551 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔2 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter2 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端
pb82 天前
引入最新fluwx2.5.4的时候报错
flutter·ios