為了安全地存取線上服務,使用者需要驗證服務,而他們需要提供身分證明。對於存取第三方服務的應用程式,安全性問題會更複雜。使用者不僅需要通過驗證才能存取服務,應用程式也必須取得授權,才能代表使用者採取行動。
處理第三方服務驗證的業界標準做法是 OAuth2 通訊協定。OAuth2 提供單一值 (稱為驗證權杖),代表使用者身分和應用程式授權,可代表使用者的身分採取行動。本課程示範如何連線至支援 OAuth2 的 Google 伺服器。雖然其中使用 Google 服務的範例,但實際介紹的技術也適用於任何正確支援 OAuth2 通訊協定的服務。
使用 OAuth2 的好處如下:
- 取得使用者透過帳戶存取線上服務的權限。
- 代表使用者為線上服務驗證。
- 處理驗證錯誤。
收集資訊
如要開始使用 OAuth2,您需要瞭解所要存取服務的 API 專屬資訊:
- 您要存取的服務網址。
- 驗證範圍是字串,定義應用程式要求的特定存取權類型。舉例來說,Google Tasks 唯讀存取權的驗證範圍為
View your tasks
,而 Google Tasks 讀取/寫入存取權的驗證範圍為Manage your tasks
。 - 用戶端 ID 和用戶端密鑰,為服務向服務識別您的應用程式的字串。您必須直接向服務擁有者取得這些字串。Google 有專門用來取得用戶端 ID 和密鑰的自助式系統。
要求網際網路權限
如果應用程式指定 Android 6.0 (API 級別 23) 以上版本,則 getAuthToken()
方法本身不需要任何權限。不過,如要對權杖執行作業,您必須將 INTERNET
權限新增至資訊清單檔案,如以下程式碼片段所示:
<manifest ... > <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
要求驗證權杖
如要取得權杖,請呼叫 AccountManager.getAuthToken()
。
注意: 由於某些帳戶作業可能會涉及網路通訊,因此大部分的 AccountManager
方法都是非同步的。也就是說,您需要以一系列回呼的形式實作,而非在單一函式中執行所有驗證作業。
下列程式碼片段說明如何使用一系列的回呼取得權杖:
Kotlin
val am: AccountManager = AccountManager.get(this) val options = Bundle() am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity OnTokenAcquired(), // Callback called when a token is successfully acquired Handler(OnError()) // Callback called if an error occurs )
Java
AccountManager am = AccountManager.get(this); Bundle options = new Bundle(); am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity new OnTokenAcquired(), // Callback called when a token is successfully acquired new Handler(new OnError())); // Callback called if an error occurs
在這個範例中,OnTokenAcquired
是實作 AccountManagerCallback
的類別。AccountManager
會使用包含 Bundle
的 AccountManagerFuture
在 OnTokenAcquired
上呼叫 run()
。如果呼叫成功,權杖會位於 Bundle
中。
以下說明如何從 Bundle
取得權杖:
Kotlin
private class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { // Get the result of the operation from the AccountManagerFuture. val 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. val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN) } }
Java
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. String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); ... } }
如果一切順利,Bundle
的 KEY_AUTHTOKEN
金鑰中會包含有效的權杖,這樣就大功告成了。
首次要求驗證權杖時可能會失敗,原因如下:
- 裝置或網路發生錯誤,導致
AccountManager
失敗。 - 使用者決定不將帳戶存取權授予您的應用程式。
- 儲存的帳戶憑證不足以取得帳戶存取權。
- 快取驗證權杖已過期。
應用程式可以簡單地處理前兩個情況,通常只需向使用者顯示錯誤訊息即可。如果網路停止運作,或是使用者決定不授予存取權,應用程式就無法提供足夠的功能。最後兩個情況會稍微複雜一點,因為擁有良好狀態的應用程式應會自動處理這類故障。
第三個失敗情況是憑證不足,會透過您在 AccountManagerCallback
中收到的 Bundle
(上一個範例中的 OnTokenAcquired
) 傳達。如果 Bundle
在 KEY_INTENT
金鑰中加入 Intent
,驗證器就會指出必須先與使用者直接互動,才能提供有效權杖。
驗證器傳回 Intent
的原因可能有很多種。可能是使用者第一次登入這個帳戶。這可能是因為使用者的帳戶已過期,所以他們需要重新登入,或者儲存的憑證可能不正確。也許帳戶需要雙重驗證,或是需要啟用相機才能執行視網膜掃描。但這並不重要。如果您想取得有效的權杖,就必須關閉 Intent
才能取得。
Kotlin
private inner class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent if (launch != null) { startActivityForResult(launch, 0) } } }
Java
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { ... Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT); if (launch != null) { startActivityForResult(launch, 0); return; } } }
請注意,這個範例使用 startActivityForResult()
,因此您可以在自己的活動中實作 onActivityResult()
,藉此擷取 Intent
的結果。重要事項:如果您未從驗證者的回應 Intent
擷取結果,就無法判斷使用者是否已成功驗證。
如果結果是 RESULT_OK
,驗證者已更新儲存的憑證,使其足以處理您要求的存取層級,建議您再次呼叫 AccountManager.getAuthToken()
以要求新的驗證權杖。
最後一個案例 (權杖已過期) 實際上並非 AccountManager
失敗。如要偵測權杖是否已過期,唯一的方法就是聯絡伺服器,這樣 AccountManager
要持續連線至網路來檢查其所有權杖的狀態,既浪費又昂貴的成本。因此,只有在類似應用程式嘗試使用驗證權杖存取線上服務時,系統才能偵測到這項失敗。
連線至線上服務
下例顯示如何連線至 Google 伺服器。由於 Google 使用產業標準 OAuth2 通訊協定來驗證要求,因此本文所述的技巧普遍適用。不過請注意,每部伺服器都不同。您可能會發現自己需要稍微調整這些操作說明,以考量到您遇到的問題。
Google API 會要求您為每個要求提供四個值:API 金鑰、用戶端 ID、用戶端密鑰和驗證金鑰。前三個來自 Google API 控制台網站最後是呼叫 AccountManager.getAuthToken()
取得的字串值。請將這些符記傳送至 Google 伺服器,做為 HTTP 要求的一部分。
Kotlin
val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key") val conn = url.openConnection() as HttpURLConnection conn.apply { addRequestProperty("client_id", your client id) addRequestProperty("client_secret", your client secret) setRequestProperty("Authorization", "OAuth $token") }
Java
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);
如果要求傳回 HTTP 錯誤代碼 401,表示權杖已遭拒。如上一節所述,最常見的原因是權杖已過期。解決方法很簡單:請呼叫 AccountManager.invalidateAuthToken()
,然後重複執行權杖取得程序。
由於過期的符記是常見的情況,修正方法十分簡單,因此許多應用程式會在要求權杖之前,直接假設權杖已過期。如果更新權杖是伺服器最便宜的作業,建議您在第一次呼叫 AccountManager.getAuthToken()
之前呼叫 AccountManager.invalidateAuthToken()
,這樣就不需要要求驗證權杖兩次。