Asp.Net Authorization

Engin UNAL
Bilişim Hareketi
Published in
8 min readOct 14, 2021

Yetkilendirme ve bu alana kullanılan kavramlar incelenecek, bunlarla ilgili temel bilgiler verilip yazının sonuna doğru requirement, handler, policy provider gibi konulara değinilecektir. İyi okumalar.

Bildiğinizi varsayarak hatırlatma amacıyla Authentication ve Authorization kavramlarının birbirinden çok farklı konular olduğunu bu yazıda sadece Authorization konusuna değinileceğini belirtmek isterim. Authentication, kimlik doğrulama işlemidir. Kimlik kartı gibi düşünülebilir. Bir işleme yetkinizin olup olmadığına karar verebilmek için öncelikle kim olduğunuzu doğru bir şekilde bilmek gereklidir. Örneğin geliştirdiğiniz bir web sitesinde kullanıcıların login edebilmesi için öncelikle sisteminizde kimlerin login edebileceğinin tanımlı olması yani kullanıcı kodu ve şifre gibi bilgileri sağlamaları gerekir. Kullanıcı kodu ve şifre bilgilerini sağlayan kullanıcılar sistem tarafından doğrulanmış ve login edebilecek kullanıcılardır. Authentication bu noktada sisteminize giriş yapacak kullanıcıların kontrolünün gerçekleştirildiği katmanda yer alır.

Authenticaton(Kimlik Doğrulama) işleminden sonra yetkilendirme işlemlerine geçilir. Authorization ise yetkilendirme işlemidir. Kim olduğunu bildiğiniz yani kimliğini doğruladığınız bir kullanıcının yapmak istediği işleme yetkisi olup olmadığının belirlendiği süreç yetkilendirme sürecidir.

Buraya kadar olanları bir örnekle açıklamaya çalışırsak; Diyelim ki yüksek güvenlikli bir binada çalışıyorsunuz ve binadaki her kat için ayrı yetkiler gerekiyor. Bina girişinde ve her katta kart okutma cihazları mevcut ve kartınızı okuttuktan sonra eğer kata giriş yetkiniz varsa kapı açılıyor. Elinizdeki kart Authentication için kullanılır, eğer kartınız yoksa binaya giriş yapamazsınız. Kartınız varsa ve birinci kata girmek istiyorsanız birinci kat yetkisine sahip olmanız gerekir. Kat yetkileri Authorization konusudur. Hangi kartın hangi kapıları açabileceği kontrolleri Authorization katmanında yapılır. Kart okutulduğunda bu yetkiler kontrol edilerek ilgili kapı açılır veya açılmaz.

Yazının ilerleyen kısımlarında Asp.Net Authorization nasıl kurgulanıyor ve nasıl çalışıyor inceleyeceğiz. Authorization mimarisinde Role Based, Claims Based ve Policy Based Authorization nedir? Neler yapar? Sorularına cevap arayacağız. Fakat bunlardan önce User, Role, Claim kavramları ve Asp.net Core Identity konusunda ön bilgiler ile başlayalım.

ASP.NET Core Identity

Asp.Net Core Identity, kullanıcı tanımları, roller, claim tanımları, login, token vb. işlemleri yapabileceğimiz fonksiyonları içeren bir framework sunar. Eskiden membership ismiyle bilinen mimarinin güncel gereksinimler eklenerek esnek bir mimaride yazılması ile ortaya çıkmıştır. Login işlemleri için UI(User Interface) kütüphanesi de içermektedir. Standart olarak Entity Framework (EF) Core veri modelini kullanır. Kullanıcı hesapları ve login bilgileri Identity’de depolanabilir veya harici bir login provider kullanabilir.

Identity modeli aşağıdaki temel entity tiplerinden oluşmaktadır:

User içerisinde kullanıcı bilgileri(isim, şifre, email, ..) bulunmaktadır. Role ise sadece role adını barındırır. UserClaim class içerisinde UserId bilgisi ve Claim tipi ve değerini içeren ClaimType ve ClaimValue alanları bulunur, user’a verilen claim’lerin tutulduğu yapıdır. UserToken class harici provider token’larını tutar. UserLogin içerisinde harici provider(google, twitter.. ) bilgileri tutulur. RoleClaim, kullanıcıların gruplandığı role’lere verilen claim bilgilerini tutar. UserRole class içerisinde ise user ve role bağlantısı tutulur.

Temel olarak entity’lerin kullanım amacı bu şekildedir. Bu entity’ler isimlerine veya persist edildikleri ortama bağlı olmadan esnek çalışmaktadır. Yani mevcut entity’den mirasla yeni class tanımları yapılıp, farklı persistance ortamlarına aktarılıp kullanılabilir.

Örneğin User tablosuna bir kolon eklemeyi istediğinizde IdentityUser’dan türetip istediğiniz eklemeleri yeni class’ınız içerisinde yapabilir ve IdentityUser class’ının tüm işleyişini yeni tanımladığınız class ile devam ettirebilirsiniz.

using System;
using Microsoft.AspNetCore.Identity;
public class AspNetUser : IdentityUser<Guid>
{
public int CustomValue { get; set; }
}

Bu tip özellik ekleme veya customization işlemlerini diğer Identity class’larını da kullanarak uygulayabilir, tablo isimleri de dahil tamamen kendi kurguladığınız bir model mimarisi kurabilirsiniz. Sistemin çekirdeğindeki tasarımı miras olarak alacağınızdan üstünde yapacağınız eklemeler sistemin çalışmasını etkilemeyecektir. Model class’larının hazırlanması ve DBContext içerisine eklenip migration yapılması işlemleri konusunda çok fazla örnek bulabileceğiniz için yazının aşırı uzun olmasını önlemek açısından diğer konulara geçerek devam edelim.

User, Role ve Claim

User, Identity sistemindeki kullanıcı verisini tutan yapıdır. Yukarıda anlatıldığı üzere istenen eklemeler yapılmasına imkan tanır. UserManager sınıfının sunduğu metodlarla kullanıcı oluşturma, silme, güncelleme, şifre değiştirme ve diğer bir çok işlem gerçekleştirilir.

Role, kullanıcıların bir rol altında gruplandırılabilmesine imkan tanıyan yapıdır. Örneğin Muhasebe departmanında çalışanların isimleri Leyla, Ahmet, Mehmet olsun. İhtiyacımız olan düzenlemede Muhasebe isminde bir grup tanımlamak ve personeli bu gruba atamak olacak. Muhasebe isminde yeni bir role açıp isimleri verilen personeli UserRole tablosuna ekleriz. Bu şekilde kullanıcılara tek tek yetki vermek yerine Muhasebe role’una yetki vererek yetkilendirme sürecinde gruplama kullanabiliriz. Role ile ilgili işlemleri RoleManager sınıfının sunduğu metodlar ile gerçekleştirebiliriz. User ve Role bağlantıları için UserManager class kullanılır.

Claim, type-value(tip-değer) çiftinden oluşan tanımlayıcı bir yapıdır. Identity oluşturulduğunda bir veya daha çok claim bu identity’e atanabilir. .Net framework tarafından ön tanımlı claim tipleri bulunmaktadır. Claim tiplerini bu linkten inceleyebilirsiniz. Bunlara ek olarak kendi claim tipinizi de ekleyip kullanabilirsiniz. Claim konusunu örnekle açıklamak gerekirse; Kimlik kartı örneğini verebiliriz. Kimlik belgesi almak için otoritenin(issuer) koyduğu kuralları sağlayıp onay almanız gerekir. Onay alındığında bir kimlik kartınız olur. Kart üzerindeki alanlardan örneğin doğum tarihi alanı claim type ve doğum tarihi alanında yazan doğum tarihi bilgisi ise sizin claim value bilginiz olacaktır. Bu kimlik kartını kullandığınız yerlerde eğer doğum tarihi claim’ine göre kontrol yapılan bir nokta varsa(örneğin 18 yaşından küçükler giremez gibi) doğum tarihi değeri kontrol edilir ve onay-red işlemi gerçekleştirilir.

Buraya kadar anlatılanların daha açık görülebilmesi için modellerin görsel karşılığı aşağıda verilmiştir.

Artık üç temel yetkilendirme mimarisine geçebiliriz. Role, claim ve policy yetkilendirmelerine sırasıyla geçelim.

Role Based Authorization

Bir user oluşturulduğunda bunun bir veya daha çok role içerisinde olabileceğinden bahsetmiştim. Örneğin Ali kullanıcısı Yazılım ve Yönetim role’lerinde olabilir. Role tabanlı yetkilendirmede uygulamamızın bir bölümüne sadece istediğimiz role’lere üye olanların erişebileceği bir yapı kurgulayabiliriz. Yukarıdaki örnekte devam edersek Yönetim rolünde olanların erişebileceği bir Controller yazarsak :

[Authorize(Roles = "Yonetim")]
public class YonetimController : Controller
{
}

Böylece YonetimController’a sadece Yonetim role’üne üye olan kullanıcılar erişebilir. Örnekleri çoğaltabiliriz. Muhasebe, Yonetim rollerimiz olsun Controller içerisindeki bazı bölümlere bunların kontrollü olarak erişebileceği bir yapı da kurgulayabiliriz. Örneğin MaasIslemleri Controller içerisine Muhasebe ve Yonetim rollerindekiler erişebilir fakat sadece Yonetim üyeleri maaş zammı yapabilir gibi.

[Authorize(Roles = "Yonetim, Muhasebe")]
public class MaasIslemleriController : Controller
{
public ActionResult MaasGoster()
{
}
[Authorize(Roles = "Yonetim")]
public ActionResult MaasZammiYap()
{
}
}

Role gereksinim kontrolleri Policy yazımı ile de gerçekleştirilebilir. Bunun için Startup.cs içerisindeki ConfigureServices metodunda aşağıdaki gibi tanımlama yapılabilir.

public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthorization(options =>
{
options.AddPolicy("YoneticiRoluAra",
policy => policy.RequireRole("Yonetici"));
});
// ...}

Yukarıdaki örnekte YoneticiRoluAra policy tanımı içerisinde Yonetici rolü gereksinimi eklendi. Yani YoneticiRoluAra policy kontrolünün yapıldığı noktalarda Yonetici rolu içinde olan kullanıcılar bu kontrolden geçebileceklerdir.

[Authorize(Policy = "YoneticiRoluAra")]
public IActionResult MaasZammiYap()
{
return View();
}

MaasZammiYap metodu YoneticiRoluAra policy kontrolünü yapmaktadır. Bu policy içerisinde ise requirement olarak Yonetici rolü tanımlı olduğundan bu kontrolü geçebilmesi için Yonetici rolünde olan bir kullanıcı olması gerekir.

Claims Based Authorization

Önceki konularda bir identity için birden çok claim atanabileceğinden bahsetmiştim. Yetkilendirme kontrollerinde claim kontrolü policy ile yapılmaktadır. Bir policy tanımı yapılır ve requirement olarak içerisine claim eklenerek(RequireClaim) tamamlanır. Kontrol noktalarında policy çalıştırılarak içerisindeki claim kontrollerinin tek tek yapılması sağlanır. Örnekle inceleyelim.

public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthorization(options =>
{
options.AddPolicy("AraciOlanlar",
policy => policy.RequireClaim("AracPlakasi"));
});
// ...}

Yukarıda AraciOlanlar policy tanımı içerisinde claim olaran AracPlakasi olanlar requirement yani gereksinim olarak eklenmiş. Buna göre AracPlakasi claim’ine sahip olan identity’ler AraciOlanlar policy kontrolünden geçmiş olacaklar.

[Authorize(Policy = "AraciOlanlar")]
public IActionResult OtoparkRezervasyonu()
{
return View();
}

Yine bir örnek olarak araç plakası 34 veya 06 olanların erişimine izin verilen bir fonksiyon düşünelim.

public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthorization(options =>
{
options.AddPolicy("34Ve06Plakalilar",
policy => policy.RequireClaim("AracPlakaIli","34","06"));
});
// ...}

Kontrolünü sağlayalım.

[Authorize(Policy = "34Ve06Plakalilar")]
public IActionResult OtoparkRezervasyonu()
{
return View();
}

Otopark rezervasyonu için araç plaka ili 06 ve 34 olanların erişebileceği kontrolü eklemiş olduk.

Policy Based Authorization

Önceki iki yetkilendirme kontrolünde requirement ve policy yapılarını kullanmıştık. Bu yapılar yetkilendirme kontrollerinde esneklik getirmekteler. Policy tanımları içerisine eklediğimiz requirement’lar role veya claim olmak zorunda değiller, buna ek olarak birden çok requirement ekleme imkanımız da mevcut. Policy yapıları, içerisinde claim, role veya sizin belirleyeceğinzi başka tiplerdeki gereksinimleri gruplayan yapılardır. Yani temel olarak gruplama için kullanılan yapılar gibi düşünülebilir. Bunları detaylı inceleyeceğiz.

Öncelikle dikkat çekmek istediğim iki kavram bulunmakta: Requirement ve Authorization Handler. Bu iki yapıyı kullanarak kendi requirement ve kontrol mekanizmamızı eklememiz mümkün. Öncelikle requirement nedir? Ona bakalım.

Bir policy tanımladığımızda içerisine bir takım requirement’ler ekleyebiliriz, bu requirement’ların tamamının kontrolünden başarıyla geçilmesi durumunda policy yetkilendirmesinin başarıldığı sonucu alınır. Requirement gereksinimleri ifade eder. Role based authorization konusunda requirement olarak RequireRole eklemiştik, Claim based konusunda ise RequireClaim kullanmıştık. Örnekler çoğaltılabilir. Buradaki önemli nokta ön tanımlı requirement tiplerine ek olarak Custom Requirement da kullanabiliyor olmamızdır. Custom Requirement ile kendi tanımlayacağımız requirement tiplerini de policy içerisine ekleyebiliriz ve bunların kontrolünü Handler yapılarıyla sağlarız. Bu durumda yukarıdaki grafiğe handler eklendiğinde son durum aşağıdaki gibi olur.

IAuthorizationRequirement interface’inden kendi kullanacağım bir custom requirement türettim(adı önemli değil) bu requirement’ı da yine kendi yazdığım Custom Handler’da kullandım, bu handler da AuthorizationHandler sınıfından türetildi.

Bir kod örneğiyle inceleyelim. Örneğin araç plakasının 34 olmasını kontrol eden bir uygulama olsun. Requirement olarak araç plakasını alsın ve handler’da bunu kontrol etsin.

Görüldüğü üzere PlakaIliRequirement bizden plaka il bilgisini alır.

Handler içerisinde requirement kontrolümüzü yapıyoruz. Bu tip kullanımla tamamen sizin belirlediğiniz requirement’ları istediğiniz algoritmayla yetki kontrolünü gerçekleştirebilirsiniz. Requiremet tanımının policy’e ekleme işlemi ise aşağıdaki gibi yapılabilir.

Daha sonra Controller içerisinden tanımlanan policy kullanımı yapılabilir.

[Authorize(Policy = "34PlakaKontrolu")]
public class TestController : ControllerBase
{
}

Bu yazıda kafa karışıklığı oluşmasın diye değinmeyeceğim bir konu olan Authorization Attribute kullanarak [Authorize(Policy=”34PlakaKontrolu”)] yerine [34PlakaKontrolu] gibi doğrudan ismini kullanarak çağırabiliriz. Daha pratik bir kullanım şeklidir, Policy= şeklindeki yazım ile aynı çalışır. Neyse bunu geçerek yazının son konusu olan Policy Provider ile devam edelim.

Önce problemi tespit edelim. Yazdığımız kodda 34 plaka kontrolü için bir policy tanımlamıştık. Bu durumda 06, 35, 41, … plakaları kontrolü için de başka policy’ler tanımlamamız gerekecek ve bu şekilde policy’lerimiz artmaya devam edecek. Buna çözüm olarak Policy Provider yazıp enjekte(DI) edeceğiz. IAuthorizationPolicyProvider’dan kendi Policy Provider sınıfımızı yazıp gelen parametrelere göre policy üreteceğiz. Önemli Bilgi; Asp.Net Core mimarisinde IAuthorizationPolicyProvider’dan türemiş bir adet instance kullanılabilir. IAuthorizationPolicyProvider ile yukarıda anlatılan soruna bir örnekle çözüm üretelim.

Artık [Authorize(Policy = “PlakaKontrolu(34)”)] gibi policy kontrolunu parametre göndererek yapabiliriz. Buna attribute kullanımı da eklendiğinde [PlakaKontrolu(34)] şeklinde daha kısa yazımla da kullanmak mümkün.

Yukarıdaki kod şu şekilde çalışır. Policy kontrolü işlemi başladığında öncelikle policy provider içindeki GetPolicyAsync metoduna girer ve eğer policy ismi PlakaKontrolu ile başlıyorsa devamında gelen plaka bilgisi alınır ve requirement olarak PlakaIliRequirement yaratılarak yeni bir policy oluşturulur. Devamındaki çalışmada PlakaIliHandler çalışır ve requirement içinde verilen plakayı kontrol ederek karar verir.

Custom Policy Provider örneği olarak .Net Core sample repo linki aşağıda paylaşılmıştır.

Teşekkürler.

Engin ÜNAL

--

--