Zugriff auf Google-Nutzerdaten autorisieren

Bei der Authentifizierung wird festgestellt, wer jemand ist. Sie wird häufig als Nutzerregistrierung oder ‑anmeldung bezeichnet. Die Autorisierung ist der Prozess, bei dem der Zugriff auf Daten oder Ressourcen gewährt oder abgelehnt wird. Ihre App fordert beispielsweise die Einwilligung des Nutzers an, um auf Google Drive zuzugreifen.

Authentifizierungs- und Autorisierungsaufrufe sollten zwei separate und unterschiedliche Abläufe sein, die auf den Anforderungen der App basieren.

Wenn Ihre App Funktionen hat, die Google API-Daten nutzen können, aber nicht zu den Kernfunktionen Ihrer App gehören, sollten Sie Ihre App so gestalten, dass sie Fälle, in denen API-Daten nicht zugänglich sind, problemlos verarbeiten kann. So können Sie beispielsweise eine Liste der zuletzt gespeicherten Dateien ausblenden, wenn der Nutzer keinen Zugriff auf Drive gewährt hat.

Sie sollten nur dann Zugriff auf Bereiche anfordern, die Sie für den Zugriff auf Google APIs benötigen, wenn der Nutzer eine Aktion ausführt, die Zugriff auf eine bestimmte API erfordert. Sie sollten beispielsweise die Berechtigung für den Zugriff auf Google Drive des Nutzers anfordern, wenn der Nutzer auf die Schaltfläche „In Drive speichern“ tippt.

Wenn Sie die Autorisierung von der Authentifizierung trennen, können Sie vermeiden, dass neue Nutzer überfordert werden oder dass Nutzer verwirrt sind, warum sie nach bestimmten Berechtigungen gefragt werden.

Für die Authentifizierung empfehlen wir die Credential Manager API. Zum Autorisieren von Aktionen, für die Zugriff auf von Google gespeicherte Nutzerdaten erforderlich ist, empfehlen wir die Verwendung von AuthorizationClient.

-Projekt einrichten

  1. Öffnen Sie Ihr Projekt in der oder erstellen Sie ein Projekt, falls Sie noch keines haben.
  2. Prüfen Sie, ob alle Informationen im vollständig und korrekt sind.
    1. Ihrer App muss ein korrekter App-Name, ein korrektes App-Logo und eine korrekte App-Startseite zugewiesen sein. Diese Werte werden Nutzern bei der Registrierung auf dem Einwilligungsbildschirm „Über Google anmelden“ und auf dem Bildschirm Drittanbieter-Apps und ‑Dienste angezeigt.
    2. Achten Sie darauf, dass Sie die URLs der Datenschutzerklärung und der Nutzungsbedingungen Ihrer App angegeben haben.
  3. Erstellen Sie in der eine Android-Client-ID für Ihre App, falls Sie noch keine haben. Sie müssen den Paketnamen und die SHA1-Signatur Ihrer App angeben.
    1. Rufen Sie auf.
    2. Klicken Sie auf Create client.
    3. Wählen Sie den Anwendungstyp Android aus.
  4. Erstellen Sie in der eine neue Client-ID für Webanwendungen, falls Sie das noch nicht getan haben. Sie können die Felder „Autorisierte JavaScript-Quellen“ und „Autorisierte Weiterleitungs-URIs“ vorerst ignorieren. Diese Client-ID wird verwendet, um Ihren Backend-Server zu identifizieren, wenn er mit den Authentifizierungsdiensten von Google kommuniziert.
    1. Rufen Sie auf.
    2. Klicken Sie auf Create client.
    3. Wählen Sie den Typ Webanwendung aus.

Abhängigkeiten deklarieren

Deklarieren Sie in der build.gradle-Datei Ihres Moduls Abhängigkeiten mit der neuesten Version der Google Identity Services-Bibliothek.

dependencies {
  // ... other dependencies

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

Berechtigungen anfordern, die für Nutzeraktionen erforderlich sind

Immer wenn ein Nutzer eine Aktion ausführt, für die zusätzliche Bereiche erforderlich sind, rufen Sie AuthorizationClient.authorize() auf. Wenn ein Nutzer beispielsweise eine Aktion ausführt, für die Zugriff auf den Speicher der Drive App erforderlich ist, gehen Sie so vor:

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

Wenn Sie ActivityResultLauncher definieren, behandeln Sie die Antwort wie im folgenden Snippet gezeigt. Wir gehen davon aus, dass dies in einem Fragment erfolgt. Der Code prüft, ob die erforderlichen Berechtigungen erteilt wurden, und führt dann die Nutzeraktion aus.

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

Wenn Sie serverseitig auf Google APIs zugreifen, rufen Sie die Methode getServerAuthCode() aus AuthorizationResult auf, um einen Autorisierungscode zu erhalten, den Sie an Ihr Backend senden, um ihn gegen ein Zugriffs- und Aktualisierungstoken einzutauschen. Weitere Informationen

Berechtigungen für Nutzerdaten oder Ressourcen widerrufen

Wenn Sie zuvor gewährten Zugriff widerrufen möchten, rufen Sie AuthorizationClient.revokeAccess() auf. Wenn der Nutzer beispielsweise sein Konto aus Ihrer App entfernt und Ihrer App zuvor Zugriff auf DriveScopes.DRIVE_FILE gewährt wurde, können Sie den Zugriff mit dem folgenden Code widerrufen:

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

Token-Cache leeren

OAuth-Zugriffstokens werden nach dem Empfang vom Server lokal im Cache gespeichert, wodurch der Zugriff beschleunigt und die Anzahl der Netzwerkaufrufe reduziert wird. Diese Tokens werden automatisch aus dem Cache gelöscht, wenn sie ablaufen. Sie können aber auch aus anderen Gründen ungültig werden. Wenn Sie beim Verwenden eines Tokens ein IllegalStateException erhalten, leeren Sie den lokalen Cache, damit die nächste Autorisierungsanfrage für ein Zugriffstoken an den OAuth-Server gesendet wird. Im folgenden Snippet wird invalidAccessToken aus dem lokalen Cache entfernt:

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

Nutzerinformationen während der Autorisierung abrufen

Die Autorisierungsantwort enthält keine Informationen zum verwendeten Nutzerkonto, sondern nur ein Token für die angeforderten Bereiche. Die Antwort zum Abrufen eines Zugriffstokens für den Zugriff auf Google Drive eines Nutzers gibt beispielsweise nicht die Identität des vom Nutzer ausgewählten Kontos preis, obwohl damit auf Dateien in Google Drive des Nutzers zugegriffen werden kann. Um Informationen wie den Namen oder die E-Mail-Adresse des Nutzers abzurufen, haben Sie die folgenden Möglichkeiten:

  • Melden Sie den Nutzer mit seinem Google-Konto über die Credential Manager APIs an, bevor Sie ihn um die Autorisierung bitten. Die Authentifizierungsantwort von Credential Manager enthält Nutzerinformationen wie die E-Mail-Adresse und legt das Standardkonto der App auf das ausgewählte Konto fest. Bei Bedarf können Sie dieses Konto in Ihrer App verfolgen. Bei einer nachfolgenden Autorisierungsanfrage wird das Konto als Standard verwendet und der Schritt zur Kontoauswahl im Autorisierungsablauf wird übersprungen. Wenn Sie ein anderes Konto für die Autorisierung verwenden möchten, lesen Sie den Abschnitt Autorisierung über ein anderes Konto als das Standardkonto.

  • Fordern Sie in Ihrer Autorisierungsanfrage zusätzlich zu den gewünschten Bereichen (z. B. Drive scope) die Bereiche userinfo, profile und openid an. Nachdem ein Zugriffstoken zurückgegeben wurde, rufen Sie die Nutzerinformationen ab, indem Sie mit Ihrer bevorzugten HTTP-Bibliothek eine GET-HTTP-Anfrage an den OAuth-Userinfo-Endpunkt (https://www.googleapis.com/oauth2/v3/userinfo) senden und das erhaltene Zugriffstoken im Header angeben. Das entspricht dem folgenden curl-Befehl:

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

    Die Antwort ist das UserInfo, das auf die angeforderten Bereiche beschränkt und im JSON-Format formatiert ist.

Autorisierung über ein Konto, das nicht das Standardkonto ist

Wenn Sie Credential Manager zur Authentifizierung verwenden und AuthorizationClient.authorize() ausführen, wird das Standardkonto Ihrer App auf das vom Nutzer ausgewählte Konto festgelegt. Das bedeutet, dass bei allen nachfolgenden Autorisierungsaufrufen dieses Standardkonto verwendet wird. Wenn Sie die Kontoauswahl erzwingen möchten, melden Sie den Nutzer mit der clearCredentialState() API aus dem Credential Manager aus der App ab.

Kontinuierlichen Zugriff auf die Daten des Nutzers beibehalten

Wenn Sie über Ihre App auf die Daten des Nutzers zugreifen müssen, rufen Sie AuthorizationClient.authorize() einmal auf. In nachfolgenden Sitzungen und solange die erteilten Berechtigungen nicht vom Nutzer entfernt werden, rufen Sie dieselbe Methode auf, um ein Zugriffstoken zu erhalten, mit dem Sie Ihre Ziele ohne Nutzerinteraktion erreichen können. Wenn Sie hingegen im Offlinemodus von Ihrem Backend-Server aus auf die Daten des Nutzers zugreifen müssen, müssen Sie einen anderen Tokentyp anfordern, nämlich ein „Aktualisierungstoken“.

Zugriffstokens sind bewusst kurzlebig und haben eine Gültigkeitsdauer von einer Stunde. Wenn ein Zugriffstoken abgefangen oder kompromittiert wird, minimiert sein begrenzter Gültigkeitszeitraum potenziellen Missbrauch. Nach Ablauf des Tokens wird es ungültig und alle Versuche, es zu verwenden, werden vom Ressourcenserver abgelehnt. Da Zugriffstokens nur kurzlebig sind, verwenden Server Aktualisierungstokens, um den kontinuierlichen Zugriff auf die Daten eines Nutzers aufrechtzuerhalten. Aktualisierungstokens sind Tokens mit einer langen Lebensdauer, die von einem Client verwendet werden, um ein kurzlebiges Zugriffstoken vom Autorisierungsserver anzufordern, wenn das alte Zugriffstoken abgelaufen ist, ohne dass eine Nutzerinteraktion erforderlich ist.

Um ein Aktualisierungstoken zu erhalten, müssen Sie zuerst einen Autorisierungscode während des Autorisierungsschritts in Ihrer App anfordern, indem Sie „Offlinezugriff“ anfordern. Anschließend tauschen Sie den Autorisierungscode auf Ihrem Server gegen ein Aktualisierungstoken ein. Es ist wichtig, langlebige Aktualisierungstokens sicher auf Ihrem Server zu speichern, da sie wiederholt verwendet werden können, um neue Zugriffstokens abzurufen. Aus Sicherheitsgründen wird daher dringend davon abgeraten, Aktualisierungstokens auf dem Gerät zu speichern. Stattdessen sollten sie auf den Backend-Servern der App gespeichert werden, wo der Austausch gegen ein Zugriffstoken erfolgt.

Nachdem der Autorisierungscode an den Backend-Server Ihrer App gesendet wurde, können Sie ihn auf dem Server gegen ein kurzlebiges Zugriffstoken und ein langlebiges Aktualisierungstoken eintauschen. Folgen Sie dazu der Anleitung zur Kontoautorisierung. Dieser Austausch sollte nur im Backend Ihrer App erfolgen.

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

Im folgenden Snippet wird davon ausgegangen, dass die Autorisierung über ein Fragment gestartet wird.

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