Bei OAuth2-Diensten authentifizieren

Diagramm der Authentifizierungstoken-Logik
Abbildung 1: Verfahren zum Abrufen eines gültigen Authentifizierungstokens vom Android Account Manager.

Für den sicheren Zugriff auf einen Onlinedienst müssen sich Nutzer beim Dienst authentifizieren und einen Identitätsnachweis vorlegen. Bei Anwendungen, die auf Drittanbieterdienste zugreifen, ist das Sicherheitsproblem noch komplizierter. Der Nutzer muss nicht nur authentifiziert sein, um auf den Dienst zugreifen zu können. Die Anwendung muss auch autorisiert sein, im Namen des Nutzers zu handeln.

Das Branchenstandard für die Authentifizierung bei Drittanbieterdiensten ist das OAuth2-Protokoll. OAuth2 stellt einen einzelnen Wert bereit, der als Authentifizierungstoken bezeichnet wird und sowohl die Identität des Nutzers als auch die Autorisierung der Anwendung angibt, im Namen des Nutzers zu handeln. In dieser Lektion wird die Verbindung zu einem Google-Server hergestellt, der OAuth2 unterstützt. Obwohl Google-Dienste als Beispiel verwendet werden, funktionieren die gezeigten Techniken auch bei allen Diensten, die das OAuth2-Protokoll korrekt unterstützen.

Die Verwendung von OAuth2 eignet sich für Folgendes:

  • Berechtigung vom Nutzer einholen, um mit seinem Konto auf einen Onlinedienst zuzugreifen
  • Authentifizierung bei einem Online-Dienst im Namen des Nutzers.
  • Authentifizierungsfehler verarbeiten

Informationen sammeln

Um mit der Verwendung von OAuth2 beginnen zu können, müssen Sie einige API-spezifische Informationen zu dem Dienst wissen, auf den Sie zugreifen möchten:

  • Die URL des Dienstes, auf den Sie zugreifen möchten.
  • Der Authentifizierungsbereich. Dabei handelt es sich um einen String, der den spezifischen Zugriffstyp definiert, den Ihre Anwendung anfordert. Beispielsweise ist der Authentifizierungsbereich für den Lesezugriff auf Google Tasks View your tasks und der Authentifizierungsbereich für den Lese-/Schreibzugriff auf Google Tasks Manage your tasks.
  • Eine Client-ID und ein Clientschlüssel. Dabei handelt es sich um Strings, die Ihre Anwendung gegenüber dem Dienst identifizieren. Sie erhalten diese Strings direkt vom Dienstinhaber. Google hat ein Self-Service-System zum Abrufen von Client-IDs und -Secrets.

Internetberechtigung anfordern

Für Apps, die auf Android 6.0 (API-Level 23) und höher ausgerichtet sind, sind für die Methode getAuthToken() selbst keine Berechtigungen erforderlich. Um Vorgänge mit dem Token ausführen zu können, müssen Sie Ihrer Manifestdatei jedoch die Berechtigung INTERNET hinzufügen, wie im folgenden Code-Snippet gezeigt:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Authentifizierungstoken anfordern

Rufen Sie AccountManager.getAuthToken() auf, um das Token abzurufen.

Achtung : Da einige Kontovorgänge unter Umständen Netzwerkkommunikation umfassen, sind die meisten AccountManager-Methoden asynchron. Das bedeutet, dass Sie nicht die gesamte Authentifizierungsarbeit in einer Funktion ausführen müssen, sondern sie als eine Reihe von Callbacks implementieren müssen.

Das folgende Snippet zeigt, wie Sie mit einer Reihe von Callbacks arbeiten, um das Token abzurufen:

Kotlin

val am: AccountManager = AccountManager.get(this)
val options = Bundle()

am.getAuthToken(
        myAccount_,                     // Account retrieved using getAccountsByType()
        "Manage your tasks",            // Auth scope
        options,                        // Authenticator-specific options
        this,                           // Your activity
        OnTokenAcquired(),              // Callback called when a token is successfully acquired
        Handler(OnError())              // Callback called if an error occurs
)

Java

AccountManager am = AccountManager.get(this);
Bundle options = new Bundle();

am.getAuthToken(
    myAccount_,                     // Account retrieved using getAccountsByType()
    "Manage your tasks",            // Auth scope
    options,                        // Authenticator-specific options
    this,                           // Your activity
    new OnTokenAcquired(),          // Callback called when a token is successfully acquired
    new Handler(new OnError()));    // Callback called if an error occurs

In diesem Beispiel ist OnTokenAcquired eine Klasse, die AccountManagerCallback implementiert. AccountManager ruft run() für OnTokenAcquired mit einer AccountManagerFuture auf, die ein Bundle enthält. Wenn der Aufruf erfolgreich war, befindet sich das Token im Bundle.

So rufen Sie das Token aus dem Bundle ab:

Kotlin

private class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        // Get the result of the operation from the AccountManagerFuture.
        val bundle: Bundle = result.getResult()

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN)
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        // Get the result of the operation from the AccountManagerFuture.
        Bundle bundle = result.getResult();

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        ...
    }
}

Wenn alles gut läuft, enthält Bundle ein gültiges Token im Schlüssel KEY_AUTHTOKEN und Sie sind startklar.

Ihre erste Anforderung eines Authentifizierungstokens kann aus mehreren Gründen fehlschlagen:

  • Ein Fehler im Gerät oder Netzwerk hat dazu geführt, dass AccountManager fehlgeschlagen ist.
  • Der Nutzer hat Ihrer App keinen Zugriff auf das Konto gewährt.
  • Die gespeicherten Anmeldedaten reichen für den Zugriff auf das Konto nicht aus.
  • Das im Cache gespeicherte Authentifizierungstoken ist abgelaufen.

Die ersten beiden Fälle können in der Regel ganz einfach verarbeitet werden, indem dem Nutzer einfach eine Fehlermeldung angezeigt wird. Wenn das Netzwerk ausgefallen ist oder der Nutzer keinen Zugriff gewährt, kann Ihre Anwendung nicht viel dagegen tun. Die letzten beiden Fälle sind etwas komplizierter, da von gut funktionierenden Anwendungen erwartet wird, dass sie diese Fehler automatisch verarbeiten.

Der dritte Fehlerfall, also unzureichende Anmeldedaten, wird über den Bundle kommuniziert, den Sie in Ihrem AccountManagerCallback erhalten (OnTokenAcquired im vorherigen Beispiel). Wenn der Bundle ein Intent im Schlüssel KEY_INTENT enthält, teilt Ihnen der Authenticator mit, dass er direkt mit dem Nutzer interagieren muss, bevor er Ihnen ein gültiges Token liefern kann.

Es kann viele Gründe dafür geben, dass der Authenticator ein Intent zurückgibt. Möglicherweise hat sich der Nutzer zum ersten Mal in diesem Konto angemeldet. Möglicherweise ist das Konto des Nutzers abgelaufen und er muss sich noch einmal anmelden oder seine gespeicherten Anmeldedaten sind falsch. Vielleicht erfordert das Konto eine 2-Faktor-Authentifizierung oder die Kamera muss für einen Netzhautscan aktiviert werden. Es spielt keine Rolle, was der Grund ist. Wenn Sie ein gültiges Token benötigen, müssen Sie das Intent auslösen, um es abzurufen.

Kotlin

private inner class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent
        if (launch != null) {
            startActivityForResult(launch, 0)
        }
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        ...
        Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT);
        if (launch != null) {
            startActivityForResult(launch, 0);
            return;
        }
    }
}

Im Beispiel wird startActivityForResult() verwendet. Sie können also das Ergebnis von Intent erfassen, indem Sie onActivityResult() in Ihrer eigenen Aktivität implementieren. Wichtig: Wenn Sie das Ergebnis nicht aus der Antwort Intent des Authenticators erfassen, lässt sich nicht feststellen, ob der Nutzer erfolgreich authentifiziert wurde.

Wenn das Ergebnis RESULT_OK ist, hat die Authentifizierungs-App die gespeicherten Anmeldedaten aktualisiert, sodass sie für die angeforderte Zugriffsebene ausreichend sind. Sie sollten AccountManager.getAuthToken() noch einmal aufrufen, um das neue Authentifizierungstoken anzufordern.

Der letzte Fall, bei dem das Token abgelaufen ist, ist tatsächlich kein AccountManager-Fehler. Die einzige Möglichkeit herauszufinden, ob ein Token abgelaufen ist, besteht darin, den Server zu kontaktieren. Es wäre verschwenderisch und teuer, wenn AccountManager ständig online geht, um den Status aller seiner Tokens zu prüfen. Dieser Fehler kann also nur erkannt werden, wenn eine Anwendung wie Ihre versucht, das Authentifizierungstoken für den Zugriff auf einen Onlinedienst zu verwenden.

Verbindung zum Onlinedienst herstellen

Das folgende Beispiel zeigt, wie Sie eine Verbindung zu einem Google-Server herstellen. Da Google das branchenübliche OAuth2-Protokoll zur Authentifizierung von Anfragen verwendet, sind die hier beschriebenen Techniken allgemein anwendbar. Denken Sie jedoch daran, dass jeder Server anders ist. Möglicherweise müssen Sie kleinere Anpassungen an dieser Anleitung vornehmen, um Ihrer spezifischen Situation gerecht zu werden.

Bei den Google APIs müssen Sie für jede Anfrage vier Werte angeben: den API-Schlüssel, die Client-ID, den Clientschlüssel und den Authentifizierungsschlüssel. Die ersten drei stammen von der Website der Google API Console. Der letzte ist der Stringwert, den Sie durch Aufrufen von AccountManager.getAuthToken() erhalten haben. Diese werden als Teil einer HTTP-Anfrage an den Google-Server übergeben.

Kotlin

val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key")
val conn = url.openConnection() as HttpURLConnection
conn.apply {
    addRequestProperty("client_id", your client id)
    addRequestProperty("client_secret", your client secret)
    setRequestProperty("Authorization", "OAuth $token")
}

Java

URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key);
URLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("client_id", your client id);
conn.addRequestProperty("client_secret", your client secret);
conn.setRequestProperty("Authorization", "OAuth " + token);

Wenn die Anfrage den HTTP-Fehlercode 401 zurückgibt, wurde Ihr Token abgelehnt. Wie im letzten Abschnitt erwähnt, ist der häufigste Grund dafür, dass das Token abgelaufen ist. Die Lösung ist einfach: Rufen Sie AccountManager.invalidateAuthToken() auf und wiederholen Sie den Vorgang zum Abrufen des Tokens noch einmal.

Da abgelaufene Tokens so häufig vorkommen und deren Behebung so einfach ist, gehen viele Anwendungen einfach davon aus, dass das Token abgelaufen ist, bevor sie danach fragen. Wenn das Erneuern eines Tokens für Ihren Server ein kostengünstiger Vorgang ist, sollten Sie AccountManager.invalidateAuthToken() vor dem ersten Aufruf von AccountManager.getAuthToken() aufrufen und es nicht notwendig machen, zweimal ein Authentifizierungstoken anzufordern.