驗證程序會確認使用者身分,通常稱為使用者註冊或登入。授權是指授予或拒絕資料或資源存取權的程序。舉例來說,您的應用程式要求使用者同意存取其 Google 雲端硬碟。
驗證和授權呼叫必須是兩個獨立且不同的流程,具體取決於應用程式的需求。
如果應用程式的功能可以使用 Google API 資料,但並非應用程式核心功能的一部分,請將應用程式設計為能夠妥善處理無法存取 API 資料的情況。舉例來說,如果使用者未授予雲端硬碟存取權,您可以隱藏最近儲存的檔案清單。
只有在使用者執行需要存取特定 API 的動作時,您才應要求存取 Google API 所需的範圍。舉例來說,每當使用者輕觸「儲存至雲端硬碟」按鈕時,您就應該要求存取使用者雲端硬碟的權限。
將授權與驗證分開,可避免新使用者不知所措,或不清楚為何需要授予特定權限。
如要進行驗證,建議使用 Credential Manager API。如要授權需要存取 Google 儲存的使用者資料的操作,建議使用 AuthorizationClient。
設定 Google Cloud 控制台專案
- 在 Cloud 控制台中開啟專案,或建立新專案。
- 在「品牌宣傳」頁面,確認所有資訊完整正確。
- 確認應用程式已指派正確的應用程式名稱、應用程式標誌和應用程式首頁。使用者註冊時,會在「使用 Google 帳戶登入」同意畫面和第三方應用程式和服務畫面看到這些值。
- 請務必指定應用程式的隱私權政策和服務條款網址。
- 在「Clients」頁面中,為應用程式建立 Android 用戶端 ID (如果尚未建立)。您必須指定應用程式的套件名稱和 SHA-1 簽章。
- 前往「客戶」頁面。
- 按一下「Create client」(建立用戶端)。
- 選取「Android」應用程式類型。
- 輸入 OAuth 用戶端名稱。這個名稱會顯示在專案的「Clients」(用戶端) 頁面,用於識別用戶端。
- 輸入 Android 應用程式的套件名稱。這個值定義在
AndroidManifest.xml檔案中<manifest>元素的package屬性 中。 - 輸入應用程式發布版本的 SHA-1 簽署憑證指紋。
- 如果應用程式使用 Google Play 應用程式簽署功能,請從 Play 管理中心的應用程式簽署頁面複製 SHA-1 指紋。
- 如果您自行管理 KeyStore 和簽署金鑰,請使用 Java 隨附的 keytool 公用程式,以人類可讀的格式列印憑證資訊。複製 keytool 輸出內容中
Certificate fingerprints區段的SHA-1值。詳情請參閱 Google API for Android 說明文件中的「驗證用戶端」。 - (選用) 驗證 Android 應用程式的擁有權。
- 在「Clients」(用戶端) 頁面中,建立新的「網頁應用程式」用戶端 ID (如果尚未建立)。您可以暫時忽略「Authorized JavaScript Origins」和「Authorized redirect URIs」欄位。這個用戶端 ID 會用於識別後端伺服器,以便與 Google 的驗證服務通訊。
- 前往「客戶」頁面。
- 按一下「Create client」(建立用戶端)。
- 選取「網頁應用程式」類型。
驗證應用程式擁有權
您可以驗證應用程式擁有權,降低應用程式遭冒用的風險。
如要完成驗證程序,您可以使用 Google Play 開發人員帳戶 (如有),並在 Google Play 管理中心註冊應用程式。必須符合下列規定,才能順利完成驗證:
- 您必須在 Google Play 管理中心註冊應用程式,且套件名稱和 SHA-1 簽署憑證指紋,必須與要完成驗證的 Android OAuth 用戶端相同。
- 您必須在 Google Play 管理中心具備應用程式的管理員權限。 進一步瞭解 Google Play 管理中心的存取權管理。
在 Android 用戶端的「Verify App Ownership」部分,按一下「Verify Ownership」按鈕,完成驗證程序。
如果驗證成功,系統會顯示通知,確認驗證程序成功。否則系統會顯示錯誤提示。
如要修正驗證失敗的問題,請嘗試下列做法:
- 確認要驗證的應用程式已在 Google Play 管理中心註冊。
- 確認您在 Google Play 管理中心具備該應用程式的管理員權限。
宣告依附元件
在模組的 build.gradle 檔案中,使用最新版 Google Identity 服務程式庫宣告依附元件。
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:22.0.0"
}
要求使用者動作所需的權限
每當使用者執行需要額外範圍的操作時,請呼叫 AuthorizationClient.authorize()。舉例來說,如果使用者執行的動作需要存取雲端硬碟應用程式儲存空間,請按照下列步驟操作:
Kotlin
val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequestBuilder.build())
.addOnSuccessListener { authorizationResult ->
if (authorizationResult.hasResolution()) {
val pendingIntent = authorizationResult.pendingIntent
// Access needs to be granted by the user
startAuthorizationIntent.launch(IntentSenderRequest.Builder(pendingIntent!!.intentSender).build())
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
if (authorizationResult.hasResolution()) {
// Access needs to be granted by the user
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
} else {
// Access was previously granted, continue with user action
saveToDriveAppFolder(authorizationResult);
}
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
定義 ActivityResultLauncher 時,請按照下列程式碼片段所示處理回應,我們假設這是在片段中完成。程式碼會檢查是否已成功授予必要權限,然後執行使用者動作。
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
// extract the result
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
// extract the result
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// continue with user action
saveToDriveAppFolder(authorizationResult);
} catch (ApiException e) {
// log exception
}
});
}
如要在伺服器端存取 Google API,請從 AuthorizationResult 呼叫 getServerAuthCode() 方法,取得授權碼並傳送至後端,以交換存取權杖和更新權杖。詳情請參閱「持續存取使用者資料」。
撤銷使用者資料或資源的權限
如要撤銷先前授予的存取權,請呼叫 AuthorizationClient.revokeAccess()。舉例來說,如果使用者從應用程式中移除帳戶,而應用程式先前已獲授予 DriveScopes.DRIVE_FILE 存取權,請使用下列程式碼撤銷存取權:
Kotlin
val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build()
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
.addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }
Java
List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
.setAccount(account)
.setScopes(requestedScopes)
.build();
Identity.getAuthorizationClient(activity)
.revokeAccess(revokeAccessRequest)
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));
清除權杖快取
OAuth 存取權杖從伺服器收到後會在本機快取,加快存取速度並減少網路呼叫。這些權杖會在到期時自動從快取中刪除,但也可能因其他原因而失效。如果在使用權杖時收到 IllegalStateException,請清除本機快取,確保下一個存取權杖授權要求會傳送至 OAuth 伺服器。下列程式碼片段會從本機快取中移除 invalidAccessToken:
Kotlin
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
.addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }
Java
Identity.getAuthorizationClient(activity)
.clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
.addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
.addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));
在授權期間取得使用者資訊
授權回應不含所用使用者帳戶的任何資訊,僅含所要求範圍的權杖。舉例來說,如果取得存取權杖是為了存取使用者的 Google 雲端硬碟,則回應不會透露使用者選取的帳戶身分,即使該權杖可用於存取使用者雲端硬碟中的檔案。如要取得使用者名稱或電子郵件等資訊,請採取下列做法:
請先使用 Credential Manager API 讓使用者登入 Google 帳戶,再要求授權。Credential Manager 的驗證回應包含使用者資訊 (例如電子郵件地址),並將應用程式的預設帳戶設為所選帳戶。如有需要,您可以在應用程式中追蹤這個帳戶。後續的授權要求會使用該帳戶做為預設帳戶,並略過授權流程中的帳戶選取步驟。如要使用其他帳戶進行授權,請參閱「使用非預設帳戶授權」。
在授權要求中,除了您想要的範圍 (例如
Drive scope) 之外,請一併要求userinfo、profile和openid範圍。傳回存取權杖後,請使用偏好的 HTTP 程式庫,向 OAuth userinfo 端點 (https://www.googleapis.com/oauth2/v3/userinfo) 發出GETHTTP 要求,並在標頭中加入收到的存取權杖,取得使用者資訊。這相當於下列curl指令:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"回應為
UserInfo,僅限於要求的範圍,並以 JSON 格式呈現。
非預設帳戶授權
如果您使用 Credential Manager 進行驗證,並執行 AuthorizationClient.authorize(),應用程式的預設帳戶會設為使用者選取的帳戶。這表示任何後續的授權呼叫都會使用這個預設帳戶。如要強制顯示帳戶選取器,請使用 Credential Manager 的 clearCredentialState() API,將使用者登出應用程式。
持續存取使用者資料
如需從應用程式存取使用者資料,請呼叫 AuthorizationClient.authorize() 一次。在後續工作階段中,只要使用者未移除授予的權限,請呼叫相同方法取得存取權杖,即可達成目標,不需與使用者互動。另一方面,如果您需要從後端伺服器以離線模式存取使用者資料,則必須要求另一種稱為「更新權杖」的權杖。
存取權杖的設計刻意縮短效期,只有一小時。如果存取權杖遭到攔截或外洩,其有限的有效期限可盡量減少潛在的濫用行為。權杖到期後就會失效,資源伺服器會拒絕任何使用權杖的嘗試。由於存取權杖的效期較短,伺服器會使用更新權杖,持續存取使用者資料。更新權杖是壽命較長的權杖,用戶端會使用這類權杖,在舊存取權杖過期時,向授權伺服器要求短效存取權杖,不需使用者互動。
如要取得更新權杖,您必須先在應用程式的授權步驟中要求「離線存取權」,取得授權碼 (或授權碼),然後在伺服器上將授權碼換成更新權杖。請務必將長期更新權杖安全地儲存在伺服器上,因為這類權杖可重複用於取得新的存取權杖。因此,基於安全考量,強烈建議不要將更新權杖儲存在裝置上。而是應儲存在應用程式的後端伺服器,也就是交換存取權杖的地方。
授權碼傳送至應用程式的後端伺服器後,您可以按照帳戶授權指南中的步驟,在伺服器上將授權碼換成短期存取權杖和長期更新權杖。這項交換作業只能在應用程式後端進行。
Kotlin
// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build()
Identity.getAuthorizationClient(activity)
.authorize(authorizationRequest)
.addOnSuccessListener { authorizationResult ->
startAuthorizationIntent.launch(IntentSenderRequest.Builder(
pendingIntent!!.intentSender
).build())
}
.addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }
Java
// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
.setRequestedScopes(requestedScopes)
.requestOfflineAccess(serverClientId)
.build();
Identity.getAuthorizationClient(getContext())
.authorize(authorizationRequest)
.addOnSuccessListener(authorizationResult -> {
startAuthorizationIntent.launch(
new IntentSenderRequest.Builder(
authorizationResult.getPendingIntent().getIntentSender()
).build()
);
})
.addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));
下列程式碼片段假設授權是從片段啟動。
Kotlin
private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// ...
startAuthorizationIntent =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
try {
val authorizationResult = Identity.getAuthorizationClient(requireContext())
.getAuthorizationResultFromIntent(activityResult.data)
// short-lived access token
accessToken = authorizationResult.accessToken
// store the authorization code used for getting a refresh token safely to your app's backend server
val authCode: String = authorizationResult.serverAuthCode
storeAuthCodeSafely(authCode)
} catch (e: ApiException) {
// log exception
}
}
}
Java
private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
registerForActivityResult(
new ActivityResultContracts.StartIntentSenderForResult(),
activityResult -> {
try {
AuthorizationResult authorizationResult =
Identity.getAuthorizationClient(requireActivity())
.getAuthorizationResultFromIntent(activityResult.getData());
// short-lived access token
accessToken = authorizationResult.getAccessToken();
// store the authorization code used for getting a refresh token safely to your app's backend server
String authCode = authorizationResult.getServerAuthCode()
storeAuthCodeSafely(authCode);
} catch (ApiException e) {
// log exception
}
});
}