OkHttp/Retrofit'te başlık ekleme ve token'ların güvenli güncellenmesi

Kerem Aydın·10 Temmuz 2026·⏱3 dk

Kısaca: Global başlıklar (Authorization dahil) için OkHttp Interceptor kullanın; istek bazlı başlıklar için Retrofit anotasyonlarını; 401 durumunda senkron yenileme için Authenticator'ı kullanın; token'ları şifreli depolamada saklayın ve yenileme sırasında yarış durumlarını önleyin.

İçindekiler {{TOC_AUTOMATIC}}

Başlıklar nasıl eklenir

  1. Interceptor — global başlıklar için (Accept, Authorization). Interceptor, OkHttpClient'a eklenir ve bu istemci tarafından oluşturulan tüm isteklere uygulanır.

AuthInterceptor örneği (Kotlin):

class AuthInterceptor(private val tokenProvider: TokenProvider) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val req = chain.request()
        val token = tokenProvider.getAccessToken() // null dönebilir
        val newReq = req.newBuilder()
            .apply {
                header("Accept", "application/json")
                if (!token.isNullOrBlank()) header("Authorization", "Bearer $token")
            }
            .build()
        return chain.proceed(newReq)
    }
}

İstemci kaydı:

val client = OkHttpClient.Builder()
    .addInterceptor(AuthInterceptor(tokenProvider))
    .addInterceptor(HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY // prod'da devre dışı bırakın
    })
    .authenticator(TokenAuthenticator(tokenProvider, authApi))
    .build()
  1. İstek bazlı Retrofit üzerinden:
  • Tek bir değer için @Header;
  • Statik bir küme için @Headers;
  • Çağrı başına dinamik başlıklar için @HeaderMap.

Örnekler:

interface Api {
    @GET("items")
    suspend fun getItems(@Header("X-Client") client: String): List<Item>

    @GET("profile")
    @Headers("Accept: application/json")
    suspend fun profile(@HeaderMap headers: Map<String, String>): Profile
}
  1. Request.Builder — OkHttp üzerinden manuel olarak istek oluşturduğunuzda (Retrofit kullanırken daha nadir).

Production ortamında Authorization ve refresh token'ları loglamayın. Yayın öncesi loglama ayarlarınızı kontrol edin.

Refresh token ve Authenticator — pratik şablon

Authenticator, OkHttp tarafından 401/407 durumlarında çağrılır ve senkron bir yenileme gerçekleştirerek güncellenmiş başlığa sahip yeni bir Request döndürmeli veya (kullanıcının çıkış yapması gerekiyorsa) null döndürmelidir.

Temel noktalar:

  • Authenticator senkron çalışır — yenileme için sync Retrofit çağrısı (execute()) kullanın.
  • Yarış durumlarına karşı koruma sağlayın: aynı anda yalnızca bir yenileme işlemi, diğer istekler sonucu bekler (single-flight).
  • Döngüleri önlemek için yeniden deneme sayısını sınırlayın.

Senkronizasyonlu basit bir Authenticator örneği:

class TokenAuthenticator(
    private val tokenProvider: TokenProvider,
    private val authApi: AuthApi
) : Authenticator {

    @Volatile private var refreshing = false

    override fun authenticate(route: Route?, response: Response): Request? {
        if (responseCount(response) >= 2) return null // döngüyü önle

        synchronized(this) {
            val current = tokenProvider.getAccessToken()
            val reqToken = response.request.header("Authorization")?.removePrefix("Bearer ") 
            if (reqToken != null && reqToken != current) {
                // başka bir thread zaten token'ı güncelledi
                val newToken = tokenProvider.getAccessToken() ?: return null
                return response.request.newBuilder()
                    .header("Authorization", "Bearer $newToken")
                    .build()
            }

            // senkron yenileme gerçekleştir
            val refresh = tokenProvider.getRefreshToken() ?: return null
            val call = authApi.refreshTokenSync(RefreshRequest(refresh)) // sync call
            val resp = call.execute()
            if (!resp.isSuccessful) {
                tokenProvider.clearTokens()
                return null
            }
            val newTokens = resp.body() ?: return null
            tokenProvider.saveTokens(newTokens)
            val newAccess = newTokens.accessToken ?: return null
            return response.request.newBuilder()
                .header("Authorization", "Bearer $newAccess")
                .build()
        }
    }

    private fun responseCount(response: Response): Int {
        var res = response.priorResponse
        var result = 1
        while (res != null) {
            result++; res = res.priorResponse
        }
        return result
    }
}

Single-flight için ipuçları:

  • Authenticator içinde synchronized veya ReentrantLock kullanın.
  • Condition/Notify ile bekleme implemente edebilir veya yenilemenin zaten tamamlandığını belirten ayrı bir shared state kullanabilirsiniz.

DI ile entegrasyon:

  • Hilt/Dagger aracılığıyla Singleton OkHttpClient ve TokenProvider sağlayın.
  • Authenticator tarafından kullanılan AuthApi, eski Authorization'ın eklenmemesi için AuthInterceptor içermeyen bir Retrofit istemcisi olmalıdır.

Token depolama ve güvenlik

  • Access token'ı kısa süreli saklayın; refresh token'ı daha uzun süreli ancak şifreli depolamada tutun.
  • EncryptedSharedPreferences veya DataStore + Android Keystore / MasterKey anahtarlarını kullanın.
  • Çıkış yaparken her iki token'ı ve tüm önbellekleri silin.
  • Prod ortamında başlıkların ve gizli bilgiler içeren gövdelerin loglanmasını devre dışı bırakın.

TokenProvider basit bir arayüz olmalıdır: getAccessToken(), getRefreshToken(), saveTokens(), clearTokens(). Bu, test etmeyi ve implementasyonu değiştirmeyi kolaylaştırır.

Sık yapılan hatalar

  • Senkronize edilmemiş yenileme → birçok paralel yenileme isteği.
  • OkHttpClient'da Authenticator eksikliği → 401 otomatik olarak işlenmez.
  • Prod ortamında gizli bilgilerin loglanması.
  • Başarısız yenilemede sonsuz retry döngüleri (deneme sayısını saymama).

SSS

  • Authenticator'da coroutine kullanılabilir mi? Hayır: Authenticator senkron olarak çağrılır, yenileme için execute() kullanın. Coroutine mantığını uygulamanın üst katmanlarında tutun.
  • Belirli hostlar için nasıl başlık gönderilir? Interceptor'da request.url.host'u kontrol edin ve başlığı yalnızca gerekli hostlar için ekleyin.
  • Yenileme senaryoları nasıl test edilir? MockWebServer kullanın: 401'i simüle edin, yenileme yanıtını verin ve tekrarlanan isteği kontrol edin.

Özet: Global başlıklar için Interceptor, 401 durumunda güvenli senkron yenileme için Authenticator, istek bazlı başlıklar için Retrofit anotasyonları kullanın; token'ları şifreli depolamada saklayın ve yenileme sırasında yarış durumlarına karşı koruma sağlayın.