Mert Özen Her Satırda Daha İleri
Güvenlik ASP.NET Core Güvenliği — #02

Data Protection Derinlemesine: Key Ring, Rotation, Key Storage, Key'leri Şifrelemek ve Çoklu Instance

Mert Özen 8 Kas 2026 12 dk 3 görüntülenme
Data Protection Derinlemesine: Key Ring, Rotation, Key Storage, Key'leri Şifrelemek ve Çoklu Instance

Çok instance'lı ASP.NET Core uygulamasında sebepsiz logout'lar ve boş gövdeli 400'ler genelde instance başına ayrı Data Protection key ring'inden gelir. Key ring'in ömrünü, rotation ve revocation'ı, purpose hiyerarşisini, dosya, Redis ve veritabanı store'larını, key'leri certificate ile şifrelemeyi ve SetApplicationName'in rolünü gerçek log'larla anlatıyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 (ASP.NET Core 10.0.13 runtime) ile derlenip denenmiştir; Redis 8.2 ve Microsoft.AspNetCore.DataProtection.StackExchangeRedis 10.0.14, Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 10.0.14 ve Microsoft.EntityFrameworkCore.Sqlite 10.0.14 kullanılmıştır.

Önce Tahmin Et

Bookstore'u Kubernetes'e taşıdın, iki pod çalışıyor. Kullanıcı giriş yapıyor, birkaç sayfa geziyor ve hiçbir sebep yokken yeniden login ekranına düşüyor. Ödeme formunu gönderenlerin bir kısmı da gövdesi boş bir 400 alıyor. Log'da hata yok, CPU (Central Processing Unit) kullanımı normal. Deploy'dan sonra herkes bir kez daha logout oluyor. Sorunun koddaki tek bir satırın eksikliği olduğunu söylesem, hangi satır olduğunu tahmin edebilir misin? Bir ipucu: bu satır ne cookie authentication'ın ne de antiforgery'nin configuration'ında.

Bu satır Data Protection'ın configuration'ını yapan satır. ASP.NET Core'da authentication cookie'si, antiforgery token'ı, TempData ve Identity'nin e-posta ve parola sıfırlama token'ları gibi pek çok şey arka planda IDataProtector ile şifreleniyor. OWASP (Open Worldwide Application Security Project) Top 10 listesini anlatan Web Güvenliği serisinin "OWASP A04: Cryptographic Failures (2)" yazısında Data Protection'ı imzalı bir link üretmek için kullanmıştık. Bu yazıda motorun içine bakıyoruz: key ring nasıl yaşar, key'ler nerede durur, nasıl şifrelenir ve birden çok instance onu nasıl paylaşır.

Purpose, Protector ve Payload

Önce dışarıdan görünen API'yi (Application Programming Interface) bir konsol uygulamasında deneyelim. Key'leri çalışma klasörüne yazıyoruz; bunu yapmazsan macOS ve Linux'ta key'ler kullanıcının home klasöründeki .aspnet/DataProtection-Keys altına düşer:

string keyFolder = Path.Combine(AppContext.BaseDirectory, "lab-keys");
if (Directory.Exists(keyFolder))
{
    Directory.Delete(keyFolder, recursive: true);
}

ServiceCollection services = new();
services.AddLogging(logging => logging.AddSimpleConsole(o => o.SingleLine = true).SetMinimumLevel(LogLevel.Warning));
services.AddDataProtection()
    .SetApplicationName("bookstore")
    .PersistKeysToFileSystem(new DirectoryInfo(keyFolder));
await using ServiceProvider provider = services.BuildServiceProvider();

IDataProtectionProvider dataProtection = provider.GetRequiredService<IDataProtectionProvider>();
IDataProtector orderLinks = dataProtection.CreateProtector("Bookstore.OrderLinks", "v1");
IDataProtector chained = dataProtection.CreateProtector("Bookstore.OrderLinks").CreateProtector("v1");
IDataProtector invoices = dataProtection.CreateProtector("Bookstore.Invoices", "v1");

string token = orderLinks.Protect("order:1042");
Console.WriteLine($"protected: {token[..24]}... ({token.Length} chars), key {KeyIdOf(token)}");
Console.WriteLine($"chained purpose: {chained.Unprotect(token)}");
TryUnprotect("other purpose", invoices, token);
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35] No XML encryptor configured. Key {646f071e-3198-4da4-87b3-1763df17072c} may be persisted to storage in unencrypted form.
protected: CfDJ8B4Hb2SYMaRNh7MXY98X... (134 chars), key 646f071e-3198-4da4-87b3-1763df17072c
chained purpose: order:1042
other purpose: CryptographicException: The payload was invalid. For more information go to https://aka.ms/aspnet/dataprotectionwarning

Üç şey öğreniyoruz. Birincisi, purpose bir hiyerarşi: CreateProtector("A", "v1") ile CreateProtector("A").CreateProtector("v1") aynı protector'ı verir. İkincisi, purpose izolasyondur: aynı key ring'le şifrelenmiş olsa bile Bookstore.Invoices protector'ı Bookstore.OrderLinks payload'unu açamaz. Framework de bunu kullanır; cookie authentication kendi purpose zincirini, antiforgery kendininkini kurar. Üçüncüsü, en tepedeki purpose'u sen yazmazsın: SetApplicationName (ya da verilmemişse uygulamanın content root path'i) zincirin başına eklenir. Bunu birazdan production'da göreceğiz.

Payload'un kendisi de bir şey söylüyor. Her payload CfDJ8 ile başlar; bu, 4 byte'lık magic header'ın Base64 karşılığı. Arkasından 16 byte'lık key id gelir. Yukarıdaki KeyIdOf method'u tam olarak bunu okuyor:

static Guid KeyIdOf(string payload) =>
    // ilk 4 byte magic header, sonraki 16 byte key id
    new(WebEncoders.Base64UrlDecode(payload).AsSpan(4, 16));

Yani bir cookie'yi açamayan instance, "hangi key'le şifrelenmiş" sorusunun cevabını payload'dan okur ve key ring'inde o key'i arar. Bulamazsa hata mesajı key id'yi verir; bu, sorun giderirken altın değerinde.

Key Ring: Ömür, Rotation ve Revocation

Key ring, XML (Extensible Markup Language) dosyaları olarak saklanan master key'lerin listesi. Varsayılan algoritmalar, CBC (Cipher Block Chaining) modunda 256 bit'lik AES (Advanced Encryption Standard) ve SHA (Secure Hash Algorithm) ailesinden SHA-256 kullanan HMAC (Hash-based Message Authentication Code), yani AES_256_CBC ve HMACSHA256; her payload için bu iki alt key 512 bit'lik master key'den türetilir. Diskteki bir key şöyle görünüyor:

<key id="026cdee8-dbe3-4170-902e-11ec06b4b4c4" version="1">
  <creationDate>2026-11-08T05:29:33.732704Z</creationDate>
  <activationDate>2026-11-08T05:29:33.732704Z</activationDate>
  <expirationDate>2027-02-06T05:29:33.732704Z</expirationDate>
  <descriptor deserializerType="Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60">
    <descriptor>
      <encryption algorithm="AES_256_CBC" />
      <validation algorithm="HMACSHA256" />
      <masterKey p4:requiresEncryption="true" xmlns:p4="http://schemas.asp.net/2015/03/dataProtection">
        <!-- Warning: the key below is in an unencrypted form. -->
        <value>...</value>
      </masterKey>
    </descriptor>
  </descriptor>
</key>

Aktivasyon ve son kullanma tarihleri arasında 90 gün var. Bu varsayılan ömür ve Microsoft'un key management dokümanına göre kurallar şöyle: yeni bir key, oluşturulduktan 2 gün sonra aktif olur (diğer instance'lar onu görsün diye propagation penceresi). Default key'in süresinin bitmesine 2 günden az kaldıysa ve onun yerine geçecek bir key yoksa sistem yenisini üretir; yeni key'in aktivasyon tarihi eskisinin bitiş tarihidir. Ring tamamen boşsa ya da hepsi expire olmuşsa, yeni key gecikmesiz aktif olur. Yukarıdaki XML'de aktivasyonun oluşturma anıyla aynı olmasının sebebi bu.

02-key-ring-timeline-tr.png

Expire olmuş bir key ile yeni Protect yapılmaz ama eski payload'lar açılmaya devam eder. Revoke edilmiş key ise Unprotect'te de reddedilir. Key dosyasını silmek ise bunlardan farklı: o key ile şifrelenmiş her şey kalıcı olarak okunamaz hâle gelir. SetDefaultKeyLifetime ile ömrü değiştirebilirsin ama alt sınır var; bir günlük ömür denediğimde aldığım hata: System.ArgumentOutOfRangeException: The new key lifetime must be at least one week.

Rotation ve revocation'ı konsolda deneyelim. IKeyManager ile hemen aktif olan yeni bir key oluşturup ardından eski key'i revoke ediyoruz:

IKeyManager keyManager = provider.GetRequiredService<IKeyManager>();
IKey oldKey = keyManager.GetAllKeys().Single();
IKey newKey = keyManager.CreateNewKey(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(90));
Console.WriteLine($"created {newKey.KeyId}, activation now");

Stopwatch clock = Stopwatch.StartNew();
string fresh = orderLinks.Protect("order:1043");
Console.WriteLine($"+{clock.ElapsedMilliseconds} ms: new payload uses key {KeyIdOf(fresh)}");
while (KeyIdOf(fresh) != newKey.KeyId)
{
    await Task.Delay(250);
    fresh = orderLinks.Protect("order:1043");
}
Console.WriteLine($"+{clock.ElapsedMilliseconds} ms: new payload uses key {KeyIdOf(fresh)}");
Console.WriteLine($"old payload still readable: {orderLinks.Unprotect(token)}");

keyManager.RevokeKey(oldKey.KeyId, reason: "Key file was found in a public backup.");
clock.Restart();
TryUnprotect($"+{clock.ElapsedMilliseconds} ms after revocation", orderLinks, token);
while (true)
{
    await Task.Delay(250);
    try
    {
        orderLinks.Unprotect(token);
    }
    catch (CryptographicException ex)
    {
        Console.WriteLine($"+{clock.ElapsedMilliseconds} ms after revocation: {ex.Message}");
        break;
    }
}
created ed38b457-40dc-4a7b-b3b4-07bc105eba37, activation now
+0 ms: new payload uses key 646f071e-3198-4da4-87b3-1763df17072c
+252 ms: new payload uses key ed38b457-40dc-4a7b-b3b4-07bc105eba37
old payload still readable: order:1042
+0 ms after revocation: order:1042
+252 ms after revocation: The key {646f071e-3198-4da4-87b3-1763df17072c} has been revoked. For more information go to https://aka.ms/aspnet/dataprotectionwarning

Bu çıktı beni ilk denemede şaşırttı. Yeni key'i oluşturduktan hemen sonraki Protect hâlâ eski key'i kullanıyor; revocation'dan hemen sonraki Unprotect de hâlâ başarılı. Sebep, KeyRingProvider'ın .NET 9 ile değişen davranışı: key ring geçersizleştiğinde istek thread'leri yeni ring'in hesaplanmasını beklemiyor, o hesap arka planda yapılırken eski ring'le devam ediyor. Aynı process'te bile bu birkaç yüz milisaniye sürüyor. Revocation diskteki dosyaya revocation-646f071e-....xml olarak yazılıyor; diğer instance'lar onu bir sonraki key ring yenilemesinde, yani yaklaşık 24 saat içinde ya da default key'in süresi dolunca görür. Dokümanın açık uyarısı da bu: revocation'ın bütün deployment'ta hemen etkili olması gerekiyorsa her instance'ı yeniden başlatmalısın.

Tahminin Cevabı: Instance Başına Key Ring

Şimdi açılıştaki soruna dönelim. Bookstore.Web küçük bir uygulama: cookie ile login, /me endpoint'i ve antiforgery korumalı bir form. Data Protection'ı configuration'dan seçilen bir store ile kuruyoruz:

IDataProtectionBuilder dataProtection = builder.Services.AddDataProtection()
    .SetApplicationName("bookstore");

string store = builder.Configuration["DataProtection:Store"] ?? "File";
switch (store)
{
    case "Redis":
        IConnectionMultiplexer redis = ConnectionMultiplexer.Connect(builder.Configuration["DataProtection:Redis"]!);
        dataProtection.PersistKeysToStackExchangeRedis(redis, "bookstore:dataprotection:keys");
        break;
    case "Db":
        builder.Services.AddDbContext<KeysDbContext>(o => o.UseSqlite(builder.Configuration["DataProtection:Db"]));
        dataProtection.PersistKeysToDbContext<KeysDbContext>();
        break;
    default:
        dataProtection.PersistKeysToFileSystem(new DirectoryInfo(builder.Configuration["DataProtection:KeyFolder"]!));
        break;
}

Önce kalıcı volume'ü olmayan iki container'ı taklit ediyoruz: iki instance, her biri kendi key klasörüyle. Ada A'dan login oluyor, sonra aynı cookie ile B'ye gidiyor; antiforgery token'ını da A'dan alıp formu B'ye gönderiyor:

curl -s -c run/jar -X POST http://127.0.0.1:19302/login -d userName=ada
# {"signedIn":"ada","instance":"a"}
curl -s -i -b run/jar http://127.0.0.1:19302/me
# HTTP/1.1 200 OK
curl -s -i -b run/jar http://127.0.0.1:19303/me
# HTTP/1.1 401 Unauthorized
curl -s -i -b run/jar -X POST http://127.0.0.1:19303/checkout -d "bookId=7&__RequestVerificationToken=$TOKEN"
# HTTP/1.1 400 Bad Request
# Content-Length: 0

B'nin log'u:

info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
      Creating key {be684a8d-1102-4079-aa43-b4a1aeed2ae9} with creation date 2026-11-08 05:30:40Z, activation date 2026-11-08 05:30:40Z, and expiration date 2027-02-06 05:30:40Z.
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
      Cookies was not authenticated. Failure message: Unprotect ticket failed
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[12]
      AuthenticationScheme: Cookies was challenged.

Cookie authentication, açamadığı bir cookie'yi hata olarak değil "authenticate olmamış kullanıcı" olarak görür; bu yüzden ortada exception yok, yalnız Information seviyesinde bir satır var. 400'ün sebebi ise varsayılan log seviyelerinde hiç görünmüyor. Log seviyesini Debug'a çektiğimde çıktı:

dbug: Microsoft.AspNetCore.Http.RequestDelegateFactory[9]
      Invalid anti-forgery token found when reading parameter "int bookId" from the request body as form.
      Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery token could not be decrypted.
       ---> System.Security.Cryptography.CryptographicException: The key {10e54d69-49f3-41e8-aa08-5ad62ba04508} was not found in the key ring. For more information go to https://aka.ms/aspnet/dataprotectionwarning

10e54d69... A'nın key'i. Kubernetes'teki hikâyenin tamamı bu: load balancer isteği hangi pod'a gönderirse o pod kendi key ring'iyle cookie'yi açmaya çalışıyor. Deploy'da container'lar yeniden yaratılınca key'ler de gidiyor, herkes logout oluyor. Resmî .NET container image'larında key'ler container'ın içindeki home klasörüne yazılır ve uygulama açılırken bu klasörün container yok edildiğinde kaybolacağını söyleyen bir uyarı log'lanır; o uyarıyı görüyorsan sorun büyük olasılıkla budur.

Aynı iki instance'ı ortak bir Redis key'ine bağladığımızda:

curl -s -c run/jar -X POST http://127.0.0.1:19302/login -d userName=ada
# {"signedIn":"ada","instance":"a"}
curl -s -b run/jar http://127.0.0.1:19303/me
# {"user":"ada","instance":"b"}
curl -s -b run/jar -X POST http://127.0.0.1:19303/checkout -d "bookId=7&__RequestVerificationToken=$TOKEN"
# {"ordered":7}
docker exec aspsecwork-redis redis-cli LLEN bookstore:dataprotection:keys
# 1

Paylaşılan ring tek başına yetmiyor; uygulama adı da aynı olmalı. SetApplicationName satırını geçici olarak silip iki instance'ı aynı Redis key'iyle ama farklı content root'larla başlattım. Redis'te yine tek bir key vardı, buna rağmen B, A'nın cookie'sine 401 Unauthorized döndü ve log'a aynı Unprotect ticket failed satırını yazdı. Uygulama adı purpose zincirinin en tepesinde olduğu için farklı content root, farklı bir protector demek. Container'da bu genelde fark edilmez, çünkü bütün pod'lar aynı yolda çalışır; ama aynı key ring'i paylaşan iki farklı uygulamayı (örneğin bir API ile bir admin paneli) bilerek konuşturmak istiyorsan SetApplicationName zorunlu.

Key Storage Seçenekleri

StorePaket / APINe zaman
Dosya sistemiPersistKeysToFileSystemTek instance ya da paylaşılan bir volume (NFS yani Network File System, Azure Files)
RedisPersistKeysToStackExchangeRedisZaten Redis kullanan çok instance'lı deployment
VeritabanıPersistKeysToDbContext<T>Ek altyapı istemeyen, EF (Entity Framework) Core kullanan uygulama
Azure Blob StoragePersistKeysToAzureBlobStorageAzure'da çalışan, slot'lar arası paylaşım isteyen uygulama

Veritabanı seçeneği için DbContext'in IDataProtectionKeyContext interface'ini uygulaması yeterli:

public sealed class KeysDbContext(DbContextOptions<KeysDbContext> options)
    : DbContext(options), IDataProtectionKeyContext
{
    public DbSet<DataProtectionKey> DataProtectionKeys => Set<DataProtectionKey>();
}
$ sqlite3 run/keys.db "select Id, FriendlyName, length(Xml) from DataProtectionKeys;"
1|key-98da50d2-b3d4-4e40-a547-3aea51910057|884

Tablo üç kolondan ibaret ve key'in XML'ini olduğu gibi tutuyor. Gerçek projede tabloyu EF Core migration'ı ile oluştur; burada kısa olsun diye EnsureCreated kullandım. Redis'te ise key'ler tek bir list'te duruyor. Redis'i yalnız cache olarak çalıştırıyorsan (persistence kapalı, eviction açık) dikkat: Redis yeniden başlarsa ya da key evict edilirse bütün oturumlar düşer. Data Protection için kullanacağın Redis'te persistence açık olmalı ve bu key eviction policy'nin dışında kalmalı.

Key'leri Şifrelemek

Yukarıdaki XML'deki Warning: the key below is in an unencrypted form. yorumu ve açılıştaki No XML encryptor configured uyarısı aynı şeyi söylüyor: key'lerin bulunduğu yere okuma erişimi olan herkes, bütün cookie'leri açabilir ve istediği kullanıcı için yeni bir cookie üretebilir. Windows'ta varsayılan kurulum key'leri DPAPI (Data Protection API) ile şifreler; ama key storage'ı kendin seçtiğin anda bu otomatik şifreleme kapanır. Linux ve macOS'ta hiç yoktur. Çözüm, key'leri bir certificate ile şifrelemek. Certificate'ı çalışma klasöründe openssl ile ürettim:

openssl req -x509 -newkey rsa:3072 -sha256 -days 365 -nodes -keyout certs/dp.key -out certs/dp.crt -subj "/CN=bookstore-dataprotection"
openssl pkcs12 -export -inkey certs/dp.key -in certs/dp.crt -out certs/dp.pfx -passout pass:$PW
string? certPath = builder.Configuration["DataProtection:CertificatePath"];
if (certPath is not null)
{
    X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12FromFile(
        certPath, builder.Configuration["DataProtection:CertificatePassword"]);
    dataProtection.ProtectKeysWithCertificate(certificate).UnprotectKeysWithAnyCertificate(certificate);
}

Bu kez uyarı log'a düşmedi ve key dosyasındaki master key yerini şifreli bir bloğa bıraktı:

<encryptedSecret decryptorType="Microsoft.AspNetCore.DataProtection.XmlEncryption.EncryptedXmlDecryptor, Microsoft.AspNetCore.DataProtection, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60" xmlns="http://schemas.asp.net/2015/03/dataProtection">
  <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
    <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
        <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
        ...

UnprotectKeysWithAnyCertificate neden gerekli? EncryptedXmlDecryptor varsayılan olarak certificate'ı işletim sisteminin certificate store'unda arar. Certificate'ı dosyadan ya da bir secret store'dan yüklüyorsan, çözme tarafına da açıkça vermen gerekir. Aynı method certificate rotation'ın da anahtarı: yeni certificate'la şifrelemeye geçerken eski certificate'ı buraya ek olarak verirsin, eski key'ler okunmaya devam eder. Azure'da aynı işi ProtectKeysWithAzureKeyVault yapar. Certificate'ın ve parolasının nerede tutulacağı Configuration ve Secret Management serisinin konusu; certificate'ın kendisini Geliştirici için Kriptografi serisinde ayrıntılı ele alacağız.

Üretimde Dikkat

İlk iş, uygulamanın açılış log'una bakmak: Creating key satırını her deploy'da görüyorsan key'lerin kalıcı değil demektir. Sağlıklı bir çok instance'lı sistemde bu satır yalnız 90 günde bir, tek bir instance'tan gelir. İkinci gösterge, Unprotect ticket failed sayısı; cookie authentication bunu Information seviyesinde yazar, bir log sorgusuyla sayılabilir. Serinin "Güvenlik Olaylarını Gözlemlemek" yazısında bunu bir alarma bağlayacağız.

Instance'lar aynı anda boş bir store'la açılırsa ikisi de key üretebilir. Bu tehlikeli değil: ring yalnızca iki key içerir ve KeyRingProvider açılıştan sonraki iki dakika boyunca bilmediği bir key id'si görünce ring'i yeniden okur. Yine de ilk deploy'u tek replica ile yapmak log'u temiz tutar.

Revocation acil durum prosedürüdür ve adımları önceden yazılı olmalı: yeni key'i oluştur, eskisini revoke et, bütün instance'ları yeniden başlat, Unprotect ticket failed artışını izle. Herkes bir kez logout olacak; bunu kabul edilebilir bir bedel olarak planla. Data Protection uzun süreli veri şifrelemek için tasarlanmadı. Veritabanında yıllarca duracak bir alanı IDataProtector ile şifrelersen key rotation ve revocation o veriyi de etkiler; kalıcı veri için Geliştirici için Kriptografi serisindeki yaklaşımlara bak.

Sık Düşülen Tuzaklar

Key klasörünü container'ın içinde bırakmak en sık görülen hata; belirtisi deploy sonrası toplu logout ve sporadik 400'ler. Key'leri kalıcı volume'e ya da paylaşılan bir store'a koy.

Key storage'ı ayarlayıp şifrelemeyi unutmak. Redis'teki ya da veritabanındaki düz metin key, o sisteme okuma yetkisi olan herkese bütün kullanıcıların oturumunu verir.

Eski key'leri "temizlik" için silmek. Expire olmuş key'ler hâlâ Unprotect için kullanılır; silersen o key'le şifrelenmiş her şey geri dönülmez biçimde açılmaz olur. Key'ler küçük, birikmeleri sorun değil.

Staging ve production'ı aynı key ring'e bağlamak. Bu durumda staging'de üretilen bir cookie production'da geçerli olur. Ortamlar arasında ring paylaşma; aynı ring'i bilinçli olarak paylaşan uygulamalarda da farklı SetApplicationName değerleri izolasyonu korur.

Küçük Bir Deneme

★☆☆ Konsol uygulamasında CreateNewKey'i aktivasyon tarihi iki gün sonrası olacak şekilde çağır. Yeni Protect'ler hangi key'i kullanıyor? GetAllKeys çıktısında iki key'in tarihlerini yorumla.

★★☆ Bookstore.Web'i iki instance ve ortak bir dosya klasörüyle çalıştır, A'da login ol, sonra key XML'ini elle düzenleyerek expirationDate'i geçmişe çek ve iki instance'ı yeniden başlat. Cookie hâlâ açılıyor mu, yeni bir key oluşuyor mu?

★★★ İki farklı certificate üret. Önce birincisiyle key'leri şifrele, sonra ProtectKeysWithCertificate'a ikincisini, UnprotectKeysWithAnyCertificate'a ikisini birden ver. Eski cookie'nin açıldığını ve yeni key'in ikinci certificate'la şifrelendiğini XML'den doğrula.

Key ring artık bütün instance'larda aynı. Bir sonraki yazıda yine çok instance'lı dünyanın bir sorununa, ama bu kez trafiğin kendisine bakıyoruz: rate limiting algoritmaları, partition key'leri ve chained limiter'lar.