Uwierzytelnianie w usługach OAuth2

Schemat logiki tokena uwierzytelniania
Rysunek 1. Procedura uzyskiwania prawidłowego tokena uwierzytelniania od menedżera konta Androida.

Aby bezpiecznie korzystać z usługi online, użytkownicy muszą uwierzytelnić się w tej usłudze – muszą przedstawić dowód swojej tożsamości. W przypadku aplikacji, która uzyskuje dostęp do usługi zewnętrznej, problem z bezpieczeństwem jest jeszcze bardziej skomplikowany. Użytkownik musi nie tylko być uwierzytelniony, aby uzyskać dostęp do usługi, ale aplikacja musi też mieć autoryzację do działania w imieniu użytkownika.

Standardowym sposobem uwierzytelniania w usługach innych firm jest protokół OAuth2. Protokół OAuth2 udostępnia jedną wartość, nazywaną tokenem uwierzytelniania, która reprezentuje zarówno tożsamość użytkownika, jak i autoryzację aplikacji do działania w jego imieniu. W tej lekcji pokazujemy, jak połączyć się z serwerem Google, który obsługuje OAuth2. Chociaż zostały użyte usługi Google jako przykład, zademonstrowane techniki będą działać w każdej usłudze, która prawidłowo obsługuje protokół OAuth2.

Korzystanie z protokołu OAuth2 jest korzystne dla:

  • Uzyskanie od użytkownika zgody na dostęp do usługi online za pomocą jego konta.
  • Uwierzytelnianie w usłudze online w imieniu użytkownika
  • Obsługa błędów uwierzytelniania.

Zbieranie informacji

Aby zacząć korzystać z OAuth2, musisz wiedzieć kilka specyficznych dla interfejsu API informacji o usłudze, do której próbujesz uzyskać dostęp:

  • Adres URL usługi, do której chcesz uzyskać dostęp.
  • Zakres uwierzytelniania, czyli ciąg znaków określający konkretny typ dostępu, o który prosi Twoja aplikacja. Na przykład zakres uwierzytelniania dla dostępu tylko do odczytu do Listy zadań Google to View your tasks, a zakres uwierzytelniania w przypadku uprawnień do odczytu i zapisu w przypadku Listy zadań Google to Manage your tasks.
  • identyfikator klienta i tajny klucz klienta, czyli ciągi znaków identyfikujące aplikację w usłudze. Te ciągi musisz uzyskać bezpośrednio od właściciela usługi. Google ma samoobsługowy system do uzyskiwania identyfikatorów klientów i obiektów tajnych.

Poproś o dostęp do internetu

W przypadku aplikacji kierowanych na Androida 6.0 (poziom interfejsu API 23) lub nowszy sama metoda getAuthToken() nie wymaga żadnych uprawnień. Aby jednak wykonywać operacje na tokenie, musisz dodać do pliku manifestu uprawnienie INTERNET, jak pokazano w tym fragmencie kodu:

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

Żądanie tokena uwierzytelniania

Aby uzyskać token, wywołaj AccountManager.getAuthToken().

Uwaga: niektóre działania na koncie mogą wymagać komunikacji sieciowej, dlatego większość metod AccountManager jest asynchroniczna. Oznacza to, że zamiast wykonywać całą procedurę uwierzytelniania w jednej funkcji, trzeba wdrożyć ją jako serię wywołań zwrotnych.

Ten fragment kodu pokazuje, jak pracować z serią wywołań zwrotnych, aby uzyskać token:

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

W tym przykładzie OnTokenAcquired to klasa, która implementuje AccountManagerCallback. AccountManager wywołuje run() w usłudze OnTokenAcquired z parametrem AccountManagerFuture zawierającym Bundle. Jeśli wywołanie się powiedzie, token będzie znajdować się w obrębie Bundle.

Aby uzyskać token z Bundle:

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

Jeśli wszystko pójdzie dobrze, Bundle będzie zawierać w kluczu KEY_AUTHTOKEN prawidłowy token i wszystko będzie gotowe.

Pierwsze żądanie tokena uwierzytelniania może zakończyć się niepowodzeniem z kilku powodów:

  • Błąd w urządzeniu lub sieci spowodował awarię AccountManager.
  • Użytkownik zdecydował się nie przyznawać aplikacji dostępu do konta.
  • Zapisane dane logowania nie wystarczą, aby uzyskać do niego dostęp.
  • Token uwierzytelniania zapisany w pamięci podręcznej wygasł.

Aplikacje mogą łatwo obsłużyć 2 pierwsze przypadki, zwykle po prostu wyświetlając użytkownikowi komunikat o błędzie. Jeśli sieć nie działa lub użytkownik nie chce przyznać dostępu, aplikacja niewiele może zrobić w tym celu. Dwa ostatnie przypadki są nieco bardziej skomplikowane, ponieważ dobrze w pełni obsługiwane aplikacje powinny obsługiwać takie błędy automatycznie.

Trzeci przypadek niepowodzenia jest informowany o niewystarczających danych logowania za pomocą Bundle, który otrzymujesz w usłudze AccountManagerCallback (OnTokenAcquired z poprzedniego przykładu). Jeśli Bundle zawiera Intent w kluczu KEY_INTENT, mechanizm uwierzytelniający informuje Cię, że musi wejść w bezpośrednią interakcję z użytkownikiem, zanim przekaże Ci prawidłowy token.

Narzędzie uwierzytelniające może zwrócić kod Intent z różnych powodów. Użytkownik może zalogować się na to konto po raz pierwszy. Być może konto użytkownika wygasło i musi się zalogować ponownie lub zapisane dane logowania są nieprawidłowe. Być może konto wymaga uwierzytelniania dwuskładnikowego lub aktywacji aparatu do przeprowadzenia skanowania siatkówki. Przyczyna nie ma znaczenia. Jeśli potrzebujesz ważnego tokena, musisz uruchomić Intent, aby go otrzymać.

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

Zwróć uwagę, że w przykładzie użyliśmy elementu startActivityForResult(), więc możesz przechwycić wynik funkcji Intent, implementując onActivityResult() w swojej aktywności. To ważne: jeśli nie przechwycisz wyniku z odpowiedzi Intent uwierzytelniania, nie będzie można określić, czy użytkownik się uwierzytelnił.

Jeśli wynik to RESULT_OK, oznacza to, że usługa uwierzytelniania zaktualizowała zapisane dane logowania, aby były wystarczające na potrzeby żądanego poziomu dostępu. Aby zażądać nowego tokena uwierzytelniania, należy ponownie wywołać metodę AccountManager.getAuthToken().

Ostatni przypadek, w którym token wygasł, nie jest w rzeczywistości błędem AccountManager. Jedynym sposobem na sprawdzenie, czy token wygasł, jest skontaktowanie się z serwerem. Jeśli usługa AccountManager będzie stale online w celu sprawdzenia stanu wszystkich swoich tokenów, jej ciągłe połączenie z internetem byłoby stratne i kosztowne. Jest to błąd, który może zostać wykryty tylko wtedy, gdy aplikacja taka jak Twoja próbuje użyć tokena uwierzytelniania, aby uzyskać dostęp do usługi online.

Połącz się z usługą online

Przykład poniżej pokazuje, jak nawiązać połączenie z serwerem Google. Google do uwierzytelniania żądań używa standardowego protokołu OAuth2, więc omówione tu techniki mają szerokie zastosowanie. Pamiętaj jednak, że każdy serwer jest inny. Może się okazać, że konieczne będzie wprowadzenie drobnych zmian w tych instrukcjach, aby uwzględnić Twoją sytuację.

Interfejsy API Google wymagają podawania przy każdym żądaniu 4 wartości: klucza interfejsu API, identyfikatora klienta, tajnego klucza klienta i klucza uwierzytelniania. Pierwsze 3 pochodzą ze strony Konsoli interfejsów API Google. Ostatnia to wartość ciągu tekstowego uzyskana dzięki wywołaniu funkcji AccountManager.getAuthToken(). Przekazujesz je do serwera Google w ramach żądania HTTP.

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

Jeśli żądanie zwraca kod błędu HTTP 401, token został odrzucony. Jak już wspomnieliśmy w ostatniej sekcji, najczęstszą przyczyną takiej sytuacji jest wygaśnięcie tokena. Rozwiązanie problemu jest proste: wywołaj AccountManager.invalidateAuthToken() i powtórz proces pozyskiwania tokenów jeszcze raz.

Ponieważ wygasłe tokeny zdarzają się dość często, a naprawianie ich jest bardzo łatwe, wiele aplikacji po prostu zakłada, że token wygasł, zanim jeszcze o niego poprosi. Jeśli odnowienie tokena w przypadku Twojego serwera jest tanie, możesz wywołać metodę AccountManager.invalidateAuthToken() przed pierwszym wywołaniem AccountManager.getAuthToken() i uniknąć dwukrotnego zgłaszania prośby o token uwierzytelniania.