Android開發(fā):OAuth2服務(wù)認(rèn)證
為了安全地訪問在線服務(wù),用戶需要在服務(wù)上進(jìn)行身份驗(yàn)證,即要提供他們的身份的證明。對于一個(gè)要訪問第三方服務(wù)的程序來說,安全問題甚至更復(fù)雜。不僅僅是用戶需要在訪問服務(wù)前要進(jìn)行身份驗(yàn)證,而且程序也要進(jìn)行身份驗(yàn)證來授權(quán)用戶。
OAuth2協(xié)議是一種向第三方服務(wù)進(jìn)行身份驗(yàn)證的工業(yè)標(biāo)準(zhǔn)方法.OAuth2提供一個(gè)單值,叫做** 認(rèn)證令牌(auth token)** ,代表用戶身份和程序身份驗(yàn)證授權(quán)。這節(jié)課將要演示連接到一個(gè)支持OAuth2的Google服務(wù)器上。盡管Google服務(wù)只是用作示例,但是演示的這 項(xiàng)技術(shù)將會(huì)在任何正確支持OAuth2協(xié)議的服務(wù)上工作。
使用OAuth2有利于:
- 從用戶手中得到授權(quán)訪問那些需要他/她的賬戶的在線服務(wù)。
- 代替用戶在一個(gè)在線服務(wù)上驗(yàn)證身份。
- 處理驗(yàn)證錯(cuò)誤
收集信息
要開始使用Oauth2,你需要知道一些關(guān)于你要訪問的API的信息:
- 你要訪問的服務(wù)的地址。
- 認(rèn)證范圍(auth scope)。它是一個(gè)定義了你的應(yīng)用需要的特定訪問類型的字符串。例如,Google Tasks的只讀訪問認(rèn)證范圍范圍是'''查看你的任務(wù)(View your tasks)''',而可讀寫訪問的認(rèn)證范圍是'_管理你的任務(wù)(Manage Your Tasks)* 。
- 一個(gè)**客戶端ID(client id)和客戶端密鑰(client secret)** 。他們是在服務(wù)中為了識(shí)別你的應(yīng)用的字符串。你需要從服務(wù)提供者手中獲取這些字符串。Google 有一個(gè)自服務(wù)系統(tǒng)用來獲取客戶端ID和密鑰。Getting Started with the Tasks API and OAuth 2.0 on Android 這篇文章解釋了如何使用這套系統(tǒng)來獲取這些用于Google Tasks API的值對。
請求一個(gè)驗(yàn)證令牌
現(xiàn)在你已經(jīng)準(zhǔn)備好獲取一個(gè)身份驗(yàn)證令牌了。獲取步驟如下圖所示。
為了得到一個(gè)驗(yàn)證令牌,首先需要在你的manifest文件中請求* INTERNET* 權(quán)限。
- <manifest ... >
- <uses-permission android:name="android.permission.ACCOUNT_MANAGER" />
- <uses-permission android:name="android.permission.INTERNET" />
- ...
- </manifest>
一旦你的應(yīng)用有了這些權(quán)限,你就可以調(diào)用AccountManager.getAuthToken() 方法來獲取令牌。
注意了!在AccountManager 的方法是異步的。這意味著相對于在一個(gè)方法中獲取所有的令牌的工作,你需要把他們分散到一系列的回調(diào)函數(shù)中實(shí)現(xiàn)。例如:
- AccountManager am new Bundle();
- am.getAuthToken(
- myAccount_, // 用getAccountsByType()來檢索的賬戶
- "Manage your tasks", // 令牌范圍
- options, // 特殊驗(yàn)證選項(xiàng)
- this, // 你的activity
- new OnTokenAcquired(), // 成功獲取令牌后調(diào)用的回調(diào)函數(shù)
- new Handler(new OnError())); // 錯(cuò)誤發(fā)生時(shí)調(diào)用的回調(diào)函數(shù)
在這個(gè)例子中,OnTokenAcquired是一個(gè)繼承了**AccountManagerCallback**的類。**AccountManager**在**OnTokenAcquired**中調(diào)用**run()**方法,該方法需要傳遞一個(gè)含有一個(gè)**Bundle**的**AccountManagerFuture**的實(shí)例。如果調(diào)用成功,那么這個(gè)令牌中就包含了這個(gè)Bundle。
這里展現(xiàn)如何從** 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);
- ...
- }
- }
如果這一切都運(yùn)行順利,那么這個(gè)** KEY_AUTHTOKEN** 中會(huì)包含一個(gè)有小額令牌,并且你就開始“啟程遠(yuǎn)洋”了。盡管事情不會(huì)總是那么順利……
請求一個(gè)驗(yàn)證令牌……再來一次
首先,你的一個(gè)驗(yàn)證令牌請求可能會(huì)因?yàn)橐恍┰蚨。?/p>
- 設(shè)備或網(wǎng)絡(luò)錯(cuò)誤導(dǎo)致
- 用戶決定不想讓你的應(yīng)用訪問他的賬戶。
- 保存的賬戶憑據(jù)不足以能夠得到訪問該賬戶的權(quán)限。
- 緩存的賬戶令牌已經(jīng)過期。
應(yīng)用程序可以用平常方式夠處理前兩種問題,通常做法是簡單地把錯(cuò)誤信息顯示給用戶。如果網(wǎng)絡(luò)斷開或者用戶決定不同意訪問,那么你的程序就沒有太多可以做的事。最后兩個(gè)問題有點(diǎn)復(fù)雜,因?yàn)檫\(yùn)行正常的應(yīng)用能夠自行處理這些失敗情形。
對于第三種失敗情形,即沒有充分的憑據(jù),會(huì)通過你在* Intent* ,那么這個(gè)驗(yàn)證程序就會(huì)告訴你,在它可以給你一個(gè)可用的令牌之前,它需要和用戶直接進(jìn)行交互。
有很多原因可導(dǎo)致驗(yàn)證程序返回一個(gè)* Intent*。它可能是用戶第一次登錄賬戶的時(shí)候?;蛟S該用戶的賬戶已經(jīng)過期卻還要登陸,又或許他們存儲(chǔ)的憑據(jù)本身就是錯(cuò)的。可能該賬戶需要雙重認(rèn)證或者它需要激活照相機(jī)來做虹膜掃描。具體什么原因并不重要,如果你想得到一個(gè)合法令牌,你就不得不一連串的詢問* Intent* 來獲得。
- 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;
- }
- }
- }
要注意這個(gè)例子中用了* startActivityForResult()) * 方法,所以你可以通過實(shí)現(xiàn)*onActivityResult())* 方法來捕獲這個(gè) * Intent* 的結(jié)果。這個(gè)非常重要!如果你不想從驗(yàn)證程序反饋的* Intent* 中捕獲結(jié)果,那么你將不可能知道用戶到底有沒有成功驗(yàn)證。如果結(jié)果是* RESULT_OK*,那么驗(yàn)證程序已經(jīng)更新并保存這些憑據(jù),以便這些憑據(jù)足夠達(dá)到你所請求的訪問等級需求,然后你應(yīng)該再次調(diào)用*AccountManager.getAuthToken()* 方法來請求新的認(rèn)證令牌。
對于最后一個(gè)問題,即令牌已過期,這其實(shí)并不是一個(gè)* AccountManager* 不斷地去線上檢查所有令牌的狀態(tài)時(shí)非常浪費(fèi)并且代價(jià)昂貴的。所以這個(gè)失敗只有當(dāng)想你的一樣的程序常使用認(rèn)證令牌來訪問線上服務(wù)時(shí)才能被檢測到。
連接在線服務(wù)
下面的例子展示如何連接到Google服務(wù)器。由于Google 使用了工業(yè)標(biāo)準(zhǔn)OAuth2協(xié)議來認(rèn)證請求,我們之前在這里討論過的技術(shù)具有廣泛的適用性。但是,請記住每個(gè)服務(wù)器是不同的。你可能自己會(huì)發(fā)現(xiàn)根據(jù)具體情況,這些訪問賬戶的指令需要作出輕微的調(diào)整。
Google Api需要提供四個(gè)值以及與之對應(yīng)的請求:API 鍵(key),客戶端ID,客戶端密鑰,以及認(rèn)證鍵(the auth key)。前三個(gè)來自Google API Console網(wǎng)站。最后一個(gè)是你通過調(diào)用* AccountManager.getAuthToken()* 獲得的值。你把它們作為HTTP請求中的一部分傳遞到Google服務(wù)器。
- URL url " + 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);
如果請求返回一個(gè)HTTP錯(cuò)誤號碼401,說明你的令牌被拒了。就像最后一部分提到的,這種情況最大的可能是令牌過期了。修復(fù)方法很簡單:調(diào)用* AccountManager.invalidateAuthToken())* 方法并且再次重復(fù)取令牌的過程。
由于令牌過期的情況太普遍,而且球服方法又如此簡單,許多程序會(huì)在請求令牌前都假定令牌已經(jīng)過期。如果更新對于服務(wù)器成本很小,你應(yīng)該更傾向于在第一次調(diào)用* AccountManager.getAuthToken()*之前就調(diào)用*AccountManager.invalidateAuthToken())* 方法,并且把你的一次認(rèn)證令牌請求拆成兩次。