OAuth2 hizmetleri için kimlik doğrulama

Yetkilendirme jetonu mantığı şeması
Şekil 1. Android Hesap Yöneticisi'nden geçerli bir yetkilendirme jetonu alma prosedürü.

Kullanıcıların online bir hizmete güvenli bir şekilde erişebilmesi için hizmette kimlik doğrulaması yapmaları, kimlik belgelerini sunmaları gerekir. Üçüncü taraf hizmetlerine erişen uygulamalar için güvenlik sorunu daha da karmaşıktır. Hizmete erişmek için kullanıcının kimliğinin doğrulanmasının yanı sıra uygulamanın, kullanıcı adına hareket etme yetkisine de sahip olması gerekir.

Üçüncü taraf hizmetlerde kimlik doğrulamayı ele almanın endüstri standardı yöntemi, OAuth2 protokolüdür. OAuth2, hem kullanıcı kimliğini hem de uygulamanın kullanıcı adına hareket etme yetkisini temsil eden, kimlik doğrulama jetonu adı verilen tek bir değer sağlar. Bu derste, OAuth2'yi destekleyen bir Google sunucusuna bağlanma gösterilmektedir. Örnek olarak Google hizmetleri kullanılsa da gösterilen teknikler, OAuth2 protokolünü doğru destekleyen tüm hizmetlerde çalışır.

OAuth2'nin kullanılması şunlar için yararlıdır:

  • Hesabı kullanarak online bir hizmete erişmek için kullanıcıdan izin almak.
  • Kullanıcı adına çevrimiçi bir hizmet için kimlik doğrulaması.
  • Kimlik doğrulama hatalarını işleme.

Bilgi toplama

OAuth2'yi kullanmaya başlamak için, erişmeye çalıştığınız hizmetle ilgili API'ye özgü birkaç noktayı bilmeniz gerekir:

  • Erişmek istediğiniz hizmetin URL'si.
  • Uygulamanızın istediği belirli erişim türünü tanımlayan bir dize olan kimlik doğrulama kapsamı. Örneğin, Google Görevler'e salt okuma erişimi için kimlik doğrulama kapsamı View your tasks, Google Görevler'e okuma-yazma erişiminin kimlik doğrulama kapsamı ise Manage your tasks'dır.
  • Uygulamanızı hizmete tanımlayan dizeler olan istemci kimliği ve istemci gizli anahtarı. Bu dizeleri doğrudan hizmet sahibinden almanız gerekir. Google, istemci kimliklerini ve gizli anahtarları edinmek için kullanılan self servis bir sisteme sahiptir.

İnternet izni iste

Android 6.0 (API düzeyi 23) ve sonraki sürümleri hedefleyen uygulamalar için getAuthToken() yönteminin kendisi herhangi bir izin gerektirmez. Ancak jeton üzerinde işlem gerçekleştirmek için aşağıdaki kod snippet'inde gösterildiği gibi manifest dosyanıza INTERNET iznini eklemeniz gerekir:

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

Yetkilendirme jetonu isteme

Jetonu almak için AccountManager.getAuthToken() numaralı telefonu arayın.

Dikkat: Bazı hesap işlemleri ağ iletişimi içerebileceği için AccountManager yöntemlerinin çoğu eşzamansızdır. Bu, tüm kimlik doğrulama çalışmalarınızı tek bir işlevde yapmak yerine bunu bir geri çağırma dizisi olarak uygulamanız gerektiği anlamına gelir.

Aşağıdaki snippet'te, jetonu almak için bir dizi geri çağırmayla nasıl çalışılacağı gösterilmektedir:

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

Bu örnekte OnTokenAcquired, AccountManagerCallback yöntemini uygulayan bir sınıftır. AccountManager, OnTokenAcquired üzerinde Bundle içeren bir AccountManagerFuture ile run() işlevini çağırıyor. Çağrı başarılı olduysa jeton Bundle içindedir.

Jetonu Bundle ürününden nasıl alabileceğiniz aşağıda açıklanmıştır:

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

Her şey yolunda giderse Bundle, KEY_AUTHTOKEN anahtarında geçerli bir jeton içerir ve işlem tamamlanmış olur.

İlk yetkilendirme jetonu isteğiniz, çeşitli nedenlerden dolayı başarısız olabilir:

  • Cihaz veya ağdaki bir hata nedeniyle AccountManager aracının çalışmaması sağlandı.
  • Kullanıcı, uygulamanızın hesaba erişmesine izin vermemeye karar vermiştir.
  • Saklanan hesap kimlik bilgileri, hesaba erişim elde etmek için yeterli değildir.
  • Önbelleğe alınan yetkilendirme jetonunun süresi doldu.

Uygulamalar ilk iki durumu önemsiz olarak, genellikle yalnızca kullanıcıya bir hata mesajı göstererek ele alabilir. Ağ çalışmıyorsa veya kullanıcı erişim izni vermemeye karar verirse uygulamanızın bu konuda yapabileceği pek bir şey yoktur. Son iki durum biraz daha karmaşıktır çünkü düzgün çalışan uygulamaların bu hataları otomatik olarak ele alması beklenir.

Yetersiz kimlik bilgilerine sahip üçüncü hata durumu, AccountManagerCallback ile aldığınız Bundle (önceki örnekten OnTokenAcquired) aracılığıyla iletilir. Bundle, KEY_INTENT anahtarında bir Intent içeriyorsa kimlik doğrulayıcı, size geçerli bir jeton vermeden önce kullanıcıyla doğrudan etkileşimde bulunması gerektiğini bildiriyordur.

Kimlik doğrulayıcının bir Intent döndürmesinin birçok nedeni olabilir. Kullanıcı bu hesaba ilk kez giriş yapıyor olabilir. Kullanıcının hesabının süresi dolmuş ve tekrar giriş yapması gerekiyor ya da saklanan kimlik bilgileri yanlış olabilir. Belki hesapta iki faktörlü kimlik doğrulama gerekiyor veya retina taraması yapmak için kamerayı etkinleştirmesi gerekiyor. Nedenin ne olduğu önemli değildir. Geçerli bir jeton istiyorsanız bunu almak için Intent öğesini tetiklemeniz gerekir.

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

Örnekte startActivityForResult() kullanıldığını unutmayın. Böylece, onActivityResult() işlemini kendi etkinliğinize uygulayarak Intent sonucunu yakalayabilirsiniz. Bu önemlidir: Kimlik doğrulayıcının Intent yanıtından gelen sonucu yakalamazsanız kullanıcının kimlik doğrulamasını başarılı bir şekilde yapıp yapmadığını belirleyemezsiniz.

Sonuç RESULT_OK ise kimlik doğrulayıcı, depolanan kimlik bilgilerini istediğiniz erişim düzeyi için yeterli olacak şekilde güncellemiştir. Bu durumda, yeni kimlik doğrulama jetonunu istemek için AccountManager.getAuthToken() yöntemini tekrar çağırmanız gerekir.

Jetonun süresinin dolduğu son durum aslında bir AccountManager hatası değildir. Bir jetonun süresinin dolup dolmadığını öğrenmenin tek yolu sunucuyla iletişime geçmektir. AccountManager, tüm jetonlarının durumunu kontrol etmek için sürekli olarak çevrimiçi olmakta israf yaratır ve maliyetli olur. Bu hata yalnızca sizinki gibi bir uygulama, online bir hizmete erişmek için kimlik doğrulama jetonunu kullanmaya çalıştığında algılanabilir.

Online hizmete bağlanın

Aşağıdaki örnekte bir Google sunucusuna nasıl bağlanılacağı gösterilmektedir. Google, isteklerin kimliğini doğrulamak için endüstri standardı OAuth2 protokolünü kullandığından burada açıklanan teknikler geniş ölçüde uygulanabilir. Bununla birlikte, her sunucunun farklı olduğunu unutmayın. Durumunuzu hesaba katmak için bu talimatlarda küçük ayarlamalar yapmanız gerekebilir.

Google API'leri her istekle birlikte dört değer sağlamanızı gerektirir: API anahtarı, istemci kimliği, istemci gizli anahtarı ve kimlik doğrulama anahtarı. İlk üçü Google API Konsolu web sitesinden gelir. Sonuncusu, AccountManager.getAuthToken() yöntemini çağırarak elde ettiğiniz dize değeridir. Bunları bir HTTP isteğinin parçası olarak Google Sunucusu'na iletirsiniz.

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

İstek 401 HTTP hata kodu döndürürse jetonunuz reddedilmiştir. Son bölümde belirtildiği gibi, bunun en yaygın nedeni jetonun süresinin dolmasıdır. Çözüm basittir: AccountManager.invalidateAuthToken() yöntemini çağırın ve jeton edinme işlemini bir kez daha tekrarlayın.

Süresi dolmuş jetonlar sık karşılaşılan bir durum olduğundan ve bunları düzeltmek çok kolay olduğundan çoğu uygulama, daha istemeden jetonun süresinin dolduğunu varsayar. Jetonu yenilemek, sunucunuz için ucuz bir işlemse AccountManager.getAuthToken() hizmetine yapılan ilk çağrıdan önce AccountManager.invalidateAuthToken() yöntemini çağırmayı tercih edebilir ve iki kez yetkilendirme jetonu isteme ihtiyacından kurtulabilirsiniz.