<p>Bu makale, Laravel veya onun optimize edilmiş versiyonlarından biri olan Maravel kullanan yüksek performanslı PHP uygulamaları için önemli ipuçları ve güvenlik açıklıklarına dair bilgiler sunuyor. Bu yazıda, <strong>Cache Tags</strong> ve tymon/jwt-auth gibi popüler paketlerin nasıl güvenlik sorunlarına yol açabileceğini inceleyeceğiz.</p>
<h3>
<a name="the-hidden-architecture-trap-why-laravels-tagged-cache-amp-jwt-is-a-security-time-bomb" href="#the-hidden-architecture-trap-why-laravels-tagged-cache-amp-jwt-is-a-security-time-bomb"></a>
Gizli Mimari Tuzağı: Laravel'in Tagged Cache'inin ve JWT'nin Güvenlik Zaman Bombası Olması
</h3>
<p>Yüksek performanslı bir PHP uygulaması yürütüyorsanız, muhtemelen karmaşık, ilişkisel verileri yöneten <strong>Cache Tags</strong> kullanıyorsunuz. Ayrıca stateless API kimlik doğrulaması için tymon/jwt-auth gibi paketlere güveniyorsunuz. Ancak resmi belgelerde belirtilmeyen bir sorun var: Bu iki sistemi tek bir caching motoru altında birleştirmek, yıkıcı bir mimari çatışma yaratır.</p>
<p>Bu durum, sonsuz bellek sızıntılarına, gereksiz CPU döngüsü patlamalarına ve en tehlikelisi, Token Replay Attack'lerine açık bir gizli güvenlik açığına neden olur. Framework geliştiricileri, bellek sızıntısını düzeltmektense Redis etiketlerini belgesiz bırakmayı tercih etmiştir.</p>
<h3>
<a name="the-security-loophole-premature-blacklist-eviction" href="#the-security-loophole-premature-blacklist-eviction"></a>
Güvenlik Açığı: Prematüre Blacklist Silinmesi
</h3>
<p>Temel sorun, <strong>Stateless Güvenlik Yaşam Döngüleri ile Bellek Optimizasyonları</strong> arasındaki yapısal uyumsuzluktan kaynaklanmaktadır. JWT tokeni stateless'tır. Sunucu, bir kullanıcının oturumu kapatması gibi tokenin doğal süresinden önce geçersiz hale getirmek istediğinde, tokenin benzersiz kimliğini (<strong>jti</strong>) bir <strong>Blacklist</strong>'te saklamak zorundadır. Tokenin kriptografik imzası yenilenme süresince geçerli olduğundan, blacklist kaydı bu süre boyunca (genellikle <strong>14 gün</strong>) cache'de kalmalıdır.</p>
<p>Tam tersi, yüksek performanslı etiketleme sistemleri kısa ömürlü, ilişkisel iş verisi için tasarlanmıştır. RAM’in şişmesini engellemek için, optimize edilmiş sistemler belirli bir Süreye (TTL) sınırlı kalma kuralı uygular. Bu yapı nedeniyle, <strong>tymon/jwt-auth</strong> cache sürücünüzde tag desteği bulduğunda, blacklist girişlerini zorla içerecek şekilde 14 günlük süreyi kısaltır.</p>
<h3>
<a name="the-performance-drain-the-double-probe-storm" href="#the-performance-drain-the-double-probe-storm"></a>
Performans Kaynağı: "Double Probe" Fırtınası
</h3>
<p>Sadece güvenlik değil, düz veriyi bir etiketleme matrisinden geçirmek performansı düşürür. Varsayılan jwt-auth saklama sürücüsü her API isteğinde bir "Look Before You Leap" desenini kullanır:</p>
<ol>
<li>Cache sürücünüzün etiket desteğine sahip olup olmadığını kontrol etmek için bir dummy ->tags() çağrısı gerçekleştirilir.</li>
<li>Gerçek repository'i döndürmek için ikinci bir ->tags() çağrısı yapılır.</li>
</ol>
<p>Bu durum, her HTTP isteği başına iki kez anahtar sıralaması ve metadata tahsis etmeye zorlar. Ayrıca, yerel Laravel Redis etiketleri TTL'leri kendi referans listelerine uygulamadığı için, bağımsız JWT tokenlerini etiketli bir grup olarak değerlendirmek sonsuz bir bellek sızıntısına neden olur.</p>
<h3>
<a name="phase-1-of-the-fix-pure-flat-keyspace-decoupling" href="#phase-1-of-the-fix-pure-flat-keyspace-decoupling"></a>
Çözüm Aşaması 1: Düz Anahtar Alanı Ayrıştırması
</h3>
<p>Kimlik doğrulama katmanı için etiketleri kullanmayın. Bir token blacklist'i bağımsız, izole dizelerden oluşur; ilişkisel bir hiyerarşiye ihtiyacı yoktur. JWT saklama sağlayıcınızı alt sınıf olarak özelleştirerek supportsTags = false şeklinde ayarlamalısınız:</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="cp"><?php </span?>
<span class="kn">namespace</span> <span class="nn">App\Cache</span><span class="p">;</span>
<span class="kn">use</span> <span class="nc">Tymon\JWTAuth\Providers\Storage\Illuminate</span> <span class="k">as</span> <span class="nc">BaseIlluminateStorage</span><span class="p">;</span>
<span class="kd">class</span> <span class="nc">JWTFlatStorage</span> <span class="kd">extends</span> <span class="nc">BaseIlluminateStorage</span>
<span class="p">{</span>
<span class="cd">/
* Cache çözücüsünü kesip, tamamen düz çalışmasını zorlayın.
* Bu, etiketleme middleware'ini tamamen bypass eder.
*
* @return \Illuminate\Contracts\Cache\Repository
*/</span>
<span class="k">protected</span> <span class="k">function</span> <span class="n">cache</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// Etiketlerin desteklenmediğini açıkça belirtin.</span>
<span class="c1">// Bu, düz depolama repository'sine dönecektir.</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">supportsTags</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="n">cache</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
</div>
<p>Bu yapıyı config/jwt.php dosyanızın içinde providers.storage altında değiştirin. Blacklist tokenleriniz artık doğrudan standart anahtar-değer çiftleri olarak yazılacak ve güvenli bir şekilde 14 günlük sürelerini koruyacaktır.</p>
<h3>
<a name="phase-2-of-the-fix-the-maravelframework-20-tagged-cache-engine" href="#phase-2-of-the-fix-the-maravelframework-20-tagged-cache-engine"></a>
Çözüm Aşaması 2: Maravel-Framework 20 Tagged Cache Motoru
</h3>
<p>Düz güvenlik verileri ayrıştırıldığında, <strong>Maravel-Framework 20</strong>'de, ilişkisel iş verileriniz için pointer orphaning ve yazma ile flush yarışı sorunlarını çözmek amacıyla Tagged Cache motorunu tamamen yeniden inşa ettik.</p>
<p>Motor artık <strong>4-Tiers Generational Indexing Matrix</strong> kullanır ve bu, “Russian Doll” geleneksel TTL çürümesi ile güvence altına alınmıştır:</p>
<ul>
<li>
<strong>1. Seviye (Ana Versiyon):</strong> Her şeyden iki kat daha uzun sürer (kap * 2). Yapısal çapa görevi görür.
</li>
<li>
<strong>2. Seviye (Dizi Sayacı):</strong> Atomik dizi sayacı. (kap + 5s).
</li>
<li>
<strong>3. Seviye (Takip Pointer'ı):</strong> Hedef takip pointer'ı. (kap + 5s).
</li>
<li>
<strong>4. Seviye (Veri Yükü):</strong> Gerçek cached nesne. $this->ttlCap'de doğal olarak kaybolur.
</li>
</ul>
<h4>
<a name="o1-atomic-lazy-eviction" href="#o1-atomic-lazy-eviction"></a>
O(1) Atomik Tembel Silme
</h4>
<p>Yerel Laravel'de bir etiketi flush etmek (Cache::tags(['catalog'])->flush()) yoğun anahtar alanı taraması ve silme döngülerini tetikler, bu durum Redis thread’lerini bloke eder. Maravel-Framework 20'de ise bir flush, depolama sunucusunda tek bir mikro saniyelik atomik talimatı yürütür: $this->store->increment('tag-version:catalog');</p>
<p>Ana versiyon 1’den 2’ye yükselir. Hedef cache anahtarları dinamik bir kriptografik birleşik hash ile sentezlenir (örneğin, sha1("tag1:1|tag2:2")), versiyon yükseltmek, tüm üst üste binen bileşenlerde otomatik olarak hesaplanmış hash’u değiştirir. Bu da yerel O(1) cache hatasına yol açar. Eski yükler güvenli bir şekilde yetim kalır ve kendi TTL’leri tarafından doğal olarak temizlenir. PHP overhead yok. Silme döngüsü yok.</p>
<h3>
<a name="phase-3-of-the-fix-backporting-to-maravelframework-10x" href="#phase-3-of-the-fix-backporting-to-maravelframework-10x"></a>
Çözüm Aşaması 3: Maravel-Framework 10.x'e Geri Portlama
</h3>
<p>V10.x hattını kullanıyorsanız, uygulama bootstrap aşamanızda özel TaggedCache/RedisTaggedCache ve TagSet/RedisTagSet sınıflarını Bağımlılık Enjeksiyonu ile elle geri portlayabilirsiniz. Aşağıdaki gibi yapabilirsiniz:</p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="cd">/ START Tagged cache fix backport from v20.x */</span>
<span class="nc">\Illuminate\Cache\TaggedCache</span><span class="o">::</span><span class="n">class</span> <span class="o">=></span> <span class="p">[</span> <span class="c1">// veya RedisTaggedCache</span>
<span class="s1">'concrete'</span> <span class="o">=></span> <span class="k">function</span> <span class="p">(</span>
<span class="nc">\Illuminate\Contracts\Container\Container</span> <span class="nv">$container</span><span class="p">,</span>
<span class="k">array</span> <span class="nv">$parameters</span> <span class="o">=</span> <span class="p">[]</span>
<span class="p">)</span><span class="o">:</span> <span class="nc">\App\Cache\TaggedCache</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$container</span><span class="o">-></span><span class="nf">resolve</span><span class="p">(</span>
<span class="nc">\App\Cache\TaggedCache</span><span class="o">::</span><span class="n">class</span><span class="p">,</span>
<span class="nv">$parameters</span><span class="p">,</span>
<span class="kc">false</span>
<span class="p">);</span>
<span class="p">},</span>
<span class="s1">'shared'</span> <span class="o">=></span> <span class="kc">false</span>
<span class="p">],</span>
<span class="nc">\Illuminate\Cache\TagSet</span><span class="o">::</span><span class="n">class</span> <span class="o">=></span> <span class="p">[</span><span class="c1">// veya RedisTagSet</span>
<span class="s1">'concrete'</span> <span class="o">=></span> <span class="k">function</span> <span class="p">(</span>
<span class="nc">\Illuminate\Contracts\Container\Container</span> <span class="nv">$container</span><span class="p">,</span>
<span class="k">array</span> <span class="nv">$parameters</span> <span class="o">=</span> <span class="p">[]</span>
<span class="p">)</span><span class="o">:</span> <span class="nc">\App\Cache\TagSet</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$container</span><span class="o">-></span><span class="nf">resolve</span><span class="p">(</span>
<span class="nc">\App\Cache\TagSet</span><span class="o">::</span><span class="n">class</span><span class="p">,</span>
<span class="nv">$parameters</span><span class="p">,</span>
<span class="kc">false</span>
<span class="p">);</span>
<span class="p">},</span>
<span class="s1">'shared'</span> <span class="o">=></span> <span class="kc">false</span>
<span class="p">],</span>
<span class="cd">/ END Tagged cache fix backport from v20.x */</span>
</code></pre>
</div>
<h3>
<a name="does-laravel-13-fix-this-spoiler-no" href="#does-laravel-13-fix-this-spoiler-no"></a>
Laravel 13 Bu Sorunu Çözüyor mu? (Kısa Cevap: Hayır)
</h3>
<p>Bu mimarinin son versiyonlarda yeniden düzenlendiğini varsayabilirsiniz. Ancak durum böyle değildir. Laravel sonunda Cache Tags'ları belgelerine yeniden eklese de, Redis bellek sızıntısına yönelik "çözümü", php artisan cache:prune-stale-tags komutunu çalıştırmanızdır.</p>
<p>Temel mimariyi düzeltmek yerine, framework, ölü referans anahtarlarını manuel olarak süpürmek ve silmek için arka planda çalışan bir cron işinin uygulanmasını beklemektedir. Eğer bu cron işi başarısız olursa, API trafiği ile lag yaparsa veya dağıtım sırasında unutulursa, Redis kümeniz dolacak ve kör bir <strong>LRU Eviction</strong>'ı tetikleyecektir.</p>
<h3>
<a name="the-takeaway" href="#the-takeaway"></a>
Sonuç
</h3>
<p>Etiketler güçlüdür, ancak ilişkisel hiyerarşiler için bir araçtır ve her durum için uygun değildir. Düz güvenlik tokenlerinizi, nesil iş önbelleğinizden ayırarak, API’nizi replay ataklarına karşı güvenli hale getirirken, alt milisaniyelik atomik bellek hızlarından da faydalanırsınız.</p>
<hr/>Kaynak: Orijinal Makale



