|
為了能安全地訪問在線服務(wù),必須要驗(yàn)證用戶的身份,即需要用戶提供證明自己身份的認(rèn)證信息。 如果一個(gè)應(yīng)用程序要訪問第三方服務(wù)提供商的數(shù)據(jù),那么安全驗(yàn)證問題更加復(fù)雜,此時(shí)不僅需要驗(yàn)證用戶的身份信息,還要驗(yàn)證應(yīng)用程序的信息,以此來保證應(yīng)用程序只能訪問獲得了用戶授權(quán)的那部分服務(wù)和數(shù)據(jù)。
目前用來解決應(yīng)用程序和第三方服務(wù)提供商授權(quán)認(rèn)證的標(biāo)準(zhǔn)協(xié)議是 OAuth2,它本身只提供了一個(gè)值,即授權(quán)令牌(auth token)。 它代表了用戶的授權(quán)和應(yīng)用程序按照用戶授權(quán)的范圍能夠進(jìn)行的數(shù)據(jù)訪問與操作。 本節(jié)課展示了怎樣通過 OAuth2 協(xié)議來認(rèn)證和訪問 Google 的一個(gè)服務(wù)(服務(wù)方必須支持 OAuth2 協(xié)議), 雖然本節(jié)課的例子只是針對 Google Service,其他的任何第三方服務(wù)提供商只要支持 OAuth2 協(xié)議, 本文中提到的技術(shù)解決方案也同樣適用。
使用 OAuth2 的優(yōu)勢:
開始進(jìn)行 OAuth2 認(rèn)證之前,您首先需要獲得服務(wù)方提供的 API 信息,具體包括:
獲得訪問令牌的步驟參照下圖:
在要獲取訪問令牌之前,您首先要做的是在 manifest 文件中加入賬戶管理 ACCOUNT_MANAGER 權(quán)限。 如果要利用該令牌來訪問在線服務(wù),您還需要網(wǎng)絡(luò) INTERNET 權(quán)限。
加入的方法參見示例代碼:
當(dāng)權(quán)限加入完畢的時(shí)候,接下來的工作就是申請令牌了,通過調(diào)用方法 <a href="http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)" target="_blank">AccountManager.getAuthToken() 來獲取令牌。
特別注意,因?yàn)?AccountManager 中的很多方法都涉及到網(wǎng)絡(luò)連接,所以大部分方法都是異步調(diào)用,那么也就意著,您不能只在一個(gè)方法中簡單按照 OAuth2 的認(rèn)證流程來編寫程序,而是要拆分成一系列的回調(diào)函數(shù)(callbacks)來實(shí)現(xiàn)。
示例代碼:
在上面的例子中,OnTokenAcquired 類實(shí)現(xiàn)了 AccountManagerCallback 接口。 AccountManager 會(huì)回調(diào) OnTokenAcquired 類的 <a >run() 方法,并傳遞一個(gè)包含 Bundle 的參數(shù) AccountManagerFuture ,如果成功獲取到令牌,那么該令牌信息就會(huì)保存在 Bundle 中。
示例代碼展示了如何從 Bundle 中取得已申請的令牌:
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { // Get the result of the operation from the AccountManagerFuture. Bundle bundle = result.getResult(); // The token is a named value in the bundle. The name of the value // is stored in the constant AccountManager.KEY_AUTHTOKEN. token = bundle.getString(AccountManager.KEY_AUTHTOKEN); ... } }如果一切進(jìn)展順利,那么 Bundle 中就包含了合法的令牌值,您可以通過鍵值 KEY_AUTHTOKEN 直接拿到。不過,有時(shí)事情也沒有那么順利,那么需要我們多做如下工作。
重新獲得訪問令牌 (Auth Token)第一次申請令牌失敗的原因可能有很多,比如:
對于前兩條錯(cuò)誤,您可以通過簡單地顯示一條失敗信息通知用戶來優(yōu)雅的解決。因?yàn)槿绻蔷W(wǎng)絡(luò)掉線或者是用戶不同意授權(quán),您的應(yīng)用程序也對此無能為力,所以也不必過多關(guān)注。對于后面的兩種情況,處理的時(shí)候比較復(fù)雜,設(shè)計(jì)良好的應(yīng)用程序應(yīng)當(dāng)能夠自動(dòng)處理此類錯(cuò)誤。
第三種錯(cuò)誤是缺少足夠的賬戶憑據(jù)信息(insufficient credentials),當(dāng)這種錯(cuò)誤出現(xiàn)的時(shí)候,AccountManagerCallback (前面示例代碼中實(shí)現(xiàn)的 OnTokenAcquired 類) 中的 Bundle 包含了 一個(gè)鍵值為 KEY_INTENT 的 Intent ,您可以通過查詢 Bundle 中是有沒有包含此鍵值來判斷是否出現(xiàn)了該錯(cuò)誤,如果包含了,那么也就意味著 OAuth2 認(rèn)證過程還需要用戶的賬戶憑據(jù)信息(account credentials)才能繼續(xù)接下來的令牌獲取過程。
出現(xiàn)這種情況可能的原因有很多,或許是用戶第一次登陸該賬戶,也可能是用戶的賬戶登錄超時(shí)了,需要重新登錄, 也有可能是用戶的賬戶憑據(jù)信息(account credentials)錯(cuò)誤,又或者是用戶賬戶啟用了兩因素認(rèn)證(two-factor authentication)機(jī)制, 也有可能是需要啟動(dòng)照相機(jī)來進(jìn)行視網(wǎng)膜掃描認(rèn)證。但是不管是哪種具體類型的失敗原因,如果您的應(yīng)用程序需要獲得一個(gè)合法的訪問令牌,那就必須將該 Intent 通知給系統(tǒng),并且捕獲它的返回結(jié)果。
示例代碼: private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { ... Intent launch = (Intent) result.get(AccountManager.KEY_INTENT); if (launch != null) { startActivityForResult(launch, 0); return; } } }請注意在上例中使用的是 startActivityForResult(),那么您就可以在自己的 Activity 中通過實(shí)現(xiàn) onActivityResult() 方法來獲得該 Intent 的處理結(jié)果。 這一點(diǎn)非常重要,如果您不捕獲該 Intent 的處理結(jié)果,就不能知道用戶是否進(jìn)行正常認(rèn)證了。 假如返回的結(jié)果為 RESULT_OK, 那么賬戶憑據(jù)(account credentials)信息已經(jīng)更新,現(xiàn)在有足夠的憑據(jù)信息來進(jìn)行接下來的獲取訪問令牌的過程了,您可以通過再次調(diào)用 <a href="http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)">AccountManager.getAuthToken() 方法來獲取新的令牌。
對于最后一種情況令牌的過期,準(zhǔn)確來說,這并不算是一種 AccountManager 的錯(cuò)誤。 出現(xiàn)此種錯(cuò)誤的唯一地方是利用該令牌訪問在線服務(wù)。 如果要通過利用 AccountManager 來依次訪問所有的在線服務(wù),并循環(huán)檢查所有已存儲(chǔ)的令牌是否過期的方法是浪費(fèi)時(shí)間的,代價(jià)也比較大。所以,好的解決方法是,當(dāng)您的應(yīng)用程序在訪問在線服務(wù)的時(shí)候,捕獲這個(gè)錯(cuò)誤,如果發(fā)現(xiàn)令牌過期了,重新申請一個(gè)新的即可。 具體的申請流程可以參見上面講解。
4. 訪問在線服務(wù)下面的示例展示了如何訪問 google 在線服務(wù)。因?yàn)?Google 服務(wù)的認(rèn)證是基于標(biāo)準(zhǔn)的 OAuth2 機(jī)制,所以這里討論的技術(shù)是廣泛適用的。請記住,雖然每個(gè)服務(wù)提供方可能各不相同,但只要您按照自己的具體情況略作修改即可。
利用 Google APIs 訪問 Google 服務(wù),每一次的Http請求,您都需要提供四個(gè)值,分別是 API key, client ID, client secret, 和 auth key. 前三個(gè)值可以從Google API Console 獲得,最后一個(gè)值,即訪問令牌,通過 <a href="http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)">AccountManager.getAuthToken() 獲得。
You pass these to the Google Server as part of an HTTP request.
示例代碼: URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key); URLConnection conn = (HttpURLConnection) url.openConnection(); conn.addRequestProperty("client_id", your client id); conn.addRequestProperty("client_secret", your client secret); conn.setRequestProperty("Authorization", "OAuth " + token);如果上面的請求結(jié)果返回 401 錯(cuò)誤,那么表明您的訪問令牌(Token)是無效的。 像上面提到的第四種錯(cuò)誤類型,很可能是令牌過期了。解決這個(gè)問題非常簡單,首先調(diào)用AccountManager.invalidateAuthToken() 刪除本地緩存的無效令牌,然后調(diào)用 <a href="http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)">AccountManager.getAuthToken() 獲取一個(gè)新的令牌即可。
因?yàn)榱钆七^期是一個(gè)很普遍的情況,解決它也比較容易,很多應(yīng)用程序只是在每次獲取新令牌之前都先將本地令牌設(shè)置為過期并清除。對于服務(wù)提供商來說,如果申請一個(gè)令牌的操作代價(jià)比較小,那么您的應(yīng)用程序可以在第一次調(diào)用獲取令牌的方法 <a href="http://developer.android.com/reference/android/accounts/AccountManager.html#getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)">AccountManager.getAuthToken() 之前,也先調(diào)用清除無效令牌的方法 AccountManager.invalidateAuthToken() 。
參考文摘:
http://developer.android.com/training/id-auth/authenticate.html
聯(lián)系客服