OAuth2 hizmetleri için kimlik doğrulama

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

Online bir hizmete güvenli bir şekilde erişmek için kullanıcıların hizmette kimlik doğrulaması yapması, ancak kimlik belgelerini sunması gerekir. Üçüncü taraf hizmetlerine erişen bir uygulama 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 işlem yapma yetkisine de sahip olması gerekir.

Üçüncü taraf hizmetlerine kimlik doğrulama işlemi için endüstri standardı olan yöntem OAuth2 protokolüdür. OAuth2, hem kullanıcı kimliğini hem de uygulamanın kullanıcı adına hareket etme yetkisini temsil eden ve yetkilendirme 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 bir şekilde destekleyen tüm hizmetlerde çalışacaktır.

OAuth2 kullanmak şunlar için yararlıdır:

  • Kullanıcıdan hesabını kullanarak online bir hizmete erişim izni almak.
  • Kullanıcı adına bir online hizmet için kimlik doğrulama.
  • Kimlik doğrulama hatalarını işleme.

Bilgi toplama

OAuth2'yi kullanmaya başlamak için, erişmeye çalıştığınız hizmet hakkında 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şimi için kimlik doğrulama kapsamı ise Manage your tasks'dir.
  • İstemci kimliği ve istemci gizli anahtarı. Uygulamanızı hizmete tanımlayan dizelerdir. 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 isteme

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. Diğer bir deyişle, tüm kimlik doğrulama işlemlerinizi tek bir işlevde yapmak yerine, bu işlevi bir dizi geri çağırma olarak uygulamanız gerekir.

Aşağıdaki snippet'te, jetonu almak için bir dizi geri çağırmayla nasıl çalışacağınız 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 uygulayan bir sınıftır. AccountManager, Bundle içeren bir AccountManagerFuture ile OnTokenAcquired üzerinde run() çağrısı yapar. Çağrı başarılı olursa jeton Bundle içindedir.

Bundle hizmetinden nasıl jeton 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 artık hazırsınız demektir.

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

  • Cihazdaki veya ağdaki bir hata AccountManager cihazının başarısız olmasına neden oldu.
  • Kullanıcı, uygulamanızın hesaba erişmesine izin vermemeye karar vermiştir.
  • Depolanan hesap kimlik bilgileri, hesaba erişmek için yeterli değildir.
  • Önbelleğe alınan yetkilendirme jetonunun süresi doldu.

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

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

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 hesap, iki faktörlü kimlik doğrulama gerektiriyordur veya retina taraması için kamerayı etkinleştirmesi gerekiyordur. Sebebinin ne olduğu pek önemli değildir. Geçerli bir jeton istiyorsanız bunu almak için Intent ile tetiklenmeniz 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;
        }
    }
}

Örneğin startActivityForResult() kullandığını unutmayın. Böylece kendi etkinliğinize onActivityResult() uygulayarak Intent sonucunu yakalayabilirsiniz. Bu önemlidir: Kimlik doğrulayıcının Intent yanıtına ait sonucu almazsanız kullanıcının kimlik doğrulamasının başarıyla yapılmış olup olmadığını anlamak imkansızdır.

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. Bu durum, AccountManager adlı jetonun tüm jetonlarının durumunu kontrol etmek için sürekli olarak internete bağlanmak gereksiz ve pahalı olur. Bu hata, yalnızca sizinkine benzer bir uygulama online bir hizmete erişmek için kimlik doğrulama jetonunu kullanmaya çalıştığında algılanabilir.

Online hizmete bağlanma

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. Yine de, her sunucunun farklı olduğunu unutmayın. Özel durumunuzu açıklamak 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 geçirirsiniz.

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() çağrısı yapın ve jeton edinme işlemini bir kez daha tekrarlayın.

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