Token Yenilenme Durumunda Aynı Clienti’ın Birden Fazla İstekte Bulunduğunda, Aldığı Geçersiz Token Sorunu

Selamlar, bu makalede Token ve Refresh Token’ın kullanıldığı senaryolarda Expire durumunda, karşılaşılabilecek sorunlardan ve çözümlerinden bahsedilecektir.

Aslında projelerinde, Token kullanmayan nerede ise hiç yoktur. Ama genelde bu tokenları, expire olmadan ve kullanıcıya çaktırmadan bir Refresh Token ile yenileyen maalesef pek azdır. Çalışan cilient’ı madur etmemek adına genelde, bu Token Expire süreleri uzun tutulur. Ve bu da bazı güvenlik açıklarına neden olmaktadır. Hadi şimdi gelin bu Refresh işleminde hemen karşımıza çıkmayacak, ama kullanılmaya başlandığında karşılaşılabilecek çok sinsi bir sorundan bahsedelim. Bu makalede sıfırdan bir kod yazılmayacak, proje içinde geçen kod parçaları örnek amaçlı paylaşılacaktır.

Yukarıdaki çizime bakıldığında, bir client’ın 4 farklı request’i eş zamanlı olarak asenkron gerçekleştirdiği görülmektedir. Controller veya Actionlara gelmeden, bir “[LoginAttribute]” ile Token ve yetki kontrolünün yapıldığını düşünelim.

1-) Token Validation:

Gelen tüm Requestlerin ilk karşılandığı nokta. Bu senaryoda, ilk Threadin Token kontrolünden başarı ile geçtiğini ama diğer Threradlerin daha henüz bu kontrole girmediğini düşünelim.

2-) Token Expire Süresinin Kontrol Edilmesi:

Bu aşamada, doğrulanmış Token’ın ömrüne bakılır. Eğer sonlanmasına bu senaryo gereği 15 dakikadan az bir süre kalmış ise, Refresh Token’ın da olup olmadığına ve RefreshToken’ın doğruluğuna bakıldıktan sonra yenileme işlemi yapılarak, client-side’a bu yeni tokenlar geri dönülür. Böylece sürekli çalışan client, madur edilmemiş ve logout olmadan bir 60 dakika daha kazandırılmış olunur.

*3-) İlk Thread 1 ile Değiştirilen Tokenlar, Thread 2-3 ve 4 için Artık Geçersiz olacaktır:

Bu durum aslında çok kısa bir süre içinde gerçekleşebilmektedir. Testlerde yakalanması da çok zor olabilmektedir :) Çünkü daha çok, aynı client’ın anlık asenkron olarak birden fazla istekte bulunması durumunda,  bu sorun ile karşılaşılmaktadır. İlk istekte yenilenen Tokenın, artık diğer isteklerin elindeki Token’ı geçersiz hale getirmesi ile karşımıza çıkan 401 Unauthorized hatasıdır. Çözüm makalenin devamında, akış tamamlanınca anlatılacaktır.

4-) Double Check Lock Mutex:

Token’ın yenilenmesi sırasında, bunun sadece ilk istek için yapılması diğer 3 istek için de tekrarlanmaması istenir. Yani anlık 4 request’de Token’ın herbiri için ayrı ayrı yenilenmesi, sunucu için 4X yüktür. Bu nedenle, ilk Requestiden sonra Lock objesi ile diğer requestler kitlenmekte ve ilk Request’in ilgili Token ve RefreshToken’ı değiştirmesi beklenmektedir. Daha sonrasında, Lock objesi sadece 2.Thread için kaldıralacak ve 2. kere Token’ın Expire olup olmadığı kontrol edilecektir. Doğal olarak, ilk Request’de ilgili Token güncellendiği için bu koşul sağlanmayacak ve 2.kere Token güncellenmeyecektir. Aynı durum 3. ve 4. Requestler için de geçerli olacaktır.

Yukarıdaki kod parçasında, öncelikle Tokenın expire süresinin 15 dakkadan az kaldığı ve 60 dakikayı geçmediği koşulu kontrol edilmiştir. Daha sonra ilk request geçtikten sonra, diğer requstler için “Lock” işlemi yapılmıştır. Sonrasında ilgili Tokenlar tekrardan Redis’den çekilmiş ve 2.kere Token Expire süresine bakılmıştır. Amaç, demin de bahsedildiği gibi lock objeye ilk Requestden sonra giren diğer Requestler için yeniden Token süresini kontrol ederek, 2. , 3. ve 4. kere yeni Token ve RefreshToken’ın yaratılmasına engel olmaktır.

Şimdi Gelelim İlk Request’de Değişen Token’ın, Eş Zamanlı Gelen Diğer Requestleri Geçersiz Kılması Sorununa:

1-) Eğer Tokenin yaşı “45 < x < 60 ” arası ise, yani en fazla 15 dakika içinde Expire olacak ise aşağıda görüldüğü gibi değişimi için öncelikle, güvenlik amaçlı RefreshToken beklenir. RefreshToken yok ise, zaten güncelleme işlemi yapılmaz. Daha sonra ilerde, Redisden geçerli Token ve Referesh Tokenların çekilmesi için “userID”, mobil ise “isMobile” ve mobile cihazın UniqueID’si “UniqueDeviceID”‘i alınmaktadır. Eğer Header’dan geçerli bir “userId” gelmez ise, “UnAuthorized” hatası geri dönülmektedir.

2-)Aşağıda görüldüğü gibi Client ve BackEnd tarafından gelen RefreshTokenlar karşılaştırılmıştır. ClientSide taraftan gelen encrypted RefreshToken, decrypt edilir. Ayrıca ilgili userID’ye ait Redis’de bir RefreshToken yok ise, ya Expire olmuştur ya da client hiç LOGİN olmamıştır.

* 3-) Aşağıdaki kodda görüldüğü gibi, Expire’a uğrayacak user’a ait eski Token 5 dakka süre ile “_Old” keyword’ü ile Redis’de saklanırlar. Peki neden ? Makalenin sebebi olan cevap :) Çünkü diğer Requestler için oluşabilecek geçersiz Token durumunda, bu eski 5 dakkalık Tokenların geçerliğine bakılacak ve buna göre izin verilecektir.

4-) Aşağıdaki kod parçasında, artık yeni bir Token üretilmiş ve Redis’e 1 saatlik bir expire süresi ile konmuştur. Böylece, makina başındaki client, logout olmadan ya da bölünmeden 1 saat daha işine devam edebilecektir. Daha sonra aynı adımlar, yani eski Token’in “_Old” uzantı ile 5 dakka süre ile kaydedilip, yeni Tokenın 1 saatlik süre için yaratılıp Redis’de saklanması, RefreshToken için de yapılır.

5-) Generate Token, geriye Tuple olarak şifreli ve şifresiz hali ile iki yeni oken dönmektedir. Şifreli hali, “context.HttpContext.Items[“token”] = encToken;” ile Headerda client’a dönülür. Şifresiz hali, Backend’de Redis’de saklanır. Client’a Token’ın şifreli hali, tamamen güvenlik amacı ile dönülmektedir. Bir Token’ın açık halinin görünmesi, bir sonraki Token tahmini veya algoritmanın çözülmesi için önemli bir ip ucu olmaktadır.

6-) Aşağıdaki kod parçasında, Token üretimi için yapılan işlemlerin bir benzeri, RefreshToken için de yapılmaktadır. Buradaki tek fark, RefreshToken’ın TimeOut süresi, işi sağlama almak adına 90 dakikadır. Kısacası Refresh Token süresini, Token’ın süresinden biraz daha fazla tutmakda fayda vardır.

Tam Kod:

Şimdi Gelin Authentication Filterımızı Bu “_Old” Tokenlara Göre Tekrardan Düzenleyelim:

Aşağıdaki kod parçasında, ilk request ile değişen Token’ın, diğer Requestleri nasıl 401 Unauthorized’a düşürmeyeceği anlatılmıştır.

1-) Backend tarafinda Redis’den çekilen Token null ise, ya Expired olmuştur ya da hiç login olunmamıştır. Ama bazen Mobile uygulamalarda Token yok ise, 1 yıl Expire süresi verilmiş RefreshToken ile tekrardan yeni Token aldırılabilir. Ancak bu da tabi ki kullanıcıya bir kullanım kolaylığı getirse de, ayrıca bir güvenlik açığı oluşturmaktadır.

*2-) İşte esas çözüm aşağıda görüldüğü gibi Backend ile Client Side tarafdan gelen Tokenların eşit olmadığı durumlarda gerçekleştirilmektedir. Güncel Tokenların eşit olmadığı durumlarda, bir de Old Tokenlara yani 5 dakkalık ömrü olan Tokenlara bakılmaktadır. Eğer onlar da doğru değil ise, 401 Unauthorized hatası geri dönülmektedir. Böylece multithread anlık birden çok requestde Expire düşme sorunu çözülmüş olmaktadır.

Study shows chess is a powerful tool against dementia (video) | ChessBase
Geldik bir makalenin daha sonuna. Bu makalede, bazı güvenlik durumlarında karşılaşabileceğimiz sorunlardan bahsedilmiştir. Dikkat ederseniz, yazılımda güvenlik arttırıldıkça, üzerinde düşünülmesi gereken koşul ve durumlar da aynı oranda artmaktadır. Unutlmamalıdır ki güvenlik ile performans ters orantılıdır. Bu yüzden yazılımlarınızda güvenlik ilkelerini, ihtiyacınız doğrultusunda belirleyiniz. Aklınızdan  çıkarmamanız gereken bir durum da, aynı satrançta olduğu gibi her yaptığınız hamlenin, başka bir alanda size açık olarak geri dönebilecek olmasıdır. Hamlelerinizi akıllıca ve ihtiyacınız olduğu yönde yapınız. Örneğin bir banka uygulaması ya da ödeme sistemi yazmıyorsanız, belki de bu kadar fazla önleme ihtiyacınız yoktur.

Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

 

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

7 Cevaplar

  1. tempest fansub dedi ki:

    İçeriğinizi çok beğendim!

    • borsoft dedi ki:

      Tesekkurler..

      • Koray dedi ki:

        Anlamadığım nokta şu, refresh işlemi neden token validation sırasında yapılıyor? Client tarafı refresh işlemini ayrı bir request ile belli aralıklarla yapsa? Token expire olmadan, elindeki tokenı refresh ettiği tokenla değiştirip requestleri o token ile atsa?

        • borsoft dedi ki:

          Selamlar Koray,
          Bu durumda anlık 10K client olsa, her 1 dakkikada 10K requested BackEnd’e yok bindirecektir. Bu da hiç istenmeyen bir durumdur.

  2. Sefa Cihangire dedi ki:

    Hocam emeğinize, kaleminize sağlık. Tecrübelerinizi, bilgilerinizi paylaştığınız için çok sağolunuz

  3. Ensar dedi ki:

    Elinize emeğinize sağlık çok bilgilendirici bir yazı olmuş. Microservislerle ilgili bazı merak ettiklerim var size sorabileceğim bir yer var mıdır yoksa direkt buradan mı sormalıyım ?

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.