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

Uwierzytelnianie pozwala rozpoznać, kim ktoś jest, i jest powszechnie określane jako rejestracja lub logowanie użytkownika. 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 muszą być 2 osobnymi procesami, które zależą od potrzeb aplikacji.

Jeśli aplikacja ma funkcje, które mogą korzystać z danych interfejsu API Google, ale nie są wymagane jako część głównych funkcji aplikacji, zaprojektuj ją tak, aby mogła prawidłowo obsługiwać przypadki, 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.

Dostęp do zakresów, które są potrzebne do korzystania z interfejsów API Google, należy żądać tylko wtedy, gdy użytkownik wykonuje działanie wymagające dostępu do konkretnego interfejsu API. Na przykład prośbę o dostęp do Dysku użytkownika należy wysłać, gdy użytkownik kliknie przycisk Zapisz na Dysku.

Dzięki oddzieleniu autoryzacji od uwierzytelniania możesz uniknąć przytłaczania nowych użytkowników lub dezorientowania ich pytaniami o to, 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 w konsoli Google Cloud

  1. Otwórz projekt w konsoli Cloud lub utwórz projekt, jeśli jeszcze go nie masz.
  2. Na stronie Marka sprawdź, czy 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 podano adresy URL polityki prywatności i warunków korzystania z usługi aplikacji.
  3. Jeśli nie masz jeszcze identyfikatora klienta Androida dla swojej aplikacji, utwórz go na stronie Klienci. Musisz podać nazwę pakietu aplikacji i podpis SHA-1.
    1. Otwórz stronę Klienci.
    2. Kliknij Utwórz klienta.
    3. Wybierz typ aplikacji Android.
    4. Wpisz nazwę klienta OAuth. Ta nazwa jest wyświetlana na stronie Klientów projektu, aby umożliwić identyfikację klienta.
    5. Wpisz nazwę pakietu aplikacji na Androida. Ta wartość jest zdefiniowana w atrybucie package elementu <manifest> w pliku AndroidManifest.xml.
    6. Wpisz odcisk cyfrowy certyfikatu podpisującego SHA-1 dystrybucji aplikacji.
    7. Jeśli Twoja aplikacja korzysta z podpisywania aplikacji przez Google Play, skopiuj odcisk cyfrowy SHA-1 ze strony podpisywania aplikacji w Konsoli Play.
    8. Jeśli zarządzasz własnym magazynem kluczy i kluczami podpisywania, użyj narzędzia keytool dołączonego do Javy, aby wyświetlić informacje o certyfikacie w formacie zrozumiałym dla człowieka. Skopiuj wartość SHA-1 w sekcji Certificate fingerprints danych wyjściowych narzędzia keytool. Więcej informacji znajdziesz w sekcji Uwierzytelnianie klienta w dokumentacji interfejsów API Google na Androida.
    9. (Opcjonalnie) Potwierdź własność aplikacji na Androida.
  4. Jeśli nie masz jeszcze identyfikatora klienta „Aplikacja internetowa”, utwórz go na stronie Klienci. Możesz na razie zignorować pola „Autoryzowane źródła JavaScriptu” i „Autoryzowane identyfikatory URI przekierowania”. Ten identyfikator klienta będzie używany do identyfikowania serwera backendu podczas komunikacji z usługami uwierzytelniania Google.
    1. Otwórz stronę Klienci.
    2. Kliknij Utwórz klienta.
    3. Wybierz typ Aplikacja internetowa.

Potwierdzanie prawa własności do aplikacji

Możesz zweryfikować własność aplikacji, aby zmniejszyć ryzyko podszywania się pod nią.

Aby dokończyć proces weryfikacji, możesz użyć konta dewelopera w Google Play, jeśli je masz, a Twoja aplikacja jest zarejestrowana w Konsoli Google Play. Aby weryfikacja się powiodła, muszą zostać spełnione te wymagania:

  • Musisz mieć zarejestrowaną aplikację w Konsoli Google Play z tą samą nazwą pakietu i odciskiem cyfrowym certyfikatu podpisywania SHA-1 co klient OAuth na Androida, dla którego przeprowadzasz weryfikację.
  • Musisz mieć uprawnienia administratora w odniesieniu do aplikacji w Konsoli Google Play. Dowiedz się więcej o zarządzaniu dostępem w Konsoli Google Play.

W sekcji Potwierdź własność aplikacji klienta Androida kliknij przycisk Potwierdź własność, aby zakończyć proces weryfikacji.

Jeśli weryfikacja się powiedzie, wyświetli się powiadomienie z potwierdzeniem. W przeciwnym razie pojawi się komunikat o błędzie.

Aby rozwiązać problem z nieudaną weryfikacją:

  • Upewnij się, że aplikacja, którą weryfikujesz, jest zarejestrowana w Konsoli Google Play.
  • Sprawdź, czy masz uprawnienia administratora w odniesieniu do aplikacji w Konsoli Google Play.

Deklarowanie zależności

W pliku build.gradle modułu zadeklaruj zależności, używając najnowszej wersji biblioteki Google Identity Services.

dependencies {
  // ... other dependencies

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

Prośba o uprawnienia wymagane do działań użytkownika

Za każdym razem, gdy użytkownik wykonuje działanie, które wymaga dodatkowego zakresu, wywołaj AuthorizationClient.authorize(). Jeśli na przykład użytkownik wykonuje działanie, które wymaga dostępu do miejsca na Dysku, 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.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));

Podczas definiowania ActivityResultLauncher obsłuż odpowiedź w sposób pokazany w poniższym fragmencie kodu, w którym 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 (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
            }
        });
}

Jeśli uzyskujesz dostęp do interfejsów API Google po stronie serwera, wywołaj metodę getServerAuthCode() z AuthorizationResult, aby uzyskać kod autoryzacji, który wyślesz 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.

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

Aby unieważnić wcześniej przyznany dostęp, wywołaj metodę AuthorizationClient.revokeAccess(). Jeśli na przykład użytkownik usuwa swoje konto z Twojej aplikacji, a aplikacja miała wcześniej dostęp do DriveScopes.DRIVE_FILE, użyj tego kodu, aby go unieważnić:

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

Czyszczenie pamięci podręcznej 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ę nieprawidłowe z innych powodów. Jeśli podczas używania tokena otrzymasz błąd IllegalStateException, wyczyść lokalną pamięć podręczną, aby mieć pewność, że następne żądanie autoryzacji tokena dostępu zostanie wysłane do serwera 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));

Uzyskiwanie informacji o użytkowniku podczas autoryzacji

Odpowiedź autoryzacji nie zawiera żadnych informacji o użytym koncie użytkownika, a jedynie 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że być używana do uzyskiwania dostępu do plików na dysku użytkownika. Aby uzyskać informacje takie jak nazwa użytkownika lub adres e-mail, masz te opcje:

  • Zanim poprosisz o autoryzację, zaloguj użytkownika na jego konto Google za pomocą interfejsów Credential Manager API. Odpowiedź uwierzytelniająca z Credential Manager zawiera informacje o użytkowniku, takie jak adres e-mail, i ustawia domyślne konto aplikacji na wybrane konto. W razie potrzeby możesz śledzić to konto w aplikacji. Kolejna prośba o autoryzację używa tego konta jako domyślnego i pomija krok wyboru konta w procesie autoryzacji. Aby użyć innego konta do autoryzacji, zapoznaj się z artykułem Autoryzacja z konta innego niż domyślne.

  • W żądaniu autoryzacji oprócz zakresów, których potrzebujesz (np. Drive scope), poproś o zakresy userinfo, profile i openid. Po zwróceniu tokena dostępu pobierz informacje o użytkowniku, wysyłając żądanie HTTP GET do punktu końcowego informacji o użytkowniku OAuth (https://www.googleapis.com/oauth2/v3/userinfo) za pomocą preferowanej biblioteki HTTP i dołączając token dostępu otrzymany w nagłówku, co odpowiada temu poleceniu curl:

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

    Odpowiedzią jest UserInfo, ograniczony do żądanych zakresów, sformatowany w formacie JSON.

Autoryzacja z konta innego niż domyślne

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

Utrzymywanie stałego dostępu do danych użytkownika

Jeśli potrzebujesz dostępu do danych użytkownika z poziomu aplikacji, wywołaj metodę AuthorizationClient.authorize() raz. W kolejnych sesjach, dopóki przyznane uprawnienia nie zostaną usunięte przez użytkownika, wywołuj tę samą metodę, aby uzyskać token dostępu i osiągnąć swoje cele bez interakcji z użytkownikiem. Jeśli jednak potrzebujesz dostępu do danych użytkownika w trybie offline z serwera backendu, musisz poprosić o inny typ tokena, nazywany „tokenem odświeżania”.

Tokeny dostępu są celowo zaprojektowane tak, aby były krótkotrwałe i miały okres ważności wynoszący 1 godzinę. Jeśli token dostępu zostanie przechwycony lub przejęty, jego ograniczony okres ważności minimalizuje potencjalne nadużycia. Po upływie tego czasu token traci ważność, 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 zachować stał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 krótkotrwałego tokena dostępu z serwera autoryzacji, gdy stary token dostępu wygaśnie, bez interakcji użytkownika.

Aby uzyskać token odświeżania, musisz najpierw uzyskać kod autoryzacji podczas kroku autoryzacji w aplikacji, prosząc o „dostęp offline”, a następnie wymienić kod autoryzacji na token odświeżania na serwerze. Długoterminowe tokeny odświeżania należy przechowywać na serwerze w bezpieczny sposób, ponieważ można ich wielokrotnie używać do uzyskiwania nowych tokenów dostępu. Dlatego ze względów bezpieczeństwa zdecydowanie odradzamy przechowywanie tokenów odświeżania na urządzeniu. 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 go wymienić na krótkoterminowy token dostępu na serwerze i długoterminowy token odświeżania, wykonując czynności opisane w przewodniku po autoryzacji konta. Ta wymiana powinna odbywać się tylko w 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.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"));

Poniższy fragment kodu zakłada, że autoryzacja jest uruchamiana 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
                }
            });
}