驗證是確認使用者身分的程序,通常稱為使用者註冊或登入。授權是授予或拒絕存取資料或資源的程序。舉例來說,您的應用程式會要求使用者同意存取 Google 雲端硬碟。
驗證和授權呼叫應根據應用程式需求,分成兩個不同的流程。
如果應用程式的功能可以使用 Google API 資料,但並非應用程式核心功能的一部分,您應設計應用程式,以便順利處理無法存取 API 資料的情況。舉例來說,如果使用者尚未授予雲端硬碟存取權,您可能會隱藏最近儲存的檔案清單。
只有在使用者執行需要存取特定 Google API 的動作時,您才應要求存取所需範圍。舉例來說,每當使用者輕觸「儲存到雲端硬碟」按鈕時,您都應要求存取使用者雲端硬碟的權限。
將授權與驗證分開,可避免新使用者感到困惑,或不明白為何系統要求他們提供特定權限。
建議使用 Credential Manager API 進行驗證。如要授權需要存取 Google 儲存使用者資料的動作,建議使用 AuthorizationClient。
設定專案
- 在 中開啟專案,或建立專案 (如果尚未建立)。
- 在「」中,確認所有資訊完整且正確。
- 在 中,為應用程式建立 Android 用戶端 ID (如果還沒有的話)。您需要指定應用程式的套件名稱和 SHA-1 簽章。
- 在 中,建立新的「網頁應用程式」用戶端 ID (如果尚未建立)。目前可以忽略「已授權的 JavaScript 來源」和「已授權的重新導向 URI」欄位。後端伺服器與 Google 驗證服務通訊時,會使用這個用戶端 ID 識別自己。
宣告依附元件
在模組的 build.gradle 檔案中,使用最新版本的 Google Identity Services 程式庫宣告依附元件。
dependencies {
// ... other dependencies
implementation "com.google.android.gms:play-services-auth:21.4.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.launchIntentSenderRequest.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 (ApiException e) {
// 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 帳戶,再要求授權。憑證管理工具的驗證回應會包含電子郵件地址等使用者資訊,並將應用程式的預設帳戶設為所選帳戶;如有需要,您可以在應用程式中追蹤這個帳戶。後續的授權要求會使用這個帳戶做為預設帳戶,並略過授權流程中的帳戶選取步驟。如要使用其他帳戶授權,請參閱「從非預設帳戶授權」。
在授權要求中,除了您想要的範圍 (例如
Drive scope
) 之外,也請要求userinfo
、profile
和openid
範圍。存取權杖傳回後,請使用偏好的 HTTP 程式庫,對 OAuth 使用者資訊端點 (https://www.googleapis.com/oauth2/v3/userinfo) 發出GET
HTTP 要求,並在標頭中加入您收到的存取權杖,即可取得使用者資訊,相當於下列curl
指令:curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
回覆是
UserInfo
,僅限於所要求的範圍,並以 JSON 格式提供。
從非預設帳戶授權
如果您使用憑證管理員進行驗證並執行 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.launchIntentSenderRequest.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
}
});
}