Zezwalanie na dostęp do danych użytkowników Google

Uwierzytelnianie potwierdza tożsamość użytkownika i jest często określane jako rejestracja lub logowanie. Autoryzacja to proces przyznawania lub odrzucania dostępu do danych lub zasobów. Na przykład aplikacja prosi użytkownika o zgodę na dostęp do jego Dysku Google.

Wywołania uwierzytelniania i autoryzacji powinny być 2 osobnymi i odrębnymi procesami, które zależą od potrzeb aplikacji.

Jeśli Twoja aplikacja ma funkcje, które mogą korzystać z danych interfejsu Google API, ale nie są wymagane jako podstawowe funkcje aplikacji, zaprojektuj ją tak, aby mogła sobie poradzić w sytuacjach, gdy dane interfejsu API są niedostępne. Możesz na przykład ukryć listę ostatnio zapisanych plików, jeśli użytkownik nie przyznał dostępu do Dysku.

Prośby o dostęp do zakresów, które są potrzebne do uzyskania dostępu do interfejsów API Google, należy wysyłać tylko wtedy, gdy użytkownik wykona działanie wymagające dostępu do konkretnego interfejsu API. Na przykład należy poprosić o uprawnienia dostępu do Dysku użytkownika za każdym razem, gdy kliknie on przycisk „Zapisz na Dysku”.

Oddzielenie autoryzacji od uwierzytelniania pozwala uniknąć przytłoczenia nowych użytkowników lub wprowadzenia ich w błąd co do tego, dlaczego są proszeni o określone uprawnienia.

Do uwierzytelniania zalecamy używanie interfejsu Credential Manager API. Do autoryzowania działań, które wymagają dostępu do danych użytkownika przechowywanych przez Google, zalecamy używanie AuthorizationClient.

Konfigurowanie projektu

  1. Otwórz projekt w  lub utwórz projekt, jeśli jeszcze go nie masz.
  2. Na stronie upewnij się, że wszystkie informacje są kompletne i prawidłowe.
    1. Upewnij się, że aplikacja ma przypisaną prawidłową nazwę, logo i stronę główną. Te wartości będą wyświetlane użytkownikom na ekranie zgody funkcji Zaloguj się przez Google podczas rejestracji oraz na ekranie aplikacji i usług innych firm.
    2. Upewnij się, że masz podane adresy URL polityki prywatności i warunków korzystania z usługi w swojej aplikacji.
  3. W utwórz identyfikator klienta Androida dla swojej aplikacji, jeśli jeszcze go nie masz. Musisz podać nazwę pakietu aplikacji i podpis SHA-1.
    1. Wejdź na stronę .
    2. Kliknij Utwórz klienta.
    3. Wybierz typ aplikacji Android.
  4. W sekcji utwórz nowy identyfikator klienta „Aplikacja internetowa”, jeśli jeszcze go nie masz. Pola „Autoryzowane źródła JavaScript” i „Autoryzowane identyfikatory URI przekierowania” możesz na razie zignorować. Ten identyfikator klienta będzie używany do identyfikowania serwera backendu podczas komunikacji z usługami uwierzytelniania Google.
    1. Wejdź na stronę .
    2. Kliknij Utwórz klienta.
    3. Wybierz typ Aplikacja internetowa.

Deklarowanie zależności

W pliku build.gradle modułu zadeklaruj zależności, używając najnowszej wersji biblioteki usług tożsamości Google.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.4.0"
}

Prośby o uprawnienia wymagane przez działania użytkownika

Za każdym razem, gdy użytkownik wykona działanie wymagające dodatkowego zakresu, wywołaj funkcję AuthorizationClient.authorize(). Jeśli na przykład użytkownik wykona działanie, które wymaga dostępu do pamięci aplikacji Dysk, wykonaj te czynności:

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));

Podczas definiowania ActivityResultLauncher obsłuż odpowiedź w sposób pokazany w tym fragmencie kodu. Zakładamy, że jest to robione we fragmencie. Kod sprawdza, czy wymagane uprawnienia zostały przyznane, a następnie wykonuje działanie użytkownika.

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
            }
        });
}

Jeśli uzyskujesz dostęp do interfejsów API Google po stronie serwera, wywołaj metodę getServerAuthCode()AuthorizationResult, aby uzyskać kod autoryzacji, który wysyłasz do backendu w celu wymiany na token dostępu i token odświeżania. Więcej informacji znajdziesz w artykule Utrzymywanie stałego dostępu do danych użytkownika.

Odebranie uprawnień do danych lub zasobów użytkownika

Aby cofnąć wcześniej przyznany dostęp, zadzwoń pod numer AuthorizationClient.revokeAccess(). Jeśli na przykład użytkownik usuwa swoje konto z Twojej aplikacji, a aplikacja miała wcześniej przyznany dostęp do DriveScopes.DRIVE_FILE, użyj tego kodu, aby cofnąć dostęp:

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));

Wyczyść pamięć podręczną tokenów

Tokeny dostępu OAuth są buforowane lokalnie po otrzymaniu z serwera, co przyspiesza dostęp i zmniejsza liczbę wywołań sieciowych. Te tokeny są automatycznie usuwane z pamięci podręcznej po wygaśnięciu, ale mogą też stać się nieważne z innych powodów. Jeśli podczas używania tokena otrzymasz IllegalStateException, wyczyść lokalną pamięć podręczną, aby mieć pewność, że następne żądanie autoryzacji tokena dostępu zostanie wysłane na serwer OAuth. Ten fragment kodu usuwa invalidAccessToken z pamięci podręcznej:

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));

Pobieranie informacji o użytkowniku podczas autoryzacji

Odpowiedź autoryzacyjna nie zawiera żadnych informacji o koncie użytkownika, które zostało użyte. Zawiera tylko token dla żądanych zakresów. Na przykład odpowiedź na żądanie uzyskania tokena dostępu do Dysku Google użytkownika nie ujawnia tożsamości konta wybranego przez użytkownika, mimo że można jej użyć do uzyskania dostępu do plików na Dysku użytkownika. Aby uzyskać informacje takie jak imię i nazwisko lub adres e-mail użytkownika, możesz skorzystać z tych opcji:

  • Zaloguj użytkownika za pomocą jego konta Google, korzystając z interfejsów Credential Manager API, zanim poprosisz go o autoryzację. Odpowiedź uwierzytelniająca z Menedżera danych logowania zawiera informacje o użytkowniku, takie jak adres e-mail, a także ustawia wybrane konto jako domyślne konto aplikacji. W razie potrzeby możesz śledzić to konto w aplikacji. Kolejna prośba o autoryzację używa konta jako domyślnego i pomija krok wyboru konta w procesie autoryzacji. Aby użyć innego konta do autoryzacji, zapoznaj się z sekcją Autoryzacja z użyciem konta innego niż domyślne.

  • W żądaniu autoryzacji oprócz zakresów, które chcesz uzyskać (np. Drive scope), poproś o zakresy userinfo, profileopenid. Po otrzymaniu tokena dostępu pobierz informacje o użytkowniku, wysyłając GETżądanie HTTP do punktu końcowego OAuth userinfocurl (https://www.googleapis.com/oauth2/v3/userinfo) za pomocą wybranej biblioteki HTTP i dołączając otrzymany token dostępu w nagłówku, co jest równoważne z tym poleceniem curl:

    curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
    

    Odpowiedź to UserInfo ograniczony do zakresów, o które prosisz, w formacie JSON.

Autoryzacja z konta, które nie jest domyślne

Jeśli do uwierzytelniania używasz Menedżera danych logowania i uruchomisz AuthorizationClient.authorize(), domyślne konto aplikacji zostanie ustawione na konto wybrane przez użytkownika. Oznacza to, że wszystkie kolejne wywołania autoryzacji będą korzystać z tego konta domyślnego. Aby wymusić wyświetlenie selektora kont, wyloguj użytkownika z aplikacji za pomocą interfejsu clearCredentialState() API z Credential Manager.

zachować stały dostęp do danych użytkownika;

Jeśli chcesz uzyskać dostęp do danych użytkownika z poziomu aplikacji, wywołaj funkcję AuthorizationClient.authorize() tylko raz. W kolejnych sesjach i dopóki użytkownik nie usunie przyznanych uprawnień, wywołuj tę samą metodę, aby uzyskać token dostępu i osiągnąć swoje cele bez interakcji z użytkownikiem. Jeśli natomiast chcesz uzyskać dostęp do danych użytkownika w trybie offline z serwera backendu, musisz poprosić o inny typ tokena, zwany „tokenem odświeżania”.

Tokeny dostępu są celowo projektowane tak, aby miały krótki okres ważności, który wynosi 1 godzinę. Jeśli token dostępu zostanie przechwycony lub przejęty, jego ograniczony okres ważności zminimalizuje potencjalne nadużycia. Po wygaśnięciu token staje się nieważny, a wszelkie próby jego użycia są odrzucane przez serwer zasobów. Tokeny dostępu mają krótki okres ważności, dlatego serwery używają tokenów odświeżania, aby utrzymać ciągły dostęp do danych użytkownika. Tokeny odświeżania to tokeny o długim okresie ważności, które są używane przez klienta do żądania od serwera autoryzacji krótkotrwałego tokena dostępu po wygaśnięciu starego tokena dostępu, bez interakcji z użytkownikiem.

Aby uzyskać token odświeżania, musisz najpierw uzyskać kod autoryzacji podczas autoryzacji w aplikacji, prosząc o „dostęp offline”, a następnie wymienić go na token odświeżania na serwerze. Długoterminowe tokeny odświeżania muszą być bezpiecznie przechowywane na serwerze, ponieważ można ich wielokrotnie używać do uzyskiwania nowych tokenów dostępu. Z tego powodu zdecydowanie odradzamy przechowywanie tokenów odświeżania na urządzeniu ze względu na kwestie bezpieczeństwa. Zamiast tego powinny być przechowywane na serwerach backendu aplikacji, gdzie następuje wymiana na token dostępu.

Po wysłaniu kodu autoryzacji na serwer backendu aplikacji możesz wymienić go na serwerze na krótkoterminowy token dostępu i długoterminowy token odświeżania, wykonując czynności opisane w przewodniku po autoryzacji konta. Wymiana powinna odbywać się tylko na backendzie aplikacji.

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"));

Poniższy fragment zakłada, że autoryzacja jest rozpoczynana z fragmentu.

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
                }
            });
}