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