社交網(wǎng)絡(luò)早已成為人們?nèi)粘I畹囊徊糠侄愀臁F鋵?shí)蜓洪,社交網(wǎng)絡(luò)也是編程生活的一部分,大多數(shù) App 必須通過某種方式與社交網(wǎng)絡(luò)交互坯苹,傳送或接收與用戶相關(guān)的數(shù)據(jù)蝠咆。大多數(shù)情況下,用戶需要登錄某種社交網(wǎng)絡(luò)北滥,授權(quán) App 代表自己進(jìn)行請求刚操。
目前,此類社交網(wǎng)絡(luò)的種類非常豐富再芋,以 Facebook 與 Twitter 最為常用菊霜。而且,iOS 系統(tǒng)內(nèi)置了對(duì)這兩款社交網(wǎng)絡(luò)的支持济赎。然而鉴逞,對(duì)于其他類型的社交網(wǎng)絡(luò)记某,開發(fā)者必須投入更多的努力,以使 App 獲得授權(quán)訪問這些社交網(wǎng)絡(luò)构捡,繼而運(yùn)行經(jīng)過授權(quán)的合法請求液南。LinkedIn 就是這樣一種社交網(wǎng)絡(luò),在本教程中勾徽,我們會(huì)了解如何為 App 授權(quán)滑凉,使之與 LinkedIn 的服務(wù)器交換受保護(hù)的數(shù)據(jù)。
為 iOS App 授權(quán)以訪問 LinkedIn喘帚,并根據(jù)后者提供的 API 運(yùn)行特定的操作畅姊,可以通過兩種方法實(shí)現(xiàn)。方法一:使用 LinkedIn 支持的 OAuth 2.0 協(xié)議吹由。方法二:使用 LinkedIn 提供的 iOS SDK若未。與所有第三方 SDK 一樣,LinkedIn 提供的 SDK 必須集成到項(xiàng)目中倾鲫,經(jīng)過合理的配置才能使用粗合。
在本文中,我們將僅專注于第一種方法乌昔。因此舌劳,我們將學(xué)習(xí) LinkedIn 與 OAuth 2.0 指南中與此相關(guān)的指定內(nèi)容,包括讓用戶通過 App(是任何 App玫荣,而不僅僅是 iOS 系統(tǒng))登錄 LinkedIn 并為 App 授權(quán)執(zhí)行進(jìn)一步請求的必要流程。盡管 LinkedIn iOS SDK 也是很好的選擇大诸,但筆者更喜歡 OAuth 方法捅厂,原因有三:
- 筆者個(gè)人更偏愛此類任務(wù):使用 REST API 調(diào)用與服務(wù)器進(jìn)行直接的交流,以順利完成授權(quán)過程资柔。
- LinkedIn 網(wǎng)站中有關(guān) LinkedIn iOS SDK 的介紹相當(dāng)明確詳盡焙贷,因此筆者認(rèn)為基于相同主題寫的教程恐怕益處不大。
- 筆者認(rèn)為贿堰,使用 LinkedIn iOS SDK 時(shí)存在一個(gè)缺陷:官方的 LinkedIn App 必須已經(jīng)安裝在設(shè)備中辙芍,否則登錄與授權(quán)過程就無法完成。如果某個(gè) App 需要獲得用戶的 LinkedIn 主頁信息羹与,但用戶并不想安裝 LinkedIn 官方應(yīng)用故硅,就會(huì)造成不便。
關(guān)于 OAuth 2.0 協(xié)議纵搁,能說的實(shí)在太多了吃衅。讀者最好還是登錄官網(wǎng)仔細(xì)研讀一下。簡而言之腾誉,為了成功完成登錄與授權(quán)過程徘层,本教程將會(huì)遵循以下步驟:
- 必要地峻呕,我們將在 LinkedIn 開發(fā)者網(wǎng)站創(chuàng)建一個(gè)新的 App。從而得到完成后續(xù)過程必備的兩個(gè)重要密匙(Client ID 與 Client Secret)趣效。
- 通過一個(gè) Web 視圖瘦癌,讓用戶登錄其 LinkedIn 賬戶。
- 根據(jù)以上所得跷敬,再加上一些必要數(shù)據(jù)讯私,向 LinkedIn 服務(wù)器索取授權(quán)碼。
- 與一個(gè)訪問令牌交換授權(quán)碼干花。
訪問令牌是與 OAuth 交互的必要條件妄帘。通過一個(gè)有效的令牌,我們便能向 LinkedIn 服務(wù)器發(fā)送經(jīng)過授權(quán)的請求池凄。并根據(jù) App 的性質(zhì)抡驼,“get”或“post” 數(shù)據(jù)到用戶的主頁。
在繼續(xù)閱讀之前肿仑,請確保你理解 OAuth 2.0 的工作原理致盟,以及它的流(flow)。如果必要尤慰,閱讀其他資源以獲取更多信息(比如這兒馏锡,這兒和這兒)。
說了這么多伟端,讓我們進(jìn)入正題杯道,介紹本教程的演示應(yīng)用,然后進(jìn)入具體實(shí)現(xiàn)责蝠。筆者相信党巾,我們將要學(xué)習(xí)的內(nèi)容是趣味無窮的。
作為參考霜医,以下是 LinkedIn 官方文檔的鏈接:
演示 App 概覽
我們在本教程中將要實(shí)現(xiàn)的演示 App 由兩部分視圖控制器組成:第一個(gè)(默認(rèn)的 ViewController 類)只包含三個(gè)按鈕:
- 一個(gè)名為 LinkedIn Sign In(LinkedIn 登錄)的按鈕齿拂,用于啟動(dòng)登錄與授權(quán)流程。
- 一個(gè)名為 Get my profile URL(獲得我的主頁 URL)的按鈕肴敛,用于執(zhí)行一個(gè)經(jīng)過授權(quán)的請求署海,使用訪問令牌獲得用戶主頁的 URL。
- 一個(gè)展示主頁 URL 的按鈕医男,點(diǎn)擊之后會(huì)在 Safari 中打開用戶的 LinkedIn 主頁砸狞。
默認(rèn)情況下,只會(huì)啟用第一個(gè)按鈕镀梭。實(shí)際上趾代,只要還未獲得訪問令牌,該按鈕就會(huì)一直可用丰辣。在其他情況下撒强,第一個(gè)按鈕會(huì)被禁用禽捆,同時(shí)啟用第二個(gè)按鈕。第三個(gè)按鈕是隱藏的飘哨,只有當(dāng)?shù)玫剑ㄍㄟ^第二個(gè)按鈕)用戶主頁的 URL 時(shí)胚想,才會(huì)可見。
第二個(gè)視圖控制器會(huì)包含一個(gè) Web 視圖芽隆。通過該試圖浊服,你可以登錄 LinkedIn 賬戶,這樣認(rèn)證與授權(quán)過程才能成功進(jìn)行胚吁。當(dāng)獲得用于向 LinkedIn 發(fā)送合法請求的訪問令牌后牙躺,該視圖控制器就會(huì)被移除。
與往常一樣腕扶,我們不需要從頭開始創(chuàng)建項(xiàng)目孽拷。你可以下載一個(gè)啟動(dòng)項(xiàng)目,在此基礎(chǔ)上繼續(xù)搭建半抱。
基本上脓恕,我們的主要努力將專注于獲取訪問令牌。我們會(huì)遵循 OAuth 2.0 協(xié)議以及 LinkedIn 指南的指定窿侈,一步一步地完成所有必備流程炼幔。獲得訪問令牌之后,我們會(huì)繼續(xù)解釋如何向 LinkedIn 發(fā)送合法請求史简,以獲得授權(quán)用戶公共主頁的 URL乃秀。成功得到 URL 之后,我們會(huì)請用前面提到的第三個(gè)按鈕圆兵,將主頁內(nèi)容顯示在 Safari 瀏覽器中跺讯。
在你繼續(xù)閱讀之前,請確保已經(jīng)下載啟動(dòng)項(xiàng)目衙傀,打開它并熟悉它的。準(zhǔn)備就緒之后萨咕,請繼續(xù)往下看统抬。
LinkedIn 開發(fā)者網(wǎng)站—— 創(chuàng)建新的 App
實(shí)現(xiàn) OAuth 2.0 登錄流程的第一步,是在 LinkedIn 開發(fā)者網(wǎng)站創(chuàng)建一個(gè)新的 App 記錄危队。為此聪建,你僅需訪問此鏈接。如果你還沒有登錄 LinkedIn 主頁茫陆,你將收到提示以完成登錄操作金麸。
注意:如果你在下面的步驟中使用 Safari 出現(xiàn)問題,請選擇其他瀏覽器簿盅。我使用的是 Chrome 瀏覽器挥下。
登錄之后揍魂,找到網(wǎng)站“我的應(yīng)用(My Applications)”部分,你會(huì)發(fā)現(xiàn)一個(gè)名為“創(chuàng)建應(yīng)用(Create Application)”的黃色按鈕棚瘟。點(diǎn)擊它開始創(chuàng)建新的應(yīng)用现斋,之后我們會(huì)將它與 iOS App 進(jìn)行聯(lián)結(jié)。
在接下來出現(xiàn)的表格中偎蘸,填寫所有欄目庄蹋。如果需要填寫公司名稱或上傳應(yīng)用 logo,不用擔(dān)心迷雪,輸入一些虛假信息也可限书。之后,接受使用條款章咧,點(diǎn)擊提交按鈕倦西。請一定要在帶紅色星號(hào)的欄目中輸入內(nèi)容,否則你將無法繼續(xù)慧邮。以下為示例:
我們的目標(biāo)是抵達(dá)下一個(gè)頁面:
如你在上面的截圖中所見调限,在此頁面可以看到 Client ID 與 Client Secret 的值。請不要關(guān)閉該窗口误澳,因?yàn)榻酉聛淼牟襟E中會(huì)用到它們耻矮。你可以使用窗口左側(cè)的菜單選項(xiàng),隨意探索應(yīng)用的設(shè)置忆谓。
此處裆装,除了得到 Client 密匙(Client ID 與 Client Secret 的值),我們還要完成的另一項(xiàng)重要任務(wù)倡缠,是在“合法重定向 URLs(Authorized Redirect URLs)”一欄填入合適的值哨免。當(dāng)客戶端 App 試圖刷新現(xiàn)有的訪問令牌,用戶無需通過 Web 瀏覽器重新登錄昙沦,使用合法重定向 URL 即可琢唾。OAuth 流會(huì)自動(dòng)使用該 URL 將 App 重定向。在正常的登錄過程中盾饮,客戶端 App 與 LinkedIn 服務(wù)器會(huì)交換該 URL采桃,同時(shí)取得授權(quán)碼與訪問令牌∏鹚穑總之普办,該值不能為空,稍后會(huì)用來與服務(wù)器進(jìn)行交換徘钥,因此必須定義它衔蹲。
重定向 URL 不需要是真實(shí)存在的 URL,可以是任何以 “https://” 開頭的值呈础。在此,筆者將其賦值如下。你可以將其改為任何你希望的值批幌。
https://com.appcoda.linkedin.oauth/oauth
如果你使用了一個(gè)不同的 URL, 千萬記得對(duì)后面出現(xiàn)的代碼進(jìn)行相應(yīng)的修改。
在“OAuth 2.0”一節(jié)寫入合法重定向 URL 后畴博,必須點(diǎn)擊添加按鈕,保證將其加入 App 中蓝仲。
此外俱病,記得點(diǎn)擊屏幕底部的更新按鈕。
至于有關(guān)訪問權(quán)限的選項(xiàng)袱结,保留基本選項(xiàng)即可亮隙,因?yàn)槠渫耆珴M足本教程的需求。當(dāng)然垢夹,你也可以選擇更多權(quán)限溢吻,或在演示 App 準(zhǔn)備就緒之后再做修改。請注意果元,如果 App 請求的最初權(quán)限遭到改動(dòng)促王,用戶必須重新登錄以認(rèn)可這些改動(dòng)。
開始授權(quán)過程
現(xiàn)在而晒,打開 Xcode 中的啟動(dòng)項(xiàng)目蝇狼,我們即將開始實(shí)現(xiàn),并最終完成 OAuth 2.0 流倡怎。不過迅耘,在開始之前,請選擇項(xiàng)目導(dǎo)航欄(Project Navigator)中的 WebViewController.swift 文件监署,打開它颤专。在該類的頭部,你會(huì)看到兩個(gè)名為 linkedInKey 與 linkedInSecret 的變量钠乏。你需要將之前從 LinkedIn 開發(fā)者網(wǎng)站得到的 Client ID 與 Client Secret 值分別賦值給這兩個(gè)變量(簡單的復(fù)制栖秕、黏貼即可)。
本步的主要目的晓避,是準(zhǔn)備好用來獲取授權(quán)碼的請求簇捍,并通過一個(gè) Web 視圖加載它。界面生成器(Interface Builder)中的 WebViewController 已經(jīng)包含了一個(gè) Web 視圖够滑,因此我們將以 WebViewController 類為基礎(chǔ)構(gòu)建視圖垦写。用于獲取授權(quán)碼的請求必須包含以下參數(shù):
- response_type:取值為恒定的標(biāo)準(zhǔn)值:code吕世。
- client_id:取值為來自 LinkedIn 開發(fā)者網(wǎng)站的 Client ID彰触,之后會(huì)賦值給項(xiàng)目中的 linkedInKey 屬性。
- redirect_uri:取值為在前一節(jié)指定的合法重定向 URL命辖。請確保在后面的代碼段中填入相應(yīng)的值况毅。
- state:取值為唯一的字符串分蓖,用于預(yù)防跨站請求偽造(CSRF)。
- scope:取值為 App 請求的訪問權(quán)限列表尔许,以 URL 形式編碼么鹤。
下面,介紹代碼的具體實(shí)現(xiàn)味廊。首先蒸甜,在 WebViewController 類下創(chuàng)建一個(gè)名為 startAuthorization() 的新函數(shù)。該函數(shù)的第一個(gè)任務(wù)是根據(jù)上文的描述為請求參數(shù)賦值余佛。
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 中的特殊符號(hào)通過 URL 編碼替換為百分比編碼字符。因此辉巡,下面的鏈接:
https://com.appcoda.linkedin.oauth/oauth
將會(huì)轉(zhuǎn)換為:
https%3A%2F%2Fcom.appcoda.linkedin.oauth%2oauth
(See more about URL-encoding here).
(點(diǎn)此了解 URL 編碼的更多信息恨憎。)
其次,state 變量必須包含一個(gè)唯一且莫測的字符串郊楣。在上面的代碼中憔恳,我們將“l(fā)inkedin”字符串與當(dāng)前時(shí)間戳(自1970年以來的時(shí)間間隔)的整數(shù)部分進(jìn)行聯(lián)結(jié),以此確保字符串的唯一性净蚤。你也可以生成隨機(jī)字符钥组,再將其附加到 state 字符串上。
最后塞栅,將 scope 賦值為“r_basicprofile”者铜,后者與之前在 LinkedIn 開發(fā)者網(wǎng)站設(shè)定的 App 訪問權(quán)限相匹配。當(dāng)你設(shè)置訪問權(quán)限時(shí)放椰,請確保與官方文檔中的規(guī)定一致作烟。
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.
下一步,創(chuàng)建授權(quán) URL砾医。請注意拿撩,URL https://www.linkedin.com/uas/oauth2/authorization 必須用于該請求,而該 URL 已經(jīng)賦做 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 視圖中加載該請求。請記住撬呢,只有前文所述的請求配置得當(dāng)伦吠,用戶才能通過 Web 視圖成功登錄。否則,LinkedIn 將返回錯(cuò)誤消息毛仪,導(dǎo)致無法進(jìn)行下一步操作搁嗓。
因此,請確保正確拷貝了 Client Key箱靴、Client Secret腺逛,以及統(tǒng)一的合法重定向 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)
}
在結(jié)束本節(jié)之前衡怀,我們必須調(diào)用上面的函數(shù)棍矛。可以通過 viewDidLoad(_: ) 函數(shù)進(jìn)行調(diào)用:
override func viewDidLoad() {
...
startAuthorization()
}
此時(shí)抛杨,你終于可以運(yùn)行 App茄靠,測試其是否成功了。如果你根據(jù)筆者的指導(dǎo)配置正確蝶桶,應(yīng)該可以看到以下頁面:
不過慨绳,先別急著登錄 LinkedIn 賬號(hào),本節(jié)還有一部分工作未完成真竖。然而脐雪,如果你看到了登錄表格,說明你已經(jīng)成功發(fā)送了獲取授權(quán)碼的請求恢共。登錄之后战秋,LinkedIn 會(huì)向?yàn)g覽器(在本例中,也即我們的 Web 視圖)返回一個(gè)授權(quán)碼讨韭。
除此之外脂信,還會(huì)在控制臺(tái)打印出 authorizationURL(授權(quán) URL)字符串:
Getting an Authorization Code
獲取授權(quán)碼
授權(quán)碼請求函數(shù)準(zhǔn)備就緒,且在 Web 視圖中成功加載之后透硝,我們可以繼續(xù)執(zhí)行 webView(:shouldStartLoadWithRequest:navigationType) 委托函數(shù)狰闪。在此函數(shù)中,我們會(huì)捕獲來自 LinkedIn 的響應(yīng)濒生,并從中抽取出渴望已久的授權(quán)碼埋泵。
包含授權(quán)碼的響應(yīng)如下所示:
http://com.appcoda.linkedin.oauth/oauth?<strong>code=AQSetQ252oOM237XeXvUreC1tgnjR-VC1djehRxEUbyZ-sS11vYe0r0JyRbe9PGois7Xf42g91cnUOE5mAEKU1jpjogEUNynRswyjg2I3JG_pffOClk</strong>&state=linkedin1450703646
因此,我們需要將該字符串分為多個(gè)部分罪治,隔離出“code”的值丽声。不過,有兩點(diǎn)注意:其一觉义,我們必須確保委托函數(shù)中的 URL 是我們感興趣的雁社。其二,必須確保授權(quán)碼的確存在于該 LinkedIn 響應(yīng)中晒骇。代碼的實(shí)現(xiàn)如下:
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
}
首先霉撵,通過請求參數(shù)獲得該 URL滋饲。接著,檢查 URL 的主機(jī)屬性值以確保這是我們需要的 URL(也即在 LinkedIn 開發(fā)者網(wǎng)站設(shè)定的重定向 URL)喊巍。如果是,請求字符串中 “code” 所在的范圍箍鼓,以驗(yàn)證該 URL 是否真的包含授權(quán)碼崭参。如果返回不為空,則證明授權(quán)碼的確存在款咖。
將 URL 字符串分為多個(gè)部分并不難何暮。為了簡化步驟,筆者將該任務(wù)分為兩步:
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
}
除了上面新出現(xiàn)的兩行代碼铐殃,你也肯定注意到了對(duì) requestForAccessToken(_: ) 函數(shù)的調(diào)用海洼。這是我們將在下一部分實(shí)現(xiàn)的自定義函數(shù)。在此函數(shù)中富腊,我們會(huì)用此處獲得的授權(quán)碼讀取訪問令牌坏逢。
如你所見,只差一步赘被,我們就能使用 OAuth 2.0 流獲取訪問令牌了是整。此處扼要重述一下之前的步驟:首先,我們成功創(chuàng)建了獲取授權(quán)碼的請求民假。接著浮入,作為授權(quán)過程的一部分,用戶通過該請求連接他們的 LinkedIn 賬號(hào)羊异。最后事秀,得到并抽取出授權(quán)碼。
如果你想對(duì)目前的 App 進(jìn)行測試野舶,只需注釋掉 requestForAccessToken(_: ) 函數(shù)的調(diào)用部分即可易迹。你大可以在任意位置添加打印命令,從而深刻理解每個(gè)步驟的作用平道。
Requesting for the Access Token
信息結(jié)構(gòu)赴蝇,創(chuàng)作我們的信息就是如此,我已經(jīng)很是在此基礎(chǔ)上我們創(chuàng)作就是token整個(gè)結(jié)構(gòu)就是做這些事情的
請求訪問令牌
此前巢掺,我們與 LinkedIn 服務(wù)器的所有交流都是通過 Web 視圖進(jìn)行的句伶。從現(xiàn)在起,我們將僅通過簡便的 RESTful 請求(也即 POST 與 GET 請求)與服務(wù)器交流陆淀。更具體地說考余,我們會(huì)發(fā)起一個(gè) POST 請求來獲取訪問令牌,之后再用 GET 請求獲得用戶主頁的 URL轧苫。
話雖如此楚堤,現(xiàn)在要先創(chuàng)建在上一部分末尾提過的新的自定義函數(shù):requestForAccessToken()疫蔓。在此函數(shù)內(nèi)部,我們將執(zhí)行三個(gè)任務(wù):
- 準(zhǔn)備好 POST 請求的參數(shù)身冬。
- 初始化并配置一個(gè)可變的 URL 請求對(duì)象(NSMutableURLRequest)衅胀。
- 實(shí)例化一個(gè) NSURLSession 對(duì)象,繼而執(zhí)行一個(gè)數(shù)據(jù)任務(wù)請求酥筝。在得到恰當(dāng)?shù)捻憫?yīng)之后滚躯,我們將訪問令牌存儲(chǔ)在用戶默認(rèn)的字典中。
準(zhǔn)備 POST 請求參數(shù)
與獲取授權(quán)碼的請求準(zhǔn)備相似嘿歌,為了獲得訪問令牌掸掏,我們需要在請求中 POST 特定的參數(shù)與其對(duì)應(yīng)的值。這些參數(shù)包括:
- grant_type: It’s a standard value that should always be: authorization_code.
- 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:取值為恒定的標(biāo)準(zhǔn)值:authorization_code宙帝。
- code:取值為在上一部分獲得的授權(quán)碼丧凤。
- redirect_uri:取值為前面多次提到的合法重定向 URL。
- client_id:取值為 Client Key 的值步脓。
- client_secret:取值為 Client Secret 的值愿待。
在上一部分得到的授權(quán)碼將在新函數(shù)中用作參數(shù)。首先靴患,讓我們?yōu)閰?shù)“grant_type”與“redirect_uri”賦值:
func requestForAccessToken(authorizationCode: String) {
let grantType = "authorization_code"
let redirectURL = "https://com.appcoda.linkedin.oauth/oauth".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}
其他所有參數(shù)的值 App 都已經(jīng)知道了呼盆,因此,我們可以將其整合為一個(gè)字符串:
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 類創(chuàng)建過 POST 請求蚁廓,那你一定知道 POST 參數(shù)無法以字符串的形式傳送访圃。它們必須轉(zhuǎn)化為 NSData 對(duì)象,再賦值給請求的 HTTPBody 部分(后文會(huì)有解釋)相嵌。因此腿时,讓我們按照要求轉(zhuǎn)化 postParams:
func requestForAccessToken(authorizationCode: String) {
...
// Convert the POST parameters into a NSData object.
let postData = postParams.dataUsingEncoding(NSUTF8StringEncoding)
}
準(zhǔn)備請求對(duì)象
準(zhǔn)備好 POST 參數(shù)之后,我們可以繼續(xù)初始化并配置 NSMutableURLRequest 對(duì)象饭宾。初始化時(shí)會(huì)用到獲取訪問令牌所需的 URL(https://www.linkedin.com/uas/oauth2/accessToken) 批糟,而后者已經(jīng)賦值給 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:
接下來看铆,告訴請求對(duì)象我們想要?jiǎng)?chuàng)建的請求類型徽鼎,并傳入 POST 參數(shù):
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
}
根據(jù) LinkedIn 文檔,請求的 Content-Type 部分需要設(shè)置為 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")
}
終于弹惦,請求對(duì)象的必要配置完成了》裼伲現(xiàn)在可以使用它了。
Performing the request
執(zhí)行請求
我們將把用于獲取訪問令牌的請求實(shí)現(xiàn)為 NSURLSession 類的對(duì)象棠隐。通過該對(duì)象石抡,創(chuàng)建一個(gè)數(shù)據(jù)任務(wù)請求,并在完成處理程序(completion handler)內(nèi)部處理 LinkedIn 服務(wù)器的響應(yīng):
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 服務(wù)器將會(huì)返回包含訪問令牌的 JSON 數(shù)據(jù)啰扛。因此嚎京,我們的任務(wù)是得到該 JSON 數(shù)據(jù),將之轉(zhuǎn)化為字典對(duì)象隐解,然后抽取出訪問令牌鞍帝。當(dāng)然,這一切只有在返回的 HTTP 狀態(tài)碼是 200煞茫,也即請求成功時(shí)帕涌,才能進(jìn)行。
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 數(shù)據(jù)的倒換就是加載我們呢自身的SDK,接入信息就是從來不會(huì)有很多的考很多事情就是自己做起來用戶就是會(huì)考
請注意架谎,轉(zhuǎn)化發(fā)生在一個(gè) do-catch 語句內(nèi)部炸宵,因?yàn)閺?Swift 2.0 開始,此類操作可能拋出異常(并不存在錯(cuò)誤參數(shù))谷扣。在我們的演示 App 中土全,無需特別考慮出現(xiàn)異常的情況,因此可以向控制器發(fā)送一條信息会涎,表示轉(zhuǎn)化失敗裹匙。如果一切運(yùn)行順利,我們就將 JSON 數(shù)據(jù)(閉包中的數(shù)據(jù)參數(shù))轉(zhuǎn)化為字典(dataDictionary 對(duì)象)末秃,之后就可以直接讀取訪問令牌概页。
接下來做什么呢?將字典保存在用戶默認(rèn)的字典中练慕,然后移除視圖控制器:
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()
}
注意惰匙,視圖控制器會(huì)在主線程中移除。請永遠(yuǎn)牢記铃将,與 UI 相關(guān)的改動(dòng)必須發(fā)生在 App 的主線程中项鬼,而不是背景線程中。而上面顯示的完成處理程序(閉包)則永遠(yuǎn)在背景線程中執(zhí)行劲阎。
Our ultimate goal has been finally achieved! We managed to acquire the access token that will “unlock” several API features.
我們的終極目標(biāo)終于完成啦绘盟!得到訪問令牌之后,可以“解鎖”許多 API 功能悯仙。
獲得用戶主頁的 URL
接下來龄毡,我們將演示如何用訪問令牌獲得用戶主頁的 URL,并在 Safari 瀏覽器中打開它锡垄。然而稚虎,在此之前,讓我們先討論一點(diǎn)別的問題偎捎。當(dāng)你啟動(dòng) App 時(shí)蠢终,你有兩個(gè)選擇序攘,如下圖所示:
默認(rèn)情況下,LinkedIn Sign In (LinkedIn 登錄)按鈕是啟用的寻拂,而 Get my profile URL(獲得我的主頁 URL)按鈕是禁用的程奠。既然現(xiàn)在已經(jīng)得到了訪問令牌,我們需要啟用第二個(gè)按鈕祭钉,同時(shí)禁用第一個(gè)按鈕瞄沙。這要如何完成呢?
一種實(shí)現(xiàn)方式是使用委托模式慌核,通過一個(gè)委托函數(shù)通知 ViewController 類:訪問令牌已經(jīng)得到距境,請啟用第二個(gè)按鈕。另一種方式是從 WebViewController 類中 Post 一個(gè)自定義通知(NSNotification 對(duì)象)垮卓,在 ViewController 類中監(jiān)聽該通知垫桂。其實(shí),兩種方法都可以實(shí)現(xiàn)粟按。但是诬滩,還有一種更為簡單的方法三:在 ViewController 出現(xiàn)時(shí),檢查訪問令牌是否存在于用戶默認(rèn)的字典中灭将。如果存在疼鸟,就禁用登錄按鈕,啟用第二個(gè)按鈕庙曙。否則空镜,就保持不變。
此處捌朴,我們會(huì)在 ViewController 類中實(shí)現(xiàn)一個(gè)新的小函數(shù)來進(jìn)行檢查姑裂。請注意,我們還設(shè)置了第三個(gè)默認(rèn)隱藏的按鈕(也即 btnOpenProfile IBOutlet 屬性)男旗。當(dāng)?shù)玫接脩糁黜摰?URL 時(shí)舶斧,該按鈕就會(huì)變?yōu)榭梢姡⒁源?URL 字符串作為其標(biāo)題(后文會(huì)有示例)察皇。
Now, let’s define this new function:
現(xiàn)在茴厉,先來定義這個(gè)新函數(shù):
func checkForExistingAccessToken() {
if NSUserDefaults.standardUserDefaults().objectForKey("LIAccessToken") != nil {
btnSignIn.enabled = false
btnGetProfileInfo.enabled = true
}
}
我們會(huì)在 viewWillAppear(_: ) 方法中調(diào)用該函數(shù):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
checkForExistingAccessToken()
}
之后,App 就能合理地啟用或禁用 ViewController 中的兩個(gè)按鈕了什荣。
接下來矾缓,讓我們聚焦于 getProfileInfo(_: ) IBAction 方法。此方法會(huì)在 Get my profile URL(獲得我的主頁 URL)按鈕被點(diǎn)擊時(shí)執(zhí)行稻爬。屆時(shí)嗜闻,我們可以向 LinkedIn 服務(wù)器發(fā)送 GET 請求,使用訪問令牌獲得用戶主頁的 URL桅锄。此處采用的方法與在上一部分創(chuàng)建獲取訪問令牌的請求時(shí)所用的方法非常相似琉雳。
現(xiàn)在样眠,讓我們從指定請求的 URL 字符串開始吧。請注意翠肘,當(dāng)你不是很確定自己需要什么 URL檐束,或者指定哪些參數(shù)時(shí),大可以尋求官方文檔的幫助束倍。
@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 會(huì)返給我們用戶主頁的 URL。不要忘記廊移,在執(zhí)行這類請求之前糕簿,必須獲得適當(dāng)?shù)臋?quán)限探入。在本演示案例中狡孔,我們已經(jīng)獲得了訪問用戶基本介紹信息的權(quán)限。
接下來蜂嗽,創(chuàng)建一個(gè)新的 NSMutableURLRequest 對(duì)象苗膝,并以“GET”方法作為理想的 HTTP 方法。此外植旧,還需指定一個(gè) 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 類創(chuàng)建該請求:
@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 狀態(tài)碼為 200),閉包中的數(shù)據(jù)參數(shù)將會(huì)包含服務(wù)器返回的 JSON 數(shù)據(jù)完沪。與之前一樣域庇,我們必須將此 JSON 數(shù)據(jù)轉(zhuǎn)化為字典,才能最終抽取出用戶主頁的 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()
}
}
現(xiàn)在听皿,回到之前提到過的一點(diǎn)內(nèi)容:profileURLString 的值將會(huì)賦給 btnOpenProfile 按鈕的標(biāo)題,該按鈕也會(huì)變成可見宽档。還記得不尉姨?我們現(xiàn)在的工作都是在背景線程中進(jìn)行的,因此吗冤,我們還需將其加入主線程中:
@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()
}
}
現(xiàn)在又厉,運(yùn)行 App九府,如果你成功得到了訪問令牌,在點(diǎn)擊 Get my profile URL(獲得我的主頁 URL)按鈕后不久馋没,你就能看到自己主頁的 URL 顯示在第三個(gè)按鈕的位置昔逗。
在 Safari 瀏覽器查看主頁
現(xiàn)在,通過使用訪問令牌與 LinkedIn API篷朵,我們得到了用戶主頁的 URL勾怒。接下來就該驗(yàn)證其是否正確了。既然已將此 URL 設(shè)置為一個(gè)按鈕的標(biāo)題声旺,最快的驗(yàn)證方法莫過于打開它了笔链。具體的實(shí)現(xiàn)方法箱單簡單,因此筆者也不必要多言:
@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.
上面最后一行代碼會(huì)觸發(fā) Safari 瀏覽器腮猖,后者會(huì)加載并展示用戶的主頁鉴扫。
你可能已經(jīng)發(fā)現(xiàn),教程已經(jīng)步入尾聲澈缺,卻仍然沒有提及廢除或刷新訪問令牌的內(nèi)容坪创。其實(shí)原因如下:關(guān)于廢除訪問令牌,LinkedIn 并未提供任何相關(guān)的 API姐赡。因此莱预,如果你需要停止 App 發(fā)送合法請求,最好的做法應(yīng)該是從存儲(chǔ)機(jī)制(數(shù)據(jù)庫项滑,用戶默認(rèn)設(shè)置等)中刪除之依沮。除此之外,一個(gè)訪問令牌的有效期大約為60天(在筆者撰寫本文之時(shí)枪狂,官網(wǎng)文檔是如此規(guī)定的)危喉。LinkedIn 建議,在此時(shí)間范圍到期之前州疾,刷新訪問令牌辜限。刷新的操作非常簡單,你只需要從頭進(jìn)行驗(yàn)證與授權(quán)過程即可严蓖。刷新時(shí)薄嫡,如果訪問令牌有效,用戶便無需再次輸入登錄信息谈飒,一切都會(huì)在后臺(tái)進(jìn)行岂座,訪問令牌會(huì)自動(dòng)刷新,延遲有效期60天杭措。然而费什,對(duì)于大多數(shù) Web 應(yīng)用,存在一個(gè)常見情況:后臺(tái)刷新的一個(gè)基本前提,是用戶已經(jīng)登錄了他們的 LinkedIn 賬號(hào)鸳址,而對(duì)于 App 中的內(nèi)部 Web 視圖瘩蚪,這一條件無法滿足。因此稿黍,在訪問令牌快要到期之前疹瘦,你很可能要讓用戶再走一遍登錄流程。想要了解更多信息巡球,可以點(diǎn)擊此處言沐,查看“刷新訪問令牌”一節(jié)。好了酣栈,說再見的時(shí)候到了险胰。筆者希望本教程對(duì)你有所幫助,并成功向 LinkedIn 發(fā)送經(jīng)過授權(quán)的請求矿筝。
作為參考端铛,你可以從 GitHub 下載本案例完整的 Xcode 項(xiàng)目文件档押。
OneAPM Mobile Insight 以真實(shí)用戶體驗(yàn)為度量標(biāo)準(zhǔn)進(jìn)行 Crash 分析纲辽,監(jiān)控網(wǎng)絡(luò)請求及網(wǎng)絡(luò)錯(cuò)誤绿店,提升用戶留存。訪問 OneAPM 官方網(wǎng)站感受更多應(yīng)用性能優(yōu)化體驗(yàn)铸史,想閱讀更多技術(shù)文章鼻疮,請?jiān)L問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客