作者:Gabriel Theodoropoulos,原文鏈接,原文日期:2016-1-3
譯者:小鐵匠Linus;校對:千葉知風;定稿:小鍋
從很久以前開始,社交網絡就成為了我們每天生活的一部分了。同時,社交網絡也是我們程序員生活的一部分,絕大多數的應用都對進行了集成,用于接收和發送用戶的信息。在大多數情況下,用戶都被要求能在應用中綁定每個社交網絡,并且授權該應用能代表用戶發起請求。有很多這樣的社交網絡,比如 Facebook 和 Twitter 是使用比較頻繁的。另外,iOS 也對 Facebook 和 Twitter 有內置的支持。但是,對于大多數其他的社交網絡來說,開發者必須要配置一下才能成功地使得應用授權每個社交網絡,并能代表用戶發起請求。本教程中,我們將針對 LinkedIn 這樣的一個社交網絡演示如何使應用獲得授權,并能與服務器傳遞受保護數據。
有兩種方式可以針對 LinkedIn 授權 iOS 應用并根據 API 調用相應操作。第一種是使用 LinkedIn 支持的 OAuth 2.0 協議。第二種可行的方式是使用 LinkedIn 提供的 iOS SDK,這種方式和其他第三方的 SDK 一樣,要集成到工程并配置完畢才能使用。
在本教程中,我們只會關注第一種方法,也就是說,我們會使用 LinkedIn 和 OAuth 2.0 指南讓用戶在應用(不僅僅是 iOS 的應用)中登錄,并授權以提供與服務器的數據傳遞。LinkedIn iOS SDK 也是一個很好的選擇,但是我堅持使用 OAuth 是出于以下幾點原因:
老實說,我是被以下這種方式的交互給吸引的:在授權成功后,直接與服務器通過 REST API 調用來交互。
關于 Linnked In iOS SDK 的使用,在 LinkedIn 已經有了很詳細的教程,我不確定再寫一篇相同主題的教程會有什么好處。
在我看來,使用 LinkedIn iOS SDK 時會有一個缺點:必須在設備上安裝 LinkedIn 的官方應用,否則不能登錄和進行授權。這樣在需要獲取用戶帳戶信息,而用戶不想僅僅為了登錄而安裝 LinkedIn 官方應用時,就會產生問題。
關于 OAuth 2.0 協議,沒有什么可以多說的,你可以移步到該協議的官方網站查看詳情。簡而言之,為了成功登錄并授權,在本教程中我們會使用以下的步驟:
我們會在 LinkedIn 開發者網站創建一個應用。同時,我們會得到 Client ID 和 Client Secret,這兩個值會在后面的步驟中用的到。
我們會在 webview 中讓用戶登錄 LinkedIn 賬號。
通過上一步,添加一些必要數據后,讓 LinkedIn 服務器返回一個授權碼。
我們會交換授權碼以獲得 access token。
access token 是我們在使用 OAuth 時真正需要的。使用一個有效的 token,我們可以向 LinkedIn 服務器發起授權請求,并通過 get 或 post 請求獲取用戶的信息。
在我們行動之前,請確保你對 OAuth 2.0 的運行有一個基本的了解,和它的流程是什么。如果可以的話,建議看看其他資源以獲得更多幫助(比如這里,這里還有這里)。
說完了上面這些,讓我們繼續本教程的演示應用程序,然后直接過渡到實際的實現。我真的希望接下來我們要做的可以對你有些幫助。
LinkedIn 官方文檔有以下這些鏈接可以作為參考:
演示應用總覽
我們將要實現的演示應用是由兩個視圖控制器構成的。第一個視圖控制器(默認的 ViewController 類)里會有三個按鈕:
一個叫 LinkedIn Sign In 的按鈕,用來初始化登錄以及授權過程。
一個叫 Get my profile URL 的按鈕,使用 access token 發起授權請求后獲得 profile URL。
一個會顯示 profile URL 的按鈕,當點擊它的時候會跳轉到 Safari 打開相應的網頁。
默認情況下,只有第一個按鈕可以被點擊。實際上,只要沒有獲取到 access token,它都是可以被點擊的。在任何其他情況下,第一個按鈕不能點擊的時候,第二個按鈕就可以被點擊了。第三個按鈕是隱藏的,但是當使用第二個按鈕獲取到 profile URL 的時候,第三個按鈕就會顯示出來了。
第二個視圖控制器會包含一個 webview。我們可以使用 webview 來登錄 LinkedIn 的帳號,并完成認證授權成功。這個視圖控制器會在獲取到授權所需的 access token 后就會消失(dismiss)。
與通常一樣,我們不從頭開始做項目,可以從這里下載一個初始的工程,然后在這個工程上開始。
我們會將注意力主要集中在如何獲得 access token 上。我們也會根據 OAuth 2.0 協議和 LinkedIn 指南上的內容一步步操作。一旦我們獲取到了 access token,就可以使用它來向 LinkedIn 請求授權了。一旦授權成功,我們就可以在 Safari 里通過我上面談到的第三個按鈕顯示用戶信息了。
在開始行動之前,確保你下載了之前的初始工程。如果沒問題的話,就可以繼續下去了。
LinkedIn 開發者網站 - 創建一個應用
在我們的演示應用中,實現 OAuth 2.0 登錄的第一步就是在 LinkedIn 開發者網站上創建一個應用。我們需要先訪問這個鏈接 如果你還未登錄 LinkedIn 的話,網站會提示你要先進行登錄。
值得注意的是,如果你使用 Safari,并且在以下步驟中遇到任何問題,那就換另一個瀏覽器試試。我使用的是 Chrome 瀏覽器。
點擊網頁的 My Applications 區域,然后你會看到一個叫 Create Application 的黃色按鈕,點擊它就可以創建我們之后要在 iOS APP 中進行連接的應用。
接下來出現的表單中的每一項都必須填寫。不用擔心它需要你填寫的公司名字或應用 logo,提交一些假數據即可。接著勾選“已閱讀使用條款”,點擊提交按鈕。每一項帶有紅色星星的都必須填寫,否則是不能繼續下去的。下圖是個例子:

下一個界面就是我們需要的了:
正如你在上面的截圖看到的,我們可以在這里找到我們需要的 Client ID 和 Client Secret。先不要關閉當前頁面,我們下一步還要用到。同時,可以看看菜單選項窗口的左側中其他的各種應用設置。
在這里有一個非常重要的任務(除了簡單地訪問客戶端密鑰外),那就是把 Authorized Redirect URLs 這個表單給填上。當客戶端應用嘗試刷新已經存在的 access token 時,就需要 Authorized Redirect URL,且用戶不需要再像一開始那樣在網頁瀏覽器里登錄帳號。OAuth 會根據該 URL 自動重定向客戶端應用。對于一個正常的登錄操作來說,當我們在獲取授權碼和 access token 時,重定向的 URL 會在應用和服務器之間交換。不管怎樣,這個 URL 必須存在并在之后和服務器端進行交換。
重定向的 URL 不需要是真實存在的 URL。它可以是任何以“https://”開頭的值。我在這里設置的值如下,你也可以設置你想要的 URL:
https://com.appcoda.linkedin.oauth/oauth
假如你使用的 URL 不是上面這個,那么,不要忘記在接下來的代碼段里替換成你使用的 URL。
一旦 authorized redirect URL 在 OAuth 2.0 中填寫完畢,請務必點擊 Add 按鈕以添加到當前的應用中。

同時,不要忘記點擊屏幕下方的 Update 按鈕。
關于默認的應用權限,本教程只是勾選了 “basic profile” 選項。當然,你也可以勾選更多的權限,或者在完成演示應用后再看看是否有需要添加的權限。值得注意的是,如果權限改變之后,用戶必須重新登錄來授權這些新的權限。
初始化授權過程
現在回到 Xcode 中的工程,我們將要實現 OAuth 2.0。在開始之前,打開 WebViewController.swift 文件。在該文件的最上面,你會發現兩個變量分別叫 linkedInKey 和 linkedInSecret。你必須將應用的 Client ID 和 Client Secret 分別賦值給這兩個變量(從 LinkedIn 開發者網站上復制拷貝即可)。
這一步的主要目的是通過加載 webview 獲得授權碼。Interface Builder 里的 WebViewController 自帶一個 webview,因此我們可以直接在 WebViewController 的類中寫相應的操作了。獲取授權碼的請求必須包含以下這些參數:
- response_type:填上 code 即可。
- client_id:LinkedIn 開發者網站上可以拿到的 Client ID,也就是已經在 linkedInKey 變量里保存的。
- redirect_uri:上一部分設置的重定向 URL 就填在這里。請確保復制粘帖沒有出錯。
- state:一個獨一無二的字符串,避免跨站請求偽造(cross-site request forgery (CSRF))。
- scope:應用請求的權限列表。
回到代碼上來,在 WebViewController
類中創建一個用來發送請求的方法,并將其命名為 startAuthorization()
。接著,將前面說到的請求參數都在這個方法中指定好,代碼如下:
func startAuthorization() {
// 指定響應類型應該是 "code".
let responseType = "code"
// 設置重定向 URL。增加的百分比 escape characthers 是必要的。
let redirectURL = "https://com.appcoda.linkedin.oauth/oauth".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
// 創建一個基于時間間隔的隨機字符串 (它將是這樣的 linkedin12345679).
let state = "linkedin\(Int(NSDate().timeIntervalSince1970))"
// 設置首選的范圍。
let scope = "r_basicprofile"
}
值得注意的是,這里我們為 redirectURL
變量賦值了 authorized redirect URL 的值,同時,我們也用 percent encoding characters 來替換其中的特殊字符。這意味著,
https://com.appcoda.linkedin.oauth/oauth
會被轉化成這樣:
https%3A%2F%2Fcom.appcoda.linkedin.oauth%2oauth
(關于 URL-encoding 詳情可見這里)
接下來,state
變量必須是一個獨一無二的,且不容易被猜到的字符串。上面的代碼中,我們把 “linkedin” 字符串和當前時間戳(自 1970 年以來的時間間隔)的整數部分結合起來,以確保這個值是唯一的。另外一種方式就是產生隨機的字符追加到 state
變量后,如果你想這樣做的話也是可行的。
最后,scope
變量會保存 “r_basicprofile”,這個字符串與我們在 LinkedIn 開發者網站上設置的權限相同。一旦你修改了應用的權限,確保這個字符串與官方文檔中描述權限的內容相同。
我們下一步要做的就是 compose
這個 authorization URL。值得注意的是,https://www.linkedin.com/uas/oauth2/authorization
這個 URL 是必須要在請求中使用的,在之前代碼中賦值給了 authorizationEndPoint
屬性。
再看一下之前的代碼:
func startAuthorization() {
...
// 創建授權的 URL 字符串.
var authorizationURL = "\(authorizationEndPoint)?"
authorizationURL += "response_type=\(responseType)&"
authorizationURL += "client_id=\(linkedInKey)&"
authorizationURL += "redirect_uri=\(redirectURL)&"
authorizationURL += "state=\(state)&"
authorizationURL += "scope=\(scope)"
print(authorizationURL)
}
其中,添加了 print
語句,可以在控制臺看看最終構成的請求是什么樣的。
最后,就是在我們的 webview 里加載這個請求。如果前面這些屬性都設置正確的話,用戶就能在 webview 里進行登錄了;否則的話,LinkedIn 就會返回錯誤信息,并且無法繼續下去了。因此,確保你把 Client Key 、Secret 和 authorized redirect URL 都設置正確。
在 webview 中加載請求,只需要幾行代碼:
func startAuthorization() {
...
// 創建一個 URL 請求和負載的 web 視圖。
let request = NSURLRequest(URL: NSURL(string: authorizationURL)!)
webView.loadRequest(request)
}
在結束這一部分之前,我們必須調用上面的這個應該發生在 viewDidLoad(_: )
中的方法:
override func viewDidLoad() {
...
startAuthorization()
}
這時候,你就可以試著運行一下這個應用來測試了。如果你都設置正確的話,你就能看到以下的界面了:
這時候還不要登錄你的 LinkedIn 帳號,因為我們還沒有一些操作沒有完成。盡管如此,假如你看到登錄界面,然后獲取了授權碼,并成功登錄的話,LinkedIn 會跳回瀏覽器(也就是我們的 web view)。
下圖是我們在控制臺打印的 authorizationURL
變量的值:
獲取授權碼
我們可以通過 webView(:shouldStartLoadWithRequest:navigationType)
方法實現加載之前已經準備好的請求。在該方法中,我們可以“抓取”到 LinkedIn 的 response,并從中提取授權碼。
實際上,一個包含授權碼的 response 大概是以下這樣的:
http://com.appcoda.linkedin.oauth/oauth?code=AQSetQ252oOM237XeXvUreC1tgnjR-VC1djehRxEUbyZ-sS11vYe0r0JyRbe9PGois7Xf42g91cnUOE5mAEKU1jpjogEUNynRswyjg2I3JG_pffOClk&state=linkedin1450703646
這也就意味著,我們需要將這個字符串分割開來,并獲取其中 “code” 的值。兩件事情要注意:第一,我們必須確定該方法中的 URL 是我們需要的,第二,我們必須確定 response 中有授權碼。下面是代碼:
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
}
首先,我們通過 request
參數獲取 URL。接著,我們先檢查了 URL 的 host
屬性是否為 LinkedIn 的。假如是我們需要的話,接著確保 URL 中包含 “code” 單詞,如果沒有授權碼的話就會返回 nil。
將字符串分割開來并不困難。為了簡單化,我們把任務分成了兩步:
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 {
// 提取授權 code。
let urlParts = url.absoluteString.componentsSeparatedByString("?")
let code = urlParts[1].componentsSeparatedByString("=")[1]
requestForAccessToken(code)
}
}
return true
}
除了新添加的兩行代碼,你也可以看到另外調用了一個叫 requestForAccessToken(_: )
的方法。這個一個新的自定義方法,它將在下一部分內容中進行實現。在該方法的調用中,我們會通過授權碼獲取 access token。
正如你看到的,我們現在離使用 OAuth 2.0 獲取 access token 只有一步之遙了。到目前為止,我們已經創建了獲取授權碼的請求,授權過程中允許用戶登錄,并最終提取授權碼。
如果你想在現階段運行應用的話,只要將 requestForAccessToken(_: )
這行代碼注釋掉就可以成功運行了。在你需要添加打印命令時不要猶豫,這樣你就能很清楚的知道每一步發生了什么。
請求 access token
目前為止,所有與 LinkedIn 的通訊都是通過 web view 實現的。從現在開始,我們將只使用 RESTful 請求(POST 和 GET 請求)來通訊。準確點說,我們會使用一個 POST 請求獲取 access token,再使用一個 GET 請求訪問用戶信息。
是時候講講上一部分提到的 requestForAccessToken()
方法了。我們會在該方法中執行三個不同的任務:
準備 POST 請求的參數。
初始化并配置好一個可變的 URL 請求對象(
NSMutableURLRequest
)。實例化
NSURLSession
對象,并執行一個 data task request。假如我們獲取到了正確的 response,我們也會將 access token 添加到 UserDefaults 的字典中。
準備 POST 請求的參數
與獲取授權碼的準備過程類似,我們也需要一些特定的參數來請求 access token。這些參數有:
grant_type:填寫
authorization_code
即可。code:上一部分獲取到的授權碼。
redirect_uri:我們之前就談論過的 authorized redirection URL。
client_id:Client Key 的值。
client_secret:Client Secret 的值。
上一部分我們獲取到的授權碼是由方法的輸入參數給定的。我們先來指定 “grant_type” 和 “redirect_uri” 參數:
func requestForAccessToken(authorizationCode: String) {
let grantType = "authorization_code"
let redirectURL = "https://com.appcoda.linkedin.oauth/oauth".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}
其他所有參數都是“已知的”,將它們組合成一個字符串:
func requestForAccessToken(authorizationCode: String) {
...
// 設置 POST 參數。
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) {
...
// 轉換 POST 參數 為一個 NSData 對象.
let postData = postParams.dataUsingEncoding(NSUTF8StringEncoding)
}
配置 URL 請求對象
準備好 POST 參數之后,我們可以初始化并配置 NSMutableURLRequest
對象了。使用 URL 獲取 access token 時,NSMutableURLRequest
對象的初始化就會發生了,且獲取到的 access token 會賦值給 accessTokenEndPoint
屬性。
func requestForAccessToken(authorizationCode: String) {
...
// 使用 access token endpoint URL string 初始化一個可變的 URL 請求對象
let request = NSMutableURLRequest(URL: NSURL(string: accessTokenEndPoint)!)
}
接下來,是時候構建我們想要的 request
對象了,同時也要傳入 POST 參數:
func requestForAccessToken(authorizationCode: String) {
...
// 標明我們將要發起的請求類型為 POST 請求
request.HTTPMethod = "POST"
// 將之前創建的 postData 對象設置給 HTTP body
request.HTTPBody = postData
}
根據 LinkedIn 的文檔,請求的 Content-Type
應該被設置成使用 application/x-www-form-urlencoded
的值:
func requestForAccessToken(authorizationCode: String) {
...
// 為請求添加 HTTP header
request.addValue("application/x-www-form-urlencoded;", forHTTPHeaderField: "Content-Type")
}
就這樣,請求對象的設置就完成了。
執行請求
我們會使用 NSURLSession
類的實例來執行獲取 access token 的請求。我們會發起一個 data task request,并在完成處理器(completion handler)里處理 LinkedIn 服務器發回的 response:
func requestForAccessToken(authorizationCode: String) {
...
// 初始化 NSURLSession 對象
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
}
task.resume()
}
如果請求成功的話,LinkedIn 服務器就會返回包含 access token 的 JSON 數據。因此,我們的任務就是將獲取到的 JSON 數據轉換成 dictionary
對象,并提取出 access token。當然,只有在 HTTP 狀態碼返回 200 時,才意味著是一個成功的請求。
func requestForAccessToken(authorizationCode: String) {
...
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// 獲取請求的 HTTP 狀態碼
let statusCode = (response as! NSHTTPURLResponse).statusCode
if statusCode == 200 {
// 將 JSON 數據轉換成字典
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()
}
值得注意的是,整個過程包含在 do-catch
語句里,因為從 Swift 2.0 開始該語句支持拋出異常(沒有 error 參數)了。在我們的演示應用中,我們不需要考慮異常的情況,因此我們只需要在控制臺顯示一條消息即可。假如都正常運行的話,我們會將 JSON 數據(閉包中的 data
參數)轉換成字典(dataDictionary
對象),接著我們就能直接訪問 access token了。
接下來干什么呢?簡單地將其保存在 UserDefaults 字典中,并 dismiss 視圖控制器:
func requestForAccessToken(authorizationCode: String) {
...
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// 獲取請求的 HTTP 狀態碼
let statusCode = (response as! NSHTTPURLResponse).statusCode
if statusCode == 200 {
// 將 JSON 數據轉換成字典
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()
}
值得注意的是,在主線程中視圖控制器已經被 dismiss 了。任何 UI 相關的操作都必須在應用的主線程進行,而不是在后臺(background),這是必須牢記的。如上面的閉包(closures)是在后臺執行的。
我們最終的目的已經完成了。我們設法獲得 access token 來“解鎖”幾個 API 特性。
獲取用戶信息 URL
我們會通過訪問用戶信息 URL(我們會在 Safari 中打開)來展示 access token 的簡單用法。但是,在我們動手之前,我們先來討論一下其它的東西。如下圖所示,當你開啟應用時,你會看到以下兩個按鈕:
默認情況下,LinkedIn Sign In 按鈕是可點擊的,Get my profile URL 按鈕是禁用的。既然我們已經獲得了 access token,那么第二個按鈕就需要是可用的,并使得第一個按鈕不可用。我們該怎么實現這個功能呢?
一種方法是可以使用委托模式,通過委托方法我們可以通知 ViewController
類 access token 已經被獲取了,所以讓第二個按鈕啟用。另外一種方法是從 WebViewController
類里 post 自定義的通知(NSNotification
),并在 ViewController
里觀察。這兩種方法都能很好的實現這個功能,但是還有第三種方法:我們在 ViewController
里檢查 UserDefaults 字典中是否存在 access token,如果存在的話,我們將禁用登錄按鈕,并讓第二個按鈕可用。否則的話,我們就讓界面保留成上圖那樣子。
我們會在 ViewController
類中創建一個方法用來檢查。值得注意的是,還有第三個按鈕(btnOpenProfile
IBOutlet 屬性),它默認是隱藏的。當我們獲取到用戶信息 URL 時,它就會可見了,因為我們會將 URL 字符串設置成它的標題(我們過會會聊到)。
現在,讓我們來定義這個行方法:
func checkForExistingAccessToken() {
if NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") != nil {
btnSignIn.enabled = false
btnGetProfileInfo.enabled = true
}
}
我們會在 viewWillAppear(_: )
中調用這個方法:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
checkForExistingAccessToken()
}
從現在開始,這個應用已經能在 ViewController
界面中正常啟用和禁用兩個按鈕了。
現在,讓我們將注意力關注到 getProfileInfo(_: )
這個 IBAction 方法。Get my profile URL 按鈕被點擊時,這個方法會被調用。這時,我們會使用 access token 向 LinkedIn 服務器發起一個 GET 請求來獲取用戶信息 URL。這一步的操作與之前準備發起請求獲取 access token 類似。
因此,讓我們從請求的 URL 字符串入手吧。值得注意的是,如果你不知道你需要什么 URL 或 參數,你就應該去官方文檔的指南里去尋找。
@IBAction func getProfileInfo(sender: AnyObject) {
if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
// 為 targetURLString 賦值用戶信息對應的 URL 字符串
let targetURLString = "https://api.linkedin.com/v1/people/~:(public-profile-url)?format=json"
}
}
值得注意的是,我們再次檢查 access token 是否真的存在,將其作為一個額外的措施。通過使用 if-let
語句,將其賦值給 accessToken
常量。而且,以上的 URL 就會保存用戶信息 URL。不要忘記在發起任何請求之前都要向用戶申請權限。在我們的例子中,我們只是請求基本的信息。
我們接著創建一個 NSMutableURLRequest
對象,我們將設置“GET”作為所需的 HTTP 方法。另外,我們還要指定 HTTP header,即是 access token。
@IBAction func getProfileInfo(sender: AnyObject) {
if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
...
// 初始化一個可變的 URL 請求對象
let request = NSMutableURLRequest(URL: NSURL(string: targetURLString)!)
// 標明我們將要發起的請求類型為 GET 請求
request.HTTPMethod = "GET"
// 為 HTTP header 添加 access token
request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
}
}
最后,再次使用 NSURLSession
和 NSURLSessionDataTask
這兩個類:
@IBAction func getProfileInfo(sender: AnyObject) {
if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
...
// 初始化 NSURLSession 對象
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
}
task.resume()
}
}
如果請求成功(HTTP 狀態碼 = 200)的話,那么閉包中的 data
參數會包含服務器返回的 JSON 數據。正如我們之前實現的,我們必須將 JSON 數據轉換成字典,并最終獲取用戶信息 URL 字符串。
@IBAction func getProfileInfo(sender: AnyObject) {
if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") {
...
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// 獲取請求的 HTTP 狀態碼
let statusCode = (response as! NSHTTPURLResponse).statusCode
if statusCode == 200 {
// 將 JSON 數據轉換成字典
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") {
...
// 發起請求
let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
// 獲取請求的 HTTP 狀態碼
let statusCode = (response as! NSHTTPURLResponse).statusCode
if statusCode == 200 {
// 將 JSON 數據轉換成字典
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()
}
}
點擊 Get my profile URL 按鈕后,成功獲取 access token 后,就會在第三個按鈕上顯示 URL。
Safari 中顯示頁面
我們已經通過 access token 和 LinkedIn API 獲取到了用戶信息 URL,現在是時候驗證它是否正確了。因為我們將 URL 設置為按鈕的標題了,所以最快的驗證方法就是點擊按鈕來打開 URL 對應的頁面。這個實現也是很簡單的,所以我就不細講了,直接上代碼:
@IBAction func openProfileInSafari(sender: AnyObject) {
let profileURL = NSURL(string: btnOpenProfile.titleForState(UIControlState.Normal)!)
UIApplication.sharedApplication().openURL(profileURL!)
}
以上的最后一行代碼會打開 Safari,并加載和顯示對應頁面。
總結
你可能已經注意到了,本教程已經到了尾聲,但是我還沒有講如何廢除(revoke)和刷新(refresh)access token。有幾個原因要講一下:盡管廢除 access token 也是很重要的,但是 LinkedIn 并沒有提供相應的 API。因此,一旦你想讓你的應用停止發起授權請求時,最好是從你的存儲(數據庫,用戶默認等)里直接刪除 access token。還有,一個 access token 的有效期是 60 天(這是我在寫這篇文章時官方文檔里的說法)。LinkedIn 建議在過期前刷新 access token,而且刷新的過程也只是重新驗證和授權。如果 access token 當前是有效的話,用戶不需要重復登錄,所有一切操作都會發生在后臺,且 access token 也會被自動刷新,有效期為新的 60 天。然而,在一般情況下,上面這種方式主要適用于 web 應用,而不是 iOS 應用中。后臺自動刷新的基本先決條件是用戶已經登錄了他們的 LinkedIn 賬號,很顯然這對于嵌套在應用內 webview 并不適用。因此,假如 access token 快過期了,你就需要讓用戶再次登錄了。更多的信息可以看看這里“Refresh your Access Tokens”部分。
就講這么多了。我希望你能從本教程中獲得一些幫助,并最終設法讓你也能完成 LinkedIn 的授權請求。
你可以從 GitHub 上下載完整的 Xcode 項目供參考。
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg。