Uygulama geliştirirken kullandığımız dil dışında, üzerinde çalıştığımız “framework” ya da “kütüphane” API‘larına da hakim olmak, bazı gereksinimleri kolay bir şekilde sunabilmek ya da kullandığımız “framework”‘ü daha etkin kullanabilmek için oldukça önemli diye düşünüyorum. Bu yaklaşımla ASP.NET Core içindeki küçük bir API’dan bahsetmeye çalışacağım; -ki farklı senaryolar için ihtiyaç duyulabilecek güvenli ya da limitli veri modelleri oluşturmak için faydalı olabilir.
Sadece belirli bir süre için geçerli olacak veri modelleri ya da “text” ifadeler zaman zaman ihtiyacımız olan bir yaklaşım olabiliyor. Üyelik işlemlerinde “E-mail onayı” için gönderilen linkler ya da şifre yenilemek için gönderilen linkler sanırım birçok kişiye tanıdık gelecektir. Ya da “soft-OTP”(One-time password) senaryolarında belli bir zaman geçerli olacak kodlar ya da “Bearer” token gibi çeşitli değerler…
Açıkçası bu tarz gereksinimler için farklı yöntemler ve yaklaşımlar tabi ki mümkün. Çok derinlere girmeden, .NET platformunda, bu tarz ihtiyaçları nasıl karşılayabiliriz kısaca bundan bahsetmeye çalışacağım.
.NET ve özellikle ASP.NET Core günümüz uygulamalarının güvenlik ihtiyaçlarını karşılayabilmek için birçok API ile bize yol gösteriyor bildiğiniz gibi. Güçlü şifreleme API’ları, HTTPS kavramları, CORS mekanizmaları, CRSF önlemleri, veri koruma, kimlik doğrulama, yetkilendirme, “secret” kullanımı gibi gibi…
Yukarıda az önce bahsetmiş olduğum gereksinim için, “veri koruma” çatısı altında, .NET’de ki ITimeLimitedDataProtector ara yüzüne bakalım. Sadece belli bir süre geçerli olacak veriler ya da ifadeleri bu arayüzün sağladığı metotlar ile karşılayabiliyoruz.
Bu arayüzün metotlarını kullanabilmek için, öncelikle Microsoft.AspNetCore.DataProtection.Extensions kütüphanesine ihtiyacımız var. Genel olarak bu kütüphane .NET içerisinde “veri koruma” özelliklerinin dışarı sunulduğu bir kütüphane.
ITimeLimitedDataProtector arayüzünü kullanabilmek için öncelikle bir tane “DataProtectionProvider” yaratmamız, daha sonra bu “provider” ile de verimizi koruyacak bir koruyucu tanımlamamız gerekmekte.
var timeLimitedDataProtector = DataProtectionProvider.Create("SomeApplication") .CreateProtector("SomeApplication.TimeLimitedData") .ToTimeLimitedDataProtector();
Burada metotların parametrelerine baktığınızda göreceğiniz “string” ifadeler önemli; oluşturulan provider ve DataProtector‘lerin bir nevi etiketlenmesi olarak düşünülebilirsiniz. Bu etiketlemeye göre, verilerin güvenliğinin amacı ve kapsamı belirtilmiş oluyor gibi düşünebiliriz. Bu ifadeler veriyi korumak için kullanılacak anahtarların oluşturulmasında kullanılıyor. Böylece, DataProtectionProvider.Create(“abc”) ile oluşturduğunuz bir provider, DataProtectionProvider.Create(“xyz”) şeklinde oluşturulan provider’ın güvenliğini sağladığı ifadelere erişemiyor.
DataProtectionProvider.Create() metodunun parametrelerine baktığınızda veriyi korumak için bazı özellikleri ayarlayabileceğimizi görüyoruz. DirectoryInfo tipinde bir dizin ile veri korumak için kullanılacak anahtarların nerede olacağını ya da X509Certificate2 ile de anahtarların ek olarak bir bir sertifika ile şifreleneceğini belirtebiliyoruz. Bunların çok ayrıntısına girmeyeceğim ama burada belirtmek istediğim, parametreler ile veri koruma şekillerini özelleştirip, koruma yaklaşımlarını değiştirmek mümkün.
Bu şekilde yarattığımız timeLimitedDataProtector değişkeni üzerinden korumak istediğimiz ifadeyi, Protect() metodu ile zaman aralığı belirtip koruyama alıyoruz.
ProtectedData = timeLimitedDataProtector.Protect(plaintext: jsonString , lifetime: TimeSpan.FromSeconds(LifeTime));
Yukarıdaki ifade ile “Hello World” ifadesini şifreleyip, hash’leyip koruma altına alıyoruz. ProtectedData özelliğimiz 20 saniye geçerli olmak üzere aşağıdakine benzer bir yapıya dönüşüyor.
VGhlIGFuc3dlciB0byB0aGlzIGlzIHZlcnkgc2ltcGxlLiBJdCB3YXMgYSBqb2tLiBJdCBoYWQgdG8gYmUgYSBudW1iZXIsIGFuIG9yZGluYXJ5LCBzbWFsbGlzaCBudW1iZXIsIGFuZCBJIGNob3NlIHRoYXQgb25lLiBCaW5hcnkgcmVwcmVzZW50YXRpb25zLCBiYXNlIHRoaXJ0ZWVuLCBUaWJldGFuIG1vbmtzIGFyZSBhbGwgY29tcGxldGUgbm9uc2Vuc2UuIEkgc2F0IGF0IG15IGRlc2ssIHN0YXJlZCBpbnRvIHRoZSBnYXJkZW4gYW5kIHRob3VnaHQgJzQyIHdpbGwgZG8nIEkgdHlwZWQgaXQgb3V0LiBFbmQgb2Ygc3Rvcnku
Protect() metodunun lifetime parametresi ile TimeSpan şeklinde her türlü zamanı belirtebiliyoruz tabi ki de.
Koruma altına aldığımız; şifrelenmiş ve hash’lenmiş ifadeyi, bu örnekteki gibi 20 saniye içerisinde Unprotect() metodu ile açıp, tekrardan “Hello World” ifadesine ulaşabiliyoruz. Ama 20 saniyeden sonra bu değere ulaşmak mümkün olamıyor ve koruduğumuz veri geçerliliğini kaybediyor.
string data = timeLimitedDataProtector.Unprotect(protectedData);
Bu API ile uzun süre ya da belirsiz süre boyunca veri korunması tavsiye edilmiyor. Bunun sebebi korumaya aldığınızda veriyi şifrelemek ve hash’lemek için kullanılan anahtarların sürekliliğinin sağlanmasının riski. Eğer uzun süre koruma altında saklamanız gereken ifadeler varsa, farklı yöntemler ile ilerlemek mümkün ya da bu API’ın sağladığı interface’ler ile kendi ihtiyaçlarımıza göre farklı geliştirmeler yapılabilir.
Önemli bir nokta da, koruma altına sadece “text” ifadeler alabiliyoruz. Dolayısıyla biraz daha kompleks verileri, “serialize” edip (Ör: JsonSerializer gibi) koruma altına almak mümkün.
Komple resmi daha net görmek için, örnek olması için bir tane ASP.NET Core uygulaması üzerinden Razor sayfa modelinin koduna bakalım.
namespace SomeApplication.Pages { using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; using System; using System.Text.Json; public class IndexModel : PageModel { private readonly ILogger<IndexModel> _logger; public string ProtectedData { get; private set; } public string Data { get; private set; } public int LifeTime { get; private set; } = 300; public string Error { get; private set; } public IndexModel(ILogger<IndexModel> logger) { _logger = logger; } public void OnGet(string protectedData) { var timeLimitedDataProtector = DataProtectionProvider.Create("SomeApplication") .CreateProtector("SomeApplication.TimeLimitedData") .ToTimeLimitedDataProtector(); //URL'de ?protecdata= ifadesi boş if (string.IsNullOrEmpty(protectedData)) { //Bir tane basit nesne modelimiz olsun var data = new SomeDataModel { Name = "Arda Cetinkaya", EMail = "somemail@mail.com", SomeDate = DateTimeOffset.Now }; //Bu modelimizi JSON olarak "text" şeklinde ifade edelim. string jsonString = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); Data = jsonString; //Yukarıda LifeTime=20sn. şeklinde tanımladığımız özellikle, modelimizi //20 saniye geçerli olabilecek bir ifade ile korumaya alalım. ProtectedData = timeLimitedDataProtector.Protect(plaintext: jsonString , lifetime: TimeSpan.FromSeconds(LifeTime)); } else { //Sayfaya ?protecdata=a412Fe12dada... şeklinde erişim olduğunda try { //Gelen değerin korumasını kaldırıp, korumaya aldığımız değere ulaşalım string data = timeLimitedDataProtector.Unprotect(protectedData); Data = "Data is valid"; } catch (Exception ex) { Error = ex.Message; } } } } public class SomeDataModel { public string Name { get; set; } public string EMail { get; set; } public DateTimeOffset SomeDate { get; set; } } }
Yukarıdaki örnekte, bir JSON ifadeyi 20 saniye geçerli olacak bir korumaya alıyoruz ve bunu bir link ile ilişkilendiriyoruz. 20 saniye boyunca link geçerli olacak ve korumaya aldığımız değer geçerli olacaktır. Ama 20 saniye sonunda korumaya aldığımız veri geçerliliğini kaybedecektir.
Bu basit ve hızlı yazı ile uzun bir aradan sonra birşeyler yazmış olmak bana fayda getireceği gibi umarım bu yazı da bazı soru işaretlerini gidermek için sizlere bir kapı açar ve çeşitli çözümlerinizde fayda sağlar. Bir sonraki yazıda görüşmek üzere.
Yazıda küçük bir anektot saklı, bulan olur ve yüzünde küçük bir gülümseme oluşursa ne mutlu 😊