如何通过 OAuth 2.0 使 iOS Apps 集成 LinkedIn 登录功能?

标签: Mobile Insight iOS | 发表时间:2016-03-21 14:44 | 作者:贾娅妮
出处:http://news.oneapm.com/

社交网络早已成为人们日常生活的一部分。其实,社交网络也是编程生活的一部分,大多数 App 必须通过某种方式与社交网络交互,传送或接收与用户相关的数据。大多数情况下,用户需要登录某种社交网络,授权 App 代表自己进行请求。

目前,此类社交网络的种类非常丰富,以 Facebook 与 Twitter 最为常用。而且,iOS 系统内置了对这两款社交网络的支持。然而,对于其他类型的社交网络,开发者必须投入更多的努力,以使 App 获得授权访问这些社交网络,继而运行经过授权的合法请求。LinkedIn 就是这样一种社交网络,在本教程中,我们会了解如何为 App 授权,使之与 LinkedIn 的服务器交换受保护的数据。

为 iOS App 授权以访问 LinkedIn,并根据后者提供的 API 运行特定的操作,可以通过两种方法实现。方法一:使用 LinkedIn 支持的 OAuth 2.0 协议。方法二:使用 LinkedIn 提供的 iOS SDK。与所有第三方 SDK 一样,LinkedIn 提供的 SDK 必须集成到项目中,经过合理的配置才能使用。

linked-in-sign-in

在本文中,我们将仅专注于第一种方法。因此,我们将学习 LinkedIn 与 OAuth 2.0 指南中与此相关的指定内容,包括让用户通过 App(是任何 App,而不仅仅是 iOS 系统)登录 LinkedIn 并为 App 授权执行进一步请求的必要流程。尽管 LinkedIn iOS SDK 也是很好的选择,但笔者更喜欢 OAuth 方法,原因有三:

  1. 笔者个人更偏爱此类任务:使用 REST API 调用与服务器进行直接的交流,以顺利完成授权过程。
  2. LinkedIn 网站中有关 LinkedIn iOS SDK 的介绍相当明确详尽,因此笔者认为基于相同主题写的教程恐怕益处不大。
  3. 笔者认为,使用 LinkedIn iOS SDK 时存在一个缺陷:官方的 LinkedIn App 必须已经安装在设备中,否则登录与授权过程就无法完成。如果某个 App 需要获得用户的 LinkedIn 主页信息,但用户并不想安装 LinkedIn 官方应用,就会造成不便。

关于 OAuth 2.0 协议,能说的实在太多了。读者最好还是登录 官网仔细研读一下。简而言之,为了成功完成登录与授权过程,本教程将会遵循以下步骤:

  • 必要地,我们将在 LinkedIn 开发者网站创建一个新的 App。从而得到完成后续过程必备的两个重要密匙(Client ID 与 Client Secret)。
  • 通过一个 Web 视图,让用户登录其 LinkedIn 账户。
  • 根据以上所得,再加上一些必要数据,向 LinkedIn 服务器索取授权码。
  • 与一个访问令牌交换授权码。

访问令牌是与 OAuth 交互的必要条件。通过一个有效的令牌,我们便能向 LinkedIn 服务器发送经过授权的请求。并根据 App 的性质,“get”或“post” 数据到用户的主页。

在继续阅读之前,请确保你理解 OAuth 2.0 的工作原理,以及它的流(flow)。如果必要,阅读其他资源以获取更多信息(比如 这儿这儿这儿)。

说了这么多,让我们进入正题,介绍本教程的演示应用,然后进入具体实现。笔者相信,我们将要学习的内容是趣味无穷的。

作为参考,以下是 LinkedIn 官方文档的链接:

演示 App 概览

我们在本教程中将要实现的演示 App 由两部分视图控制器组成:第一个(默认的 ViewController 类)只包含三个按钮:

  1. 一个名为 LinkedIn Sign In(LinkedIn 登录)的按钮,用于启动登录与授权流程。
  2. 一个名为 Get my profile URL(获得我的主页 URL)的按钮,用于执行一个经过授权的请求,使用访问令牌获得用户主页的 URL。
  3. 一个展示主页 URL 的按钮,点击之后会在 Safari 中打开用户的 LinkedIn 主页。

默认情况下,只会启用第一个按钮。实际上,只要还未获得访问令牌,该按钮就会一直可用。在其他情况下,第一个按钮会被禁用,同时启用第二个按钮。第三个按钮是隐藏的,只有当得到(通过第二个按钮)用户主页的 URL 时,才会可见。

view-controller-signin

第二个视图控制器会包含一个 Web 视图。通过该试图,你可以登录 LinkedIn 账户,这样认证与授权过程才能成功进行。当获得用于向 LinkedIn 发送合法请求的访问令牌后,该视图控制器就会被移除。

t47_2_user_sign_in

与往常一样,我们不需要从头开始创建项目。你可以 下载一个启动项目,在此基础上继续搭建。

基本上,我们的主要努力将专注于获取访问令牌。我们会遵循 OAuth 2.0 协议以及 LinkedIn 指南的指定,一步一步地完成所有必备流程。获得访问令牌之后,我们会继续解释如何向 LinkedIn 发送合法请求,以获得授权用户公共主页的 URL。成功得到 URL 之后,我们会请用前面提到的第三个按钮,将主页内容显示在 Safari 浏览器中。

在你继续阅读之前,请确保已经下载启动项目,打开它并熟悉它的。准备就绪之后,请继续往下看。

LinkedIn 开发者网站—— 创建新的 App

实现 OAuth 2.0 登录流程的第一步,是在 LinkedIn 开发者网站创建一个新的 App 记录。为此,你仅需访问 此链接。如果你还没有登录 LinkedIn 主页,你将收到提示以完成登录操作。

注意:如果你在下面的步骤中使用 Safari 出现问题,请选择其他浏览器。我使用的是 Chrome 浏览器。

登录之后,找到网站“我的应用(My Applications)”部分,你会发现一个名为“创建应用(Create Application)”的黄色按钮。点击它开始创建新的应用,之后我们会将它与 iOS App 进行联结。

t47_3_create_app_button

在接下来出现的表格中,填写所有栏目。如果需要填写公司名称或上传应用 logo,不用担心,输入一些虚假信息也可。之后,接受使用条款,点击提交按钮。请一定要在带红色星号的栏目中输入内容,否则你将无法继续。以下为示例:

t47_4_create_new_app

我们的目标是抵达下一个页面:

t47_5_app_settings

如你在上面的截图中所见,在此页面可以看到 Client ID 与 Client Secret 的值。请不要关闭该窗口,因为接下来的步骤中会用到它们。你可以使用窗口左侧的菜单选项,随意探索应用的设置。

此处,除了得到 Client 密匙(Client ID 与 Client Secret 的值),我们还要完成的另一项重要任务,是在“合法重定向 URLs(Authorized Redirect URLs)”一栏填入合适的值。当客户端 App 试图刷新现有的访问令牌,用户无需通过 Web 浏览器重新登录,使用合法重定向 URL 即可。OAuth 流会自动使用该 URL 将 App 重定向。在正常的登录过程中,客户端 App 与 LinkedIn 服务器会交换该 URL,同时取得授权码与访问令牌。总之,该值不能为空,稍后会用来与服务器进行交换,因此必须定义它。

重定向 URL 不需要是真实存在的 URL,可以是任何以 “ https://” 开头的值。在此,笔者将其赋值如下。你可以将其改为任何你希望的值。

  https://com.appcoda.linkedin.oauth/oauth  

如果你使用了一个不同的 URL, 千万记得对后面出现的代码进行相应的修改。

在“OAuth 2.0”一节写入合法重定向 URL 后,必须点击添加按钮,保证将其加入 App 中。

t47_6_authorized_redirect_url

此外,记得点击屏幕底部的更新按钮。

至于有关访问权限的选项,保留基本选项即可,因为其完全满足本教程的需求。当然,你也可以选择更多权限,或在演示 App 准备就绪之后再做修改。请注意,如果 App 请求的最初权限遭到改动,用户必须重新登录以认可这些改动。

开始授权过程

现在,打开 Xcode 中的启动项目,我们即将开始实现,并最终完成 OAuth 2.0 流。不过,在开始之前,请选择项目导航栏(Project Navigator)中的 WebViewController.swift 文件,打开它。在该类的头部,你会看到两个名为 linkedInKey 与 linkedInSecret 的变量。你需要将之前从 LinkedIn 开发者网站得到的 Client ID 与 Client Secret 值分别赋值给这两个变量(简单的复制、黏贴即可)。

t47_7_assigned_keys

本步的主要目的,是准备好用来获取授权码的请求,并通过一个 Web 视图加载它。界面生成器(Interface Builder)中的 WebViewController 已经包含了一个 Web 视图,因此我们将以 WebViewController 类为基础构建视图。用于获取授权码的请求必须包含以下参数:

  • response_type:取值为恒定的标准值:code。
  • client_id:取值为来自 LinkedIn 开发者网站的 Client ID,之后会赋值给项目中的 linkedInKey 属性。
  • redirect_uri:取值为在前一节指定的合法重定向 URL。请确保在后面的代码段中填入相应的值。
  • state:取值为唯一的字符串,用于预防跨站请求伪造(CSRF)。
  • scope:取值为 App 请求的访问权限列表,以 URL 形式编码。

下面,介绍代码的具体实现。首先,在 WebViewController 类下创建一个名为 startAuthorization() 的新函数。该函数的第一个任务是根据上文的描述为请求参数赋值。

  func startAuthorization() {  
    // Specify the response type which should always be "code".
    let responseType = "code"

    // Set the redirect URL. Adding the percent escape characthers is necessary.
    let redirectURL = "https://com.appcoda.linkedin.oauth/oauth".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!

    // Create a random string based on the time interval (it will be in the form linkedin12345679).
    let state = "linkedin\(Int(NSDate().timeIntervalSince1970))"

    // Set preferred scope.
    let scope = "r_basicprofile"


}

请注意:不是简单地将合法重定向 URL 赋值给 redirectURL 变量。我们需要将 URL 中的特殊符号通过 URL 编码替换为百分比编码字符。因此,下面的链接:

  https://com.appcoda.linkedin.oauth/oauth  

将会转换为:

  https%3A%2F%2Fcom.appcoda.linkedin.oauth%2oauth  

(See more about URL-encoding here).

点此了解 URL 编码的更多信息。)

其次,state 变量必须包含一个唯一且莫测的字符串。在上面的代码中,我们将“linkedin”字符串与当前时间戳(自1970年以来的时间间隔)的整数部分进行联结,以此确保字符串的唯一性。你也可以生成随机字符,再将其附加到 state 字符串上。

最后,将 scope 赋值为“r_basicprofile”,后者与之前在 LinkedIn 开发者网站设定的 App 访问权限相匹配。当你设置访问权限时,请确保与官方文档中的规定一致。

Our next step is to compose the authorization URL. Note that the https://www.linkedin.com/uas/oauth2/authorization URL must be used for the request, which is already assigned to the authorizationEndPoint property.

下一步,创建授权 URL。请注意,URL https://www.linkedin.com/uas/oauth2/authorization 必须用于该请求,而该 URL 已经赋做 authorizationEndPoint 属性的值。

回到代码:

  func startAuthorization() {  
    ...

    // Create the authorization URL string.
    var authorizationURL = "\(authorizationEndPoint)?"
    authorizationURL += "response_type=\(responseType)&"
    authorizationURL += "client_id=\(linkedInKey)&"
    authorizationURL += "redirect_uri=\(redirectURL)&"
    authorizationURL += "state=\(state)&"
    authorizationURL += "scope=\(scope)"

    print(authorizationURL)
}

此处,笔者添加了打印命令,是为了让读者亲眼看到该请求最终是如何形成的。

最终,我们需要在 Web 视图中加载该请求。请记住,只有前文所述的请求配置得当,用户才能通过 Web 视图成功登录。否则,LinkedIn 将返回错误消息,导致无法进行下一步操作。

因此,请确保正确拷贝了 Client Key、Client Secret,以及统一的合法重定向 URL。

在 Web 视图中加载该请求只需短短几行代码:

  func startAuthorization() {  
    ...

    // Create a URL request and load it in the web view.
    let request = NSURLRequest(URL: NSURL(string: authorizationURL)!)
    webView.loadRequest(request)
}

在结束本节之前,我们必须调用上面的函数。可以通过 viewDidLoad(_: ) 函数进行调用:

  override func viewDidLoad() {  
    ...

    startAuthorization()
}

此时,你终于可以运行 App,测试其是否成功了。如果你根据笔者的指导配置正确,应该可以看到以下页面:

t47_2_user_sign_in

不过,先别急着登录 LinkedIn 账号,本节还有一部分工作未完成。然而,如果你看到了登录表格,说明你已经成功发送了获取授权码的请求。登录之后,LinkedIn 会向浏览器(在本例中,也即我们的 Web 视图)返回一个授权码。

除此之外,还会在控制台打印出 authorizationURL(授权 URL)字符串:

t47_8_authorization_request

Getting an Authorization Code

获取授权码

授权码请求函数准备就绪,且在 Web 视图中成功加载之后,我们可以继续执行 webView(:shouldStartLoadWithRequest:navigationType) 委托函数。在此函数中,我们会捕获来自 LinkedIn 的响应,并从中抽取出渴望已久的授权码。

包含授权码的响应如下所示:

  http://com.appcoda.linkedin.oauth/oauth?<strong>code=AQSetQ252oOM237XeXvUreC1tgnjR-VC1djehRxEUbyZ-sS11vYe0r0JyRbe9PGois7Xf42g91cnUOE5mAEKU1jpjogEUNynRswyjg2I3JG_pffOClk</strong>&state=linkedin1450703646  

因此,我们需要将该字符串分为多个部分,隔离出“code”的值。不过,有两点注意:其一,我们必须确保委托函数中的 URL 是我们感兴趣的。其二,必须确保授权码的确存在于该 LinkedIn 响应中。代码的实现如下:

  func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {  
    let url = request.URL!
    print(url)

    if url.host == "com.appcoda.linkedin.oauth" {
        if url.absoluteString.rangeOfString("code") != nil {

        }
    }

    return true
}

首先,通过请求参数获得该 URL。接着,检查 URL 的主机属性值以确保这是我们需要的 URL(也即在 LinkedIn 开发者网站设定的重定向 URL)。如果是,请求字符串中 “code” 所在的范围,以验证该 URL 是否真的包含授权码。如果返回不为空,则证明授权码的确存在。

将 URL 字符串分为多个部分并不难。为了简化步骤,笔者将该任务分为两步:

  func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {  
    let url = request.URL!
    print(url)

    if url.host == "com.appcoda.linkedin.oauth" {
        if url.absoluteString.rangeOfString("code") != nil {
            // Extract the authorization code.
            let urlParts = url.absoluteString.componentsSeparatedByString("?")
            let code = urlParts[1].componentsSeparatedByString("=")[1]

            requestForAccessToken(code)
        }
    }

    return true
}

除了上面新出现的两行代码,你也肯定注意到了对 requestForAccessToken(_: ) 函数的调用。这是我们将在下一部分实现的自定义函数。在此函数中,我们会用此处获得的授权码读取访问令牌。

如你所见,只差一步,我们就能使用 OAuth 2.0 流获取访问令牌了。此处扼要重述一下之前的步骤:首先,我们成功创建了获取授权码的请求。接着,作为授权过程的一部分,用户通过该请求连接他们的 LinkedIn 账号。最后,得到并抽取出授权码。

如果你想对目前的 App 进行测试,只需注释掉 requestForAccessToken(_: ) 函数的调用部分即可。你大可以在任意位置添加打印命令,从而深刻理解每个步骤的作用。

Requesting for the Access Token

信息结构,创作我们的信息就是如此,我已经很是在此基础上我们创作就是token整个结构就是做这些事情的

请求访问令牌

此前,我们与 LinkedIn 服务器的所有交流都是通过 Web 视图进行的。从现在起,我们将仅通过简便的 RESTful 请求(也即 POST 与 GET 请求)与服务器交流。更具体地说,我们会发起一个 POST 请求来获取访问令牌,之后再用 GET 请求获得用户主页的 URL。

话虽如此,现在要先创建在上一部分末尾提过的新的自定义函数:requestForAccessToken()。在此函数内部,我们将执行三个任务:

  1. 准备好 POST 请求的参数。
  2. 初始化并配置一个可变的 URL 请求对象(NSMutableURLRequest)。
  3. 实例化一个 NSURLSession 对象,继而执行一个数据任务请求。在得到恰当的响应之后,我们将访问令牌存储在用户默认的字典中。

准备 POST 请求参数

与获取授权码的请求准备相似,为了获得访问令牌,我们需要在请求中 POST 特定的参数与其对应的值。这些参数包括:

  • grant type: It’s a standard value that should always be: authorizationcode.
  • code: The authorization code acquired in the previous part.
  • redirect_uri: It’s the authorized redirection URL we’ve talked about many times earlier.
  • client_id: The Client Key value.
  • client_secret: The Client Secret Value.
  • grant type:取值为恒定的标准值:authorizationcode。
  • code:取值为在上一部分获得的授权码。
  • redirect_uri:取值为前面多次提到的合法重定向 URL。
  • client_id:取值为 Client Key 的值。
  • client_secret:取值为 Client Secret 的值。

在上一部分得到的授权码将在新函数中用作参数。首先,让我们为参数“grant type”与“redirecturi”赋值:

     func requestForAccessToken(authorizationCode:     String) {
    let grantType = "authorization_code"

    let redirectURL = "https://com.appcoda.linkedin.oauth/oauth".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}

其他所有参数的值 App 都已经知道了,因此,我们可以将其整合为一个字符串:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Set the POST parameters.
    var postParams = "grant_type=\(grantType)&"
    postParams += "code=\(authorizationCode)&"
    postParams += "redirect_uri=\(redirectURL)&"
    postParams += "client_id=\(linkedInKey)&"
    postParams += "client_secret=\(linkedInSecret)"
}

如果你曾用 NSMutableURLRequest 类创建过 POST 请求,那你一定知道 POST 参数无法以字符串的形式传送。它们必须转化为 NSData 对象,再赋值给请求的 HTTPBody 部分(后文会有解释)。因此,让我们按照要求转化 postParams:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Convert the POST parameters into a NSData object.
    let postData = postParams.dataUsingEncoding(NSUTF8StringEncoding)
}

准备请求对象

准备好 POST 参数之后,我们可以继续初始化并配置 NSMutableURLRequest 对象。初始化时会用到获取访问令牌所需的 URL( https://www.linkedin.com/uas/oauth2/accessToken) ,而后者已经赋值给 accessTokenEndPoint 属性。

  func requestForAccessToken(authorizationCode: String) {  
    ...    

    // Initialize a mutable URL request object using the access token endpoint URL string.
    let request = NSMutableURLRequest(URL: NSURL(string: accessTokenEndPoint)!)
}

Next, it’s time to “say” to the request object what kind of request we want to make, as well as to pass it the POST parameters:

接下来,告诉请求对象我们想要创建的请求类型,并传入 POST 参数:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Indicate that we're about to make a POST request.
    request.HTTPMethod = "POST"

    // Set the HTTP body using the postData object created above.
    request.HTTPBody = postData
}

根据 LinkedIn 文档,请求的 Content-Type 部分需要设置为 application/x-www-form-urlencoded:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Add the required HTTP header field.
    request.addValue("application/x-www-form-urlencoded;", forHTTPHeaderField: "Content-Type")
}

终于,请求对象的必要配置完成了。现在可以使用它了。

Performing the request

执行请求

我们将把用于获取访问令牌的请求实现为 NSURLSession 类的对象。通过该对象,创建一个数据任务请求,并在完成处理程序(completion handler)内部处理 LinkedIn 服务器的响应:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Initialize a NSURLSession object.
    let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in

    }

    task.resume()
}

如果请求成功,LinkedIn 服务器将会返回包含访问令牌的 JSON 数据。因此,我们的任务是得到该 JSON 数据,将之转化为字典对象,然后抽取出访问令牌。当然,这一切只有在返回的 HTTP 状态码是 200,也即请求成功时,才能进行。

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
        // Get the HTTP status code of the request.
        let statusCode = (response as! NSHTTPURLResponse).statusCode

        if statusCode == 200 {
            // Convert the received JSON data into a dictionary.
            do {
                let dataDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

                let accessToken = dataDictionary["access_token"] as! String
            }
            catch {
                print("Could not convert JSON data into a dictionary.")
            }
        }
    }

    task.resume()
}
我很好奇

此类操作可能抛出异常,将json 数据的倒换就是加载我们呢自身的SDK,接入信息就是从来不会有很多的考很多事情就是自己做起来用户就是会考 请注意,转化发生在一个 do-catch 语句内部,因为从 Swift 2.0 开始,此类操作可能抛出异常(并不存在错误参数)。在我们的演示 App 中,无需特别考虑出现异常的情况,因此可以向控制器发送一条信息,表示转化失败。如果一切运行顺利,我们就将 JSON 数据(闭包中的数据参数)转化为字典(dataDictionary 对象),之后就可以直接读取访问令牌。

接下来做什么呢?将字典保存在用户默认的字典中,然后移除视图控制器:

  func requestForAccessToken(authorizationCode: String) {  
    ...

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
        // Get the HTTP status code of the request.
        let statusCode = (response as! NSHTTPURLResponse).statusCode

        if statusCode == 200 {
            // Convert the received JSON data into a dictionary.
            do {
                ...

                NSUserDefaults.standardUserDefaults().setObject(accessToken, forKey: "LIAccessToken")
                NSUserDefaults.standardUserDefaults().synchronize()

                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.dismissViewControllerAnimated(true, completion: nil)
                })                
            }
            catch {
                print("Could not convert JSON data into a dictionary.")
            }
        }
    }

    task.resume()
}

注意,视图控制器会在主线程中移除。请永远牢记,与 UI 相关的改动必须发生在 App 的主线程中,而不是背景线程中。而上面显示的完成处理程序(闭包)则永远在背景线程中执行。

Our ultimate goal has been finally achieved! We managed to acquire the access token that will “unlock” several API features.

我们的终极目标终于完成啦!得到访问令牌之后,可以“解锁”许多 API 功能。

获得用户主页的 URL

接下来,我们将演示如何用访问令牌获得用户主页的 URL,并在 Safari 浏览器中打开它。然而,在此之前,让我们先讨论一点别的问题。当你启动 App 时,你有两个选择,如下图所示:

view-controller-signin

默认情况下,LinkedIn Sign In (LinkedIn 登录)按钮是启用的,而 Get my profile URL(获得我的主页 URL)按钮是禁用的。既然现在已经得到了访问令牌,我们需要启用第二个按钮,同时禁用第一个按钮。这要如何完成呢?

一种实现方式是使用委托模式,通过一个委托函数通知 ViewController 类:访问令牌已经得到,请启用第二个按钮。另一种方式是从 WebViewController 类中 Post 一个自定义通知(NSNotification 对象),在 ViewController 类中监听该通知。其实,两种方法都可以实现。但是,还有一种更为简单的方法三:在 ViewController 出现时,检查访问令牌是否存在于用户默认的字典中。如果存在,就禁用登录按钮,启用第二个按钮。否则,就保持不变。

此处,我们会在 ViewController 类中实现一个新的小函数来进行检查。请注意,我们还设置了第三个默认隐藏的按钮(也即 btnOpenProfile IBOutlet 属性)。当得到用户主页的 URL 时,该按钮就会变为可见,并以此 URL 字符串作为其标题(后文会有示例)。

Now, let’s define this new function:

现在,先来定义这个新函数:

  func checkForExistingAccessToken() {  
    if NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") != nil {
        btnSignIn.enabled = false
        btnGetProfileInfo.enabled = true
    }
}

我们会在 viewWillAppear(_: ) 方法中调用该函数:

  override func viewWillAppear(animated: Bool) {  
    super.viewWillAppear(animated)

    checkForExistingAccessToken()
}

之后,App 就能合理地启用或禁用 ViewController 中的两个按钮了。

接下来,让我们聚焦于 getProfileInfo(_: ) IBAction 方法。此方法会在 Get my profile URL(获得我的主页 URL)按钮被点击时执行。届时,我们可以向 LinkedIn 服务器发送 GET 请求,使用访问令牌获得用户主页的 URL。此处采用的方法与在上一部分创建获取访问令牌的请求时所用的方法非常相似。

现在,让我们从指定请求的 URL 字符串开始吧。请注意,当你不是很确定自己需要什么 URL,或者指定哪些参数时,大可以寻求官方文档的帮助。

  @IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
        // Specify the URL string that we'll get the profile info from.
        let targetURLString = "https://api.linkedin.com/v1/people/~:(public-profile-url)?format=json"
    }
}

此处,作为额外措施,我们再一次检查了访问令牌是否存在。通过 if-let 语句,如果访问令牌存在,我们便将其赋值给 accessToken 常量。而且,上面的 URL 会返给我们用户主页的 URL。不要忘记,在执行这类请求之前,必须获得适当的权限。在本演示案例中,我们已经获得了访问用户基本介绍信息的权限。

接下来,创建一个新的 NSMutableURLRequest 对象,并以“GET”方法作为理想的 HTTP 方法。此外,还需指定一个 HTTP 头字段,此处将用访问令牌为其赋值。

  @IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
        ...

        // Initialize a mutable URL request object.
        let request = NSMutableURLRequest(URL: NSURL(string: targetURLString)!)

        // Indicate that this is a GET request.
        request.HTTPMethod = "GET"

        // Add the access token as an HTTP header field.
        request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")        
    }
}

最后,再一次地,使用 NSURLSession 与 NSURLSessionDataTask 类创建该请求:

  @IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
        ...

        // Initialize a NSURLSession object.
        let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in

        }

        task.resume()
    }
}

如果请求成功(也即 HTTP 状态码为 200),闭包中的数据参数将会包含服务器返回的 JSON 数据。与之前一样,我们必须将此 JSON 数据转化为字典,才能最终抽取出用户主页的 URL 字符串。

  @IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
        ...

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
            // Get the HTTP status code of the request.
            let statusCode = (response as! NSHTTPURLResponse).statusCode

            if statusCode == 200 {
                // Convert the received JSON data into a dictionary.
                do {
                    let dataDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

                    let profileURLString = dataDictionary["publicProfileUrl"] as! String
                }
                catch {
                    print("Could not convert JSON data into a dictionary.")
                }
            }
        }

        task.resume()
    }
}

现在,回到之前提到过的一点内容:profileURLString 的值将会赋给 btnOpenProfile 按钮的标题,该按钮也会变成可见。还记得不?我们现在的工作都是在背景线程中进行的,因此,我们还需将其加入主线程中:

  @IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
        ...

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
            // Get the HTTP status code of the request.
            let statusCode = (response as! NSHTTPURLResponse).statusCode

            if statusCode == 200 {
                // Convert the received JSON data into a dictionary.
                do {
                    ...

                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        self.btnOpenProfile.setTitle(profileURLString, forState: UIControlState.Normal)
                        self.btnOpenProfile.hidden = false

                    })
                }
                catch {
                    print("Could not convert JSON data into a dictionary.")
                }
            }
        }

        task.resume()
    }
}

现在,运行 App,如果你成功得到了访问令牌,在点击 Get my profile URL(获得我的主页 URL)按钮后不久,你就能看到自己主页的 URL 显示在第三个按钮的位置。

t47_9_get_profile_url

在 Safari 浏览器查看主页

现在,通过使用访问令牌与 LinkedIn API,我们得到了用户主页的 URL。接下来就该验证其是否正确了。既然已将此 URL 设置为一个按钮的标题,最快的验证方法莫过于打开它了。具体的实现方法箱单简单,因此笔者也不必要多言:

  @IBAction func openProfileInSafari(sender: AnyObject) {
    let profileURL = NSURL(string: btnOpenProfile.titleForState(UIControlState.Normal)!)
    UIApplication.sharedApplication().openURL(profileURL!)
}

The last line above will trigger the appearance of Safari, which will load and display the profile webpage.

上面最后一行代码会触发 Safari 浏览器,后者会加载并展示用户的主页。

t47_10_open_profile

你可能已经发现,教程已经步入尾声,却仍然没有提及废除或刷新访问令牌的内容。其实原因如下:关于废除访问令牌,LinkedIn 并未提供任何相关的 API。因此,如果你需要停止 App 发送合法请求,最好的做法应该是从存储机制(数据库,用户默认设置等)中删除之。除此之外,一个访问令牌的有效期大约为60天(在笔者撰写本文之时,官网文档是如此规定的)。LinkedIn 建议,在此时间范围到期之前,刷新访问令牌。刷新的操作非常简单,你只需要从头进行验证与授权过程即可。刷新时,如果访问令牌有效,用户便无需再次输入登录信息,一切都会在后台进行,访问令牌会自动刷新,延迟有效期60天。然而,对于大多数 Web 应用,存在一个常见情况:后台刷新的一个基本前提,是用户已经登录了他们的 LinkedIn 账号,而对于 App 中的内部 Web 视图,这一条件无法满足。因此,在访问令牌快要到期之前,你很可能要让用户再走一遍登录流程。想要了解更多信息,可以点击 此处,查看“刷新访问令牌”一节。好了,说再见的时候到了。笔者希望本教程对你有所帮助,并成功向 LinkedIn 发送经过授权的请求。

作为参考,你可以从 GitHub 下载本案例 完整的 Xcode 项目文件

OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

相关 [oauth ios apps] 推荐:

如何通过 OAuth 2.0 使 iOS Apps 集成 LinkedIn 登录功能?

- - OneAPM 博客
社交网络早已成为人们日常生活的一部分. 其实,社交网络也是编程生活的一部分,大多数 App 必须通过某种方式与社交网络交互,传送或接收与用户相关的数据. 大多数情况下,用户需要登录某种社交网络,授权 App 代表自己进行请求. 目前,此类社交网络的种类非常丰富,以 Facebook 与 Twitter 最为常用.

PCworld评选2011年度最佳免费iOS Apps

- - 译言-电脑/网络/数码科技
著名媒体PCworld在11月12日评选出“2011年度最佳免费ios apps”,其中有. 苹果公司App Store上iphone和ipad的apps在今年超过了500万,而且其中一大部分是免费的——这使得它们获得了巨大的价值,因为许多都非常有用、创新,抑或相当有趣. 点击并看看今年最佳iphone和ipad上的免费apps.

OAuth的改变

- lyxint - 火丁笔记
去年我写过一篇《OAuth那些事儿》,对OAuth做了一些简单扼要的介绍,今天我打算写一些细节,以阐明OAuth如何从1.0改变成1.0a,继而改变成2.0的. 在OAuth诞生前,Web安全方面的标准协议只有OpenID,不过它关注的是验证,即WHO的问题,而不是授权,即WHAT的问题. 好在FlickrAuth和GoogleAuthSub等私有协议在授权方面做了不少有益的尝试,从而为OAuth的诞生奠定了基础.

理解OAuth 2.0

- - 阮一峰的网络日志
OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版. 本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为 RFC 6749. 为了理解OAuth的适用场合,让我举一个假设的例子. 有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来.

流行 iOS Apps 被发现将用户位置数据发送给第三方数据分析公司

- - 最新更新 – Solidot
GuardianApp 的安全研究人员 发现,数十款流行 iOS Apps 被发现会将用户位置数据发送给第三方数据分析公司. 这些应用都需要位置数据才能正常工作,它们是气象、交友或健身类应用,而与第三方公司分享数据可以为免费应用产生收入. 这些应用收集的数据包括低功耗蓝牙信标数据,GPS 经维度数据,Wi-Fi SSID 和 BSSID,部分应用还收集加速计,广告标识符,电池状态和蜂窝网络信息等.

oauth 认证心得

- 非狐外传 - python.cn(jobs, news)
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准. 简易来说,就是我们可以在某一个第三方服务器,如:新浪,豆瓣,在用户授权,并且不透漏密码等信息给我们的条件下,访问和修改用户的资源. oauth的项目主页为:http://oauth.net/ ,现在国内很多网站的开放平台都采用了Oauth方式来进行授权.

OAuth学习笔记

- 宋大妈 - FeedzShare
来自: 标点符 - FeedzShare  . 发布时间:2011年08月29日,  已有 2 人推荐. OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据.

Web Apps来袭

- - HTML5研究小组
如同历史上任何一次互联网基础标准的变化都会在随后几年中带来应用创新的大爆发一样,当HTML5在2011年逐渐被主流厂商所接受之后,围绕Web Apps领域的创新风暴正山雨欲来. 2012年1月12日,老牌传媒集团《金融时报》(Financial Times,以下简称FT)宣布收购为其开发移动Web App的研发公司Assanka ,这样,FT将不再以外包的形式雇佣Assanka为其打造移动Web App,而可以直接让它在内部进行开发.

OAuth 2.0 工作流程

- - 企业架构 - ITeye博客
原文链接:http://www-01.ibm.com/support/knowledgecenter/SSELE6_8.0.0.3/com.ibm.ammob.doc_8.0.0.3/config/concept/con_oauth20_workflow.html%23con_oauth20_workflow?lang=zh.