p2.OAuth2 5.3.1

p2.OAuth2 5.3.1

测试已测试
语言语言 SwiftSwift
许可证 NOASSERTION
发布最新发布2021年2月
SPM支持 SPM

Pascal PfiffnerDavid Jennes 维护。



p2.OAuth2 5.3.1

  • 作者:
  • Pascal Pfiffner

OAuth2

Build Status License

Swift 5.0编写的针对 macOSiOStvOS 的 OAuth2 框架。

OAuth2 需要 Xcode 10.2,生成的框架可以在 OS X 10.11iOS 8 及更高版本上使用。欢迎使用贡献,请参阅 CONTRIBUTING.md

Swift 版本

由于 Swift 语言不断进化,我采用了与 Swift 版本相对应的版本控制方案:框架版本的前两位数字总是与库兼容的 Swift 版本,请参阅 releases。与最新 Swift 版本兼容的代码在名为适当分支的单独功能分支上可以找到。

使用方法

要在您的自代码中使用 OAuth2,请在源文件中从 import OAuth2 开始。

在OAuth2中存在多种不同的流程。本库支持所有这些流程,请确保您正在使用适合您用例和授权服务器的正确流程。下面的典型代码授权流程用作演示。其他流程的步骤大致相同,只需实例化不同的子类并使用不同的客户端设置即可。

仍然不起作用吗?请参阅特定站点特性

1. 使用设置字典实例化OAuth2

在本例中,您将构建一个访问GitHub的iOS客户端,因此以下代码将在您的某个视图控制器中,例如应用委托。

let oauth2 = OAuth2CodeGrant(settings: [
    "client_id": "my_swift_app",
    "client_secret": "C7447242",
    "authorize_uri": "https://github.com/login/oauth/authorize",
    "token_uri": "https://github.com/login/oauth/access_token",   // code grant only
    "redirect_uris": ["myapp://oauth/callback"],   // register your own "myapp" scheme in Info.plist
    "scope": "user repo:status",
    "secret_in_body": true,    // Github needs this
    "keychain": false,         // if you DON'T want keychain integration
] as OAuth2JSON)

看到那些redirect_uris了吗?您可以使用想要的任何方案,但是您必须在您的Info.plist中声明您使用的方案,并且必须在您所连接的授权服务器上注册相同的URI。

请注意,自iOS 9开始,您应该使用通用链接作为您的重定向URL,而不是自定义应用方案。这可以防止其他人重用您的URI方案并截获授权流程。
如果您针对的是iOS 12及其以上版本,您应该使用ASWebAuthenticationSession,它使使用您的本地重定向方案变得安全。

想避免切换到Safari并弹出SafariViewController或NSPanel吗?设置此选项

oauth2.authConfig.authorizeEmbedded = true
oauth2.authConfig.authorizeContext = <# your UIViewController / NSWindow #>

需要指定一个单独的刷新令牌URI吗?您可以在设置字典中设置refresh_uri。如果指定了,则库将使用您指定的refresh_uri刷新访问令牌,否则它将使用token_uri

需要调试吗?使用.debug或甚至.trace日志记录器。

oauth2.logger = OAuth2DebugLogger(.trace)

有关更多信息,请参阅下方的高级设置

2. 让数据加载器或Alamofire接管

从版本3.0开始,有一个OAuth2DataLoader类,您可以使用它从API获取数据。如果需要,它将自动启动授权,并确保即使您有多个调用正在进行时也能正常工作。有关配置授权的详细信息,请参见以下第4步,在本例中,我们将使用“嵌入式”授权,这意味着如果用户需要登录,我们将在iOS上显示SFSafariViewController。

此维基页面包含了您需要的内容,以便轻松使用OAuth2和Alamofire

let base = URL(string: "https://api.github.com")!
let url = base.appendingPathComponent("user")

var req = oauth2.request(forURL: url)
req.setValue("application/vnd.github.v3+json", forHTTPHeaderField: "Accept")

self.loader = OAuth2DataLoader(oauth2: oauth2)
loader.perform(request: req) { response in
    do {
        let dict = try response.responseJSON()
        DispatchQueue.main.async {
            // you have received `dict` JSON data!
        }
    }
    catch let error {
        DispatchQueue.main.async {
            // an error occurred
        }
    }
}

3. 确保拦截回调

当使用 OS 浏览器或 iOS 9+ Safari 视图控制器时,您需要在您的应用程序代理中拦截回调并允许 OAuth2 实例处理完整的 URL。

func application(_ app: UIApplication,
              open url: URL,
               options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
    // you should probably first check if this is the callback being opened
    if <# check #> {
        // if your oauth2 instance lives somewhere else, adapt accordingly
        oauth2.handleRedirectURL(url)
    }
}

对于 iOS 13,在 SceneDelegate.swift 中设置回调。

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
	if let url = URLContexts.first?.url {
		AppDelegate.shared.oauth2?.handleRedirectURL(url)
	}
}

配置完成!


如果您想深入了解或自行进行授权,请参考以下内容

4. 手动授权用户

默认情况下,如果不存在访问令牌或在密钥链中,OS 浏览器将被用于授权。从 iOS 12 开始,当在 iOS 上启用嵌入式授权时,将使用 ASWebAuthenticationSession(以前,从 iOS 9 开始,使用的是 SFSafariViewController)。

要开始授权,请调用 authorize(params:callback:) 或使用便捷方法 authorizeEmbedded(from:callback:) 来启用嵌入式授权。

登录屏幕仅在必要时才会呈现(有关详细信息,请参阅下文中的 _Manually Performing Authorization_),并在成功后自动 关闭 登录屏幕。有关其他选项,请参阅 高级设置

oauth2.authorize() { authParameters, error in
    if let params = authParameters {
        print("Authorized! Access token is in `oauth2.accessToken`")
        print("Authorized! Additional parameters: \(params)")
    }
    else {
        print("Authorization was canceled or went wrong: \(error)")   // error will not be nil
    }
}

// for embedded authorization you can simply use:
oauth2.authorizeEmbedded(from: <# presenting view controller / window #>) { ... }

// which is equivalent to:
oauth2.authConfig.authorizeEmbedded = true
oauth2.authConfig.authorizeContext = <# presenting view controller / window #>
oauth2.authorize() { ... }

不要忘记,当使用 OS 浏览器或 iOS 9+ Safari 视图控制器时,您需要在应用程序代理中拦截回调。如上步 2 中所示。

有关如何在 Mac 上执行此操作的详细信息,请参阅下文中的 手动执行授权

5. 处理回调

完成所有步骤后,回调将被调用,无论是带有非 nil 的 authParameters 字典(可能为空)还是一个错误。访问令牌和刷新令牌及其到期日期已提取出来,并作为 oauth2.accessTokenoauth2.refreshToken 参数提供。如果您想提取其他信息,则只需要检查 authParameters 字典。

对于以下详细说明的高级使用,有 afterAuthorizeOrFail 块可以用于您的 OAuth2 实例。如其名称所示,internalAfterAuthorizeOrFail 闭包是内部提供的 – 它是为子类化和编译目的而公开的,您不应触碰它。自版本 3.0.2 以来,您不能再使用 onAuthorizeonFailure 回调属性,它们已经被完全移除。

6. 发送请求

现在你可以获取一个 OAuth2Request,这是一个已经签名的 MutableURLRequest,用于从你的服务器获取数据。此请求使用以下方式设置 Authorization 头部,使用访问令牌:Authorization: Bearer {你的访问令牌}

let req = oauth2.request(forURL: <# resource URL #>)
// set up your request, e.g. `req.HTTPMethod = "POST"`
let task = oauth2.session.dataTaskWithRequest(req) { data, response, error in
    if let error = error {
        // something went wrong, check the error
    }
    else {
        // check the response and the data
        // you have just received data with an OAuth2-signed request!
    }
}
task.resume()

当然,你可以使用自己的 URLSession 进行这些请求,不必使用 oauth2.session;使用如在步骤2中所示的OAuth2DataLoader,或将它传给 Alamofire。关于如何在Alamofire中轻松使用OAuth2,请看这里

7. 取消授权

您可以通过调用 oauth2.abortAuthorization() 在任何时间取消正在进行的授权。这将取消正在进行的请求(如代码交换请求)或在您等待用户在网页上登录时调用回调。后者将关闭嵌入的登录界面或将用户重定向回应用程序。

8. 重新授权

在执行请求之前始终调用 oauth2.authorize() 是安全的。您也可以在您的应用程序再次激活后的第一个请求之前执行授权。或者,您可以在请求中拦截401并在此尝试请求之前再次调用授权。

9. 登出

如果您将令牌存储在密钥链中,可以通过调用 forgetTokens() 来删除它们。

但是,您的用户可能仍然登录到网站上,因此在进行下一次 authorize() 调用时,网页视图可能立即出现然后消失。当使用iOS 8的内置网页视图时,可以在以下代码段中抛出任何应用程序创建的cookies。使用较新的 SFSafariViewController 或在浏览器中执行登录时,最好是直接 打开退出页面 以使用户看到退出发生。

let storage = HTTPCookieStorage.shared
storage.cookies?.forEach() { storage.deleteCookie($0) }

手动执行授权

方法 authorize(params:callback:) 将执行以下操作:

  1. 检查是否已经有授权调用正在进行,如果有,将使用 OAuth2Error.alreadyAuthorizing 错误中止
  2. 检查是否存在未过期的访问令牌(或已在密钥链中),如果不存在
  3. 检查是否存在刷新令牌,如果找到
  4. 尝试使用刷新令牌获取新的访问令牌,如果失败
  5. 通过使用 authConfig 设置启动 OAuth2 舞步,以确定如何向用户显示授权屏幕

您的 oauth2 实例将使用通过 ephemeralSessionConfiguration() 配置自动创建的 URLSession 对其请求进行请求,并在 oauth2.session 中公开。您可以设置 oauth2.sessionConfiguration 为您自己的配置,例如,如果您想更改超时值。您还可以设置 oauth2.sessionDelegate 为您自己的会话代理。

维基上有 授权()方法的完整调用图。如果您不希望使用这种自动化,显示和隐藏授权屏幕的手动步骤如下:

嵌入式 iOS:

let url = try oauth2.authorizeURL(params: <# custom parameters or nil #>)
oauth2.authConfig.authorizeEmbeddedAutoDismiss = false
let web = try oauth2.authorizer.authorizeSafariEmbedded(from: <# view controller #>, at: url)
oauth2.afterAuthorizeOrFail = { authParameters, error in
    // inspect error or oauth2.accessToken / authParameters or do something else
    web.dismissViewControllerAnimated(true, completion: nil)
}

macOS 上的模态表单:

let window = <# window to present from #>
let url = try oauth2.authorizeURL(params: <# custom parameters or nil #>)
let sheet = try oauth2.authorizer.authorizeEmbedded(from: window, at: url)
oauth2.afterAuthorizeOrFail = { authParameters, error in
    // inspect error or oauth2.accessToken / authParameters or do something else
    window.endSheet(sheet)
}

macOS 上的新窗口:

let url = try oauth2.authorizeURL(params: <# custom parameters or nil #>)
let windowController = try oauth2.authorizer.authorizeInNewWindow(at: url)
oauth2.afterAuthorizeOrFail = { authParameters, error in
    // inspect error or oauth2.accessToken / authParameters or do something else
    windowController.window?.close()
}

iOS/macOS 浏览器:

let url = try oauth2.authorizeURL(params: <# custom parameters or nil #>)
try oauth2.authorizer.openAuthorizeURLInBrowser(url)
oauth2.afterAuthorizeOrFail = { authParameters, error in
    // inspect error or oauth2.accessToken / authParameters or do something else
}

macOS

有关如何在您的 Mac 应用中接收回调 URL 的示例,请参阅 OAuth2 示例应用 的 AppDelegate 类。如果授权将代码显示给用户,例如使用 Google 的 urn:ietf:wg:oauth:2.0:oob 回调 URL,您可以从用户的剪贴板中检索代码,并继续授权

let pboard = NSPasteboard.general()
if let pasted = pboard.string(forType: NSPasteboardTypeString) {
    oauth2.exchangeCodeForToken(pasted)
}

流程

根据您需要的 OAuth2 流,您将想要使用正确的子类。有关 OAuth 基础的非常好解释:OAuth 圣经

授权码授予

对于OAuth 2.0代码授权流程(response_type=code),您应该使用OAuth2CodeGrant类。这种流程通常用于可以保护其秘密的应用程序,例如服务器端应用程序,而不会在分布式二进制文件中使用。如果应用程序无法保护其秘密,例如分布式iOS应用程序,则可以使用隐式授权,或者在某些情况下,仍然使用代码授权,但省略客户端密钥。然而,从移动设备(包括客户端密钥)使用代码授权已经成为一种常见做法。

该类完全支持这些流程,如果客户端具有非空客户端密钥,它会自动创建一个“Basic”授权头。这意味着您很可能必须在其设置中指定client_secret;如果没有,例如在Reddit的情况下,指定空字符串。如果网站要求在请求体中包含客户端凭据,请将clientConfig.secretInBody设置为true,如下所述。

隐式授权

隐式授权(《response_type=token》)适用于无法保护其秘密的应用程序,例如分布式二进制文件或客户端Web应用程序。使用OAuth2ImplicitGrant类接收令牌并执行请求。

在这里添加另一个代码示例会很不错,但它与代码授权几乎相同。

客户端凭证

双端流程允许应用程序通过其客户端ID和密钥进行授权。像往常一样实例化OAuth2ClientCredentials,在设置字典中提供client_id和客户端密钥(以及其他配置),然后就可以使用了。

用户名和密码

支持使用OAuth2PasswordGrant子类进行资源拥有者密码凭证授权。创建一个实例,像上面那样设置其usernamepassword属性,然后调用authorize()

特定网站的特性

一些网站可能不完全遵循OAuth2流程,例如Facebook返回数据方式不同,或Instagram及其它产品省略强制返回参数。框架通过创建特定网站的子类和/或配置细节来处理这些偏差。如果您需要传递额外的头信息参数,可以根据以下方式在设置字典中提供这些信息。

let oauth2 = OAuth2CodeGrant(settings: [
    "client_id": "...",
    ...
    "headers": ["Accept": "application/vnd.github.v3+json"],
    "parameters": ["duration": "permanent"],
] as OAuth2JSON)

高级设置

您将使用的与oauth2.authConfig相关的最主要配置是是否使用嵌入式登录。

oauth2.authConfig.authorizeEmbedded = true

类似地,如果您想自己处理关闭登录界面(对于下文提到的较新的授权会话则不可行)。

oauth2.authConfig.authorizeEmbeddedAutoDismiss = false

一些网站还希望在请求数据中包含客户端ID/密钥组合,而不是在授权头中。

oauth2.clientConfig.secretInBody = true
// or in your settings:
"secret_in_body": true

有时您还需要提供额外的授权参数。这可以通过以下三种方式完成。

oauth2.authParameters = ["duration": "permanent"]
// or in your settings:
"parameters": ["duration": "permanent"]
// or when you authorize manually:
oauth2.authorize(params: ["duration": "permanent"]) { ... }

指定自定义HTTP头的方法与此类似。

oauth2.clientConfig.authHeaders = ["Accept": "application/json, text/plain"]
// or in your settings:
"headers": ["Accept": "application/json, text/plain"]

从iOS 9的2.0.1版本开始,将使用SFSafariViewController进行嵌入式授权。从iOS 11的4.2版本开始,您可以选择使用这些较新的授权会话视图控制器。

oauth2.authConfig.ui.useAuthenticationSession = true

要恢复到旧的定制OAuth2WebViewController,您不应该这样做,因为ASWebAuthenticationSession的加密性更高。

oauth2.authConfig.ui.useSafariView = false

使用iOS 8及更老的OAuth2WebViewController时,如何定制返回按钮。

oauth2.authConfig.ui.backButton = <# UIBarButtonItem(...) #>

有关钥匙串PKCE的设置请见下文。

与Alamofire的使用

当使用Alamofire v4或更新版本以及OAuth2 v3或更新版本时,您将获得最佳体验。

动态客户端注册

支持动态客户端注册。如果在设置过程中设置了registration_url但没有设置client_id,则在继续到实际授权之前,自动尝试注册该客户端。注册返回的身份验证凭据将被存储到密钥链中。

OAuth2DynReg类负责处理客户端注册。如果您需要手动注册,可以使用其register(client:callback:)方法。注册参数来自客户端的配置。

let oauth2 = OAuth2...()
oauth2.registerClientIfNeeded() { error in
    if let error = error {
        // registration failed
    }
    else {
        // client was registered
    }
}
let oauth2 = OAuth2...()
let dynreg = OAuth2DynReg()
dynreg.register(client: oauth2) { params, error in
    if let error = error {
        // registration failed
    }
    else {
        // client was registered with `params`
    }
}

PKCE

PKCE支持由useProofKeyForCodeExchange属性和设置字典中的use_pkce键来控制。默认情况下禁用。启用时,每次授权请求都会为每个授权请求生成一个新的代码验证符字符串。

密钥链

该框架可以透明地使用iOS和macOS密钥链。它由useKeychain属性控制,可以在初始化时使用keychain设置字典键来禁用。默认情况下启用,如果您在初始化时没有将其关闭,密钥链将被查询以获取与授权URL相关的令牌和客户端凭据。如果您在初始化后关闭它,密钥链将被查询以查找现有的令牌,但新的令牌将不会写入密钥链。

如果您想要从密钥链中删除令牌,即完全注销用户,请调用forgetTokens()。如果您已动态注册您的客户端并希望重新开始,您可以调用forgetClient()

理想情况下,访问令牌会包含一个“expires_in”参数,告诉您令牌的有效期有多长。如果缺少此参数,则在密钥链中找到令牌时,框架仍然会使用那些令牌,并不重新执行OAuth操作。如果您希望包装令牌假设未过期,您可以在设置中提供token_assume_unexpired: false或将clientConfig.accessTokenAssumeUnexpired设置为false。

以下是您可以用于更多控制的设置字典键

  • keychain:是否使用密钥链的布尔值,默认为true
  • keychain_access_mode:用于keychain kSecAttrAccessible属性的字符串值,默认为"憧AttrAccessibleWhenUnlocked",如果您需要当手机锁定时可以访问令牌,可以将其更改为例如"摊AttrAccessibleAfterFirstUnlock"。
  • keychain_access_group:用于keychain kSecAttrAccessGroup属性的字符串值,默认为nil。
  • keychain_account_for_client_credentials:在keychain中识别客户端凭据时要使用的名称,默认为"客户端凭据"。
  • keychain_account_for_tokens:在keychain中识别令牌时要使用的名称,默认为"当前令牌"。

安装

您可以使用Swift Package ManagergitCarthage。首选方法是使用Swift Package Manager

Swift Package Manager

在Xcode 11及其以上版本中,从Xcode菜单中选择"文件",然后选择"Swift Packages"」"添加包依赖...",粘贴此存储库的URL:https://github.com/p2/OAuth2.git。选择一个版本,Xcode将完成其余步骤。

Carthage

通过Carthage进行安装很简单

github "p2/OAuth2" ~> 4.2

git

使用Terminal.app,克隆OAuth2存储库,最好将其放置到您的应用程序项目子目录中。

$ cd path/to/your/app
$ git clone --recursive https://github.com/p2/OAuth2.git

如果您正在使用git,则希望将其添加为子模块。一旦克隆完成,请在Xcode中打开您的应用程序项目,并将OAuth2.xcodeproj添加到您的应用程序中。

Adding to Xcode

现在将框架链接到您的应用程序

Linking

这三步执行时的需要

  1. 使您的应用程序也能够构建框架
  2. 将框架链接到您的应用程序中
  3. 在分发时将框架嵌入到您的应用程序中

许可证

这段代码在 Apache 2.0 许可协议 下发布,这意味着您可以在开源和闭源项目中使用它。由于没有 NOTICE 文件,所以您的产品中无需包含任何内容。