Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII
Merhaba,
Bu içeriğimizde Asp.NET Core Identity yazı dizimizin 17. makalesi olan Claim Bazlı Kimlik Doğrulama(Claims Based Authorization) yöntemini inceleyeceğiz.
Herşeyden önce kavramsal temelleri oturtabilmek için ilk olarak temel tanımlamalarda bulunmamız gerekmektedir. O halde buyrun Claim’ın ne olduğundan başlayalım.
Claim Nedir?
Claim kelimesinin birçok sözlük anlamı mevcuttur. Bunlar; fiili olarak “talep etmek(f)“, “iddia etmek(f)” olmakla birlikte isim olarak ise “sav(i)“, “istek(i)” yahut “iddia(i)” karşılığına gelmektedir. Yazılımsal açıdan olayı değerlendirdiğimizde ise kullanıcı hakkında key – value şeklinde hususi bilgiler tutan ve bunları bizlere yaptığımız talepler neticesinde getiren bir yapı söz konusudur.
Claim Ne Amaçla Kullanılmaktadır?
Yukarıda bahsedildiği gibi kullanıcı hakkında hususi bilgiler tutabilmek için kullanılmaktadır. Tabi temel amaç bu değildir. Asıl amaç, bu bilgileri yetkilendirme senaryolarından rol bazlı yetkilendirmenin sınırlarını daha da esnetebilmek ve uygulamada işlev görecek olan kullanıcının edindiği rolün dışında özelleştirilmiş kontroller sağlayabilmek için tercih edilmektedir. Söz gelimi bu tarz bir kullanımada Claim Bazlı Yetkilendirme denmektedir.
Neden Claim Bazlı Yetkilendirme Kullanmalıyız?
Bu sorunun cevabını şöyle örneklendirebiliriz; internet üzerinden video hizmeti veren bir web uygulamasını misal olarak ele alalım. Sistem tarafından “İzleyici” rolü atanmış kullanıcılara video hizmeti verildiğini düşünelim. Ayriyetten uygulamada “komedi”, “gerilim” ve “korku” kategorilerinin mevcut olduğunu ve her bir kategorinin belirli bir yaş aralığı tarafından erişilebilir olduğunu düşünelim. Evet, burada her ne kadar rol bazlı yetkilendirme olmuş olsada neticede bu yaş aralığının rol bazlı yetkilendirmeyle ölçülebilmesi pek mümkün değildir. Nihayetinde her bir kategoriye erişim için öncelikli şart “İzleyici” rolünde olunmasıdır lakin her bir kategori başlı başına sisteme giriş yapmış ve “İzleyici” rolünü almış o kullanıcının yaş aralığını kontrol etmeli ve uygunsa erişim sağlamalıdır. İşte burada yaş aralığını ölçebilmek için ilgili kullanıcılara yaş değerlerinin claim olarak atanması sağlanmalı ve claim bazlı bir yetkilendirme yapılmalıdır.
Ya da bir başka örnek vermemiz gerekirse; bir makale sitesinde makaleleri kullanıcılara ödeme yapıp yapmama bilgilerine göre gösterip göstermeme durumu yine claim bazlı bir yetkilendirmenin sayesinde gerçekleşecektir. Nihayetinde burada her bir kullanıcıya atanan “Kullanıcı” rolü ile kullanıcılar tanımlanırken, ödeme bilgisi ise claim ile eklenmekte ve claim tabanlı yetkilendirme ile kontrol edilmektedir.
Yani uzun lafın kısası;
Claimler; rollerin dışında kullanıcı hakkında bilgi tutmamızı ve bu bilgilere göre yetkilendirme yapmamızı sağlayan yapılardır.
Claim Mekanizması Nasıl Çalışmaktadır?
Kullanıcıya dair tanımlanmış ekstra bilgiler olan claimleri kullanarak yetkilendirme işlevini gerçekleştirebilmek için cookie değeri kullanılmaktadır. Sisteme giriş yapacak olan kullanıcıya özel tanımlanmış claimler oluşturulan Cookie değerinin içerisine eklenmekte ve lazım olduğu taktirde oradan erişilmektedir. Burada Peki cookie içerisine claim bilgileri nasıl eklenmektedir? sorunuzu duyar gibiyim… Bu soruya esas cevabı ilerleyen satırlarda pratik olarak vereceğimizden dolayı şimdilik ‘iki farklı yöntemle ekleme yapabiliriz’ şeklinde kısa bir karşılıkla neticelendirelim. Yine de bir iki kelamla teorik olarak bahsetmemiz gerekirse bu iki yöntemden ilki kullanıcıya dair cookie değeri oluşturulurken o anda (dinamik olarak)eklemeyken bir diğeri ise veritabanında kullanıcıyla ilişkisel tutulan claim verilerinin derlenip eklenmesidir.
Velhasıl şimdi biz claim yapılanmasının mekanizmasını somutlaştırmaya devam edelim. Kullanıcıya dair claimlere erişebilmek için aşağıdaki görselde şematize edildiği gibi “System.Security.Claims” napespace’i altında bulunan ClaimsPrincipal türünden olan “User” propertysinin “User.Claims” komutunu kullanabiliriz.
Ayrıca yine aynı namespace altında bulunan “ClaimsIdentity” türünden olan nesne üzerinden ilgili kullanıcının tüm verilerini elde edebilir ve claimlerine erişebiliriz.
Claim Ekleme Yöntemleri
Uygulamaya başarılı bir şekilde giriş gerçekleştiren kullanıcıya “Dinamik Ekleme” ve “Veritabanından Ekleme” olmak üzere o anki ihtiyaca istinaden teknik açıdan farklı iki yöntemle claim dahil edebilmekteyiz. Tabi ki de bu dahil edilen claimler süreçte kullanıcı için cookie bilgilerinde tutulacak ve gerektiği taktirde oradan erişilecektir. Şimdi gelin bu iki yöntemi inceleyelim;
- Dinamik Ekleme
Kullanıcıya dair eklenecek claimin veritabanındaki AspNetUserClaims tablosunda herhangi bir karşılığı yahut fiziksel bir kaydının olmaması durumunda direkt olarak cookie değerine dahil edilmesi ile kullanılan yöntemdir. Genellikle veritabanında tutulmasına lüzum olmayan yahut AspNetUserClaims tablosunun dışındaki tablolarda zaten mevcut olan verilerin claim olarak eklenmesi için kullanılır. Örneğin, ‘doğum tarihi’ bilgisi genellikle kullanıcı(user) nesnesinde tutulduğundan dolayı bu bilgiyi ekstradan AspNetUserClaims tablosunda tutmanın bir manası bulunmamaktadır. Dolayısıyla bu bilgiyi doğrudan elde edip claim olarak dinamik bir şekilde eklememiz daha doğru olacaktır.Peki nasıl uygulayacağız?
Dinamik bir şekilde claim ekleyebilmek için öncelikle “IClaimsTransformation” interfaceinden türeyen bir provider oluşturmamız gerekmektedir.public class UserClaimProvider : IClaimsTransformation { public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { ClaimsIdentity identity = principal.Identity as ClaimsIdentity; Claim claim = null; if (principal.HasClaim(x => x.Type == "username")) { claim = new Claim("username", identity.Name); identity.AddClaim(claim); } if (principal.HasClaim(x => x.Type == "logintime")) { claim = new Claim("logintime", DateTime.Now.ToString()); identity.AddClaim(claim); } return principal; } }
Yukarıdaki kod bloğunA göz atarsanız eğer “UserClaimProvider” ismini verdiğimiz provider sınıfımızda “IClaimsTransformation” interfaceinin implementi neticesinde uygulanan “TransformAsync” metodu içerisinde kullanıcıya claimler tanımlanmakta ve eklenmektedir. Yapılandırılan bu sağlayacının uygulamada çalışabilmesi ve her bir kullanıcı üzerinde bu claim değerlerini ekleyebilmesi için uygulamaya dahil edilmesi gerekmektedir.
Bunun için uygulamanın Startup.cs dosyası içerisindeki “ConfigureServices” metodunda aşağıdaki konfigürasyonun yapılması gerekmektedir.
public class Startup { public void ConfigureServices(IServiceCollection services) { . . . services.AddScoped<IClaimsTransformation, UserClaimProvider>(); . . . } . . . }
Oluşturduğumuz provider nesnesini “AddScoped” ile uygulamaya dahil ederek Dependency Injection ile kullanılabilir hale getirmiş olduk.
Şimdi bu noktaya kadar inşa edilen claim yapılanmasını kullanarak claim bazlı yetkilendirmeyi ele almadan önce veritabanından ekleme yönteminide inceleyelim.
- Veritabanından Ekleme
Dinamik olarak eklemeye nazaran veritabanındaki AspNetUserClaims tablosuna kullanıcıyla ilişkisel bir şekilde fiziksel claim ekleyerek icra edilen yöntemdir. Kullanıcıya ait claimler her seferinde tekrar üretilmek yerine veritabanına bir kereye mahsus eklenir ve her talepte mevcut olan elde edilip işlev gerçekleştirilir.Uygulama
Uygulamayı gerçekleştirebilmek için yapılması gereken ilgili actionda claim yapılanmasını inşa etmektir. Örneğin, bizim aşağıdaki gibi kullanıcının login işlemini yaptığı actionda gerçekleştirdiğimiz gibi…[HttpPost] public async Task<IActionResult> Login(UserLoginVM model) { AppUser user = await _userManager.FindByEmailAsync(model.Email); var userClaims = await _userManager.GetClaimsAsync(user); if (user != null) { Microsoft.AspNetCore.Identity.SignInResult result = await _signInManager.PasswordSignInAsync(user, model.Password, true, true); if (result.Succeeded) { Claim claim = new Claim("pozisyon", "admin"); if (!userClaims.Any(x => x.Type == "pozisyon")) await _userManager.AddClaimAsync(user, claim); return RedirectToAction("Index", "Home"); } } return View(model); }
Yukarıdaki kod bloğunu incelerseniz eğer kullanıcı giriş yaptıktan sonra ilgili kullanıcıya özel bir claim tanımlanmakta ve veritabanına eklenmektedir. Herhangi bir kullanıcının yapmış olduğu bir login işleminde veritabanına claim kaydının oluşturulduğunu aşağıdaki görselden daha rahat inceleyebilirsiniz;
Bu işlemlerden sonra sıra oluşturulan ve uygulamaya dahil edilen bu claim yapılanması ile yetkilendirme işlemini ele almaya geldi.
Claim Bazlı Yetkilendirme
Oluşturulan claimleri kullanarak yetkilendirme yapabilmek için Startup.cs dosyasındaki “ConfigureServices” metodu içerisinde aşağıdaki gibi politika oluşturmalı ve claimlerdeki değerleri belirlediğimiz static değerlerle zorunlu kılmalıyız;
public class Startup { public void ConfigureServices(IServiceCollection services) { . . . services.AddScoped<IClaimsTransformation, UserClaimProvider>(); services.AddAuthorization(x => x.AddPolicy("UserClaimNamePolicy", policy => policy.RequireClaim("username", "gncy"))); services.AddAuthorization(x => x.AddPolicy("UserClaimPositionPolicy", policy => policy.RequireClaim("pozisyon", "admin"))); . . . } }
Yukarıdaki kod bloğunu incelerseniz eğer “UserClaimNamePolicy” ve “UserClaimPositionPolicy” olmak üzere iki adet politika oluşturulmuştur ve işlevsel olarak sırasıyla; ilki sistemdeki kullanıcının claimType değeri “username” olan claiminin “gncy” değerinde olmasını ve ikincisinin ise yine sistemdeki kullanıcının “pozisyon” claimType değerindeki claiminin “admin” olmasını şart koşmaktadır.
Bu politikaları kullanabilmek için Politika Bazlı Kimlik Doğrulama makalesinde ele alındığı gibi yetkilendirilecek action ya da controllerları ilgili politikalarla işaretlememiz yeterlidir.
public class HomeController : Controller { public IActionResult Index() { return View(); } [Authorize(Policy = "UserClaimNamePolicy")] public IActionResult Page1() { return View(); } [Authorize(Policy = "UserClaimPositionPolicy")] public IActionResult Page2() { return View(); } }
Yukarıdaki kod bloğunda görüldüğü üzere “Page1” ve “Page2” sayfaları oluşturulan politikalarla işaretlenmiştir ve böylece claim bazlı yetkilendirme gerçekleştirilmiştir. Aşağıdaki ekran görüntüsünde de olduğu gibi sistemdeki kullanıcı rollerin dışında belirtilen politikalar eşliğinde zorunlu kılınan claim değerleri ile başarılı bir şekilde yetkilendirilmekte ve daha geniş ve detaylı bir yönetim sergilenmektedir.
Nihai olarak kullanıcı yetkilendirme senaryolarında rol bazlı yetkilendirmenin dışında claim bazlı yetkilendirmeyi incelemiş ve daha teraffuatlı bir yapılanmanın nasıl inşa edilebileceğini detaylarıyla irdelemiş bulunmaktayız.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek projeyi indirmek için buraya tıklayınız.
Merhaba , sanırım bu uygulamayı daha önceki yorumlarım için oluşturdunuz fakat ben hala dinamik olarak yönetemiyorum diye düşünüyorum rol yönetimini sizce de öyle değil mi ? Startup içerisine yine claimler statik olarak tanımlanmış olarak görüyorum. Benim yapmak istediğim aslında herşeyin kullanıcı tarafından tanımlanması sadece ben ilk adım olarak yetkileri statik olarak tanımlayacağım.Örneğin ; “UserCreate”,”UserEdit” vb. bunlar kullanıcılara roller aracılığıyla atanacak ve uygulamada herhangi bir publish olmadan kontrol edeceğim.
Merhaba,
İşte bu işlem için politika yapılanmasını kullanmanızı öneririm. Şöyle bir algoritmada seyredebilirsiniz;
Tüm sayfalara static bir rol tanımlayıp, ardından bu rollerle kullanıcı tarafından tanımlanmış alt rolleri ve kullanıcıları ilişkilendiren bir cross table oluşturarak tüm süreci bir politika aracılığıyla kontrol edilmesini sağlamanız sorununuzu çözecektir. Söz gelimi politika içerisinde mevcut kullanıcının ilgili rol ile ilişkili opsiyonel tanımlanmış alt rolü var mı yok mu incelemeniz ve ona göre yetki vermeniz süreci istediğiniz gibi tam yönetilebilir ve dinamik hale getirecektir.
Sevgiler.
Hocam merhaba
Şimdi internette nereye baksam net.core ile ilgili login işleminde identity kullanılıyor. Normal kendi kullanıcı adı ve şifremizle login olma işleminden bahsediyorum. Login oluyoruz ama bu seferde kontroller üstüne
[Authorize(Roles = “Admin”)] veya [Authorize] attribute kullanamıyoruz. Bunu kullanmak için aşağıdaki şekilde login olmamız gerekiyor.
signInManager.PasswordSignInAsync(model.email, model.password, true, true);
burası result dönerse ne ala dönmezse birde bunun için uğraşıyoruz. Policy kullanmadan ben kendi login işlemim için yukarıdaki attribute leri kullanmak için ne yapmam gerekiyor,
signInManager.PasswordSignInAsync bu tam olarak ne yapıyor bende manuel yaptırayım mesala aşağıdaki gibi claim ekledim ama yinede olmadı yani.
Ama yinede User.Identity.IsAuthenticated her zaman false oluyor, hiçbirşekilde User.Name bunlara erişemiyorum. Nerede yanlış yapıyorum
Neden böyle oluyor bilmiyorum ama 2 hafta araştırdıktan sonra bir yere yazınca cevabı buluyorum 🙂
Aşağıdaki gibi yapınca oldu.
önce service tarafı
Bu işlemlerden sonra oldu 🙂
Yazmayı unuttum Logout işlemide aşağıdaki gibi olmalı
Ya bu yetki olayı iyice kafamı bulandırdı. Örnekle yapmak istediğim A kullanıcısı User rolünde olsun ve xx deposunun sorumlusu olsun B kullanıcısı yine User rolünde olsun buda xy deposunun sorumlusu olsun C kullanıcısı ise yi e User rolünde xyz deposunun sorumlusu olsun. D kullanıcısı User rolünde xx ve xy depolarının şefi olsun. E kullanıcısı ise Admin olsun şimdi her kullanıcı kendi depoları ile ilgili işlemleri yapacak ama e kullanıcısı ise bütün kullanıcıları yaptığı işi yapabilecek. Farkındayım örnekle dire biraz karmaşık gibi duruyor. Böyle bir yapı oluşturmak mümkün mü mümkünse nasıl yapılabilir.
Merhaba,
Rol bazlı yetkilendirme yapmanız gerekiyor. İlgili makale için göz atınız : https://www.gencayyildiz.com/blog/asp-net-core-identity-rolemanager-sinifi-ile-rol-yonetimi-xiv/
https://www.gencayyildiz.com/blog/asp-net-core-identity-rolemanager-sinifi-ile-rol-yonetimi-xiv/ ilgili makaleyi defalarca kez okudum burada giriş yapanin rolünü alıyoruz admin kullanıcı vs Benim takıldığım nokta ise alt rol mü oluyor yoksa yetki mi tam emin değilim. Kullanıcıya tanımlanmış olan depoya giriş yapacak ve kendi deposundaki işlemleri görebilecek sadece, admin ise bütün depoları takip edebilecek. Yani kısaca veritabanıni nasıl kurgulayacagim öncelikle orayla ilgili sorun yaşıyorum. Depo ilgileri tablosu tutup depo bgileri id sini kullanıcının clampinda mı tutmalıyım ne yapmalıyım.
Yapmak istediğim en basit haliyle söyle anlatayım.
Ali kullanıcısı Akyol deposunun sorumlusu olsun ve rolü kullanıcı rolünde giriş yaptığı anda Akyol deposu üzerinde işlem yapabilecek örneğin Akyol deposunun stoğu stok ekleyebilsin stok girişi yapabilsin stok çıkışı yapabilsin stok durumunu sorgulayabilsin.
Veritabanini tasarlarken Stokkarti tablosunda stokid key stokgirislerinde ise stokgirisid key stokid ilişkilendirme için. Peki depo bilgisini Stokkarti tablosunda mi tutmalıyım. Veritabanı tasarım konusunda daha çok yeniyim o yüzden programlama kısmında verilere nasıl daha kolay yoldan ulaşırım hiyerarşik düzeni nasıl sağlarım tablolarda tam bilmiyorum. Bahsetmiş olduğum veritabanı deseni örneği verebilirseniz en azından bir fikir çok memnun olurum. Teşekkürler.
app Configure böyle olmalı sırasıyla yapmazsanız hata veriyor.
teşekkürler @borahan cevanın için.
Teşekkürler, [Authorize] bunu yaptıktan sonra işe yaradı.
Hocam merhaba,
Bir claim’imiz database tarafında diğeri ise cookie’de tutuluyor fakat Policy eklerken requireClaim’de istenilen değerlerimizi belirtirken sisteme bu değeri nereden bakacağı konusunda bir bilgi vermiyoruz.
Sistem hem database tarafına hem de client tarafındaki cookie’ye mi bakıyor otomatik olarak? Yoksa database tarafındaki claim bilgilerini de zaten en başta cookie’nin içine yazıp oradan mı bakıyor? Burasını tam olarak anlayamadım.
İyi çalışmalar.
Merhaba,
Kullanıcıya tanımlı claim ister database’den gelsin isterse de dinamik gelsin farketmeksizin oluşturulacak cookie’ye herhangi bir direktife gerek duymaksızın direkt eklenir 🙂
Sevgiler.
Teşekkürler hocam.
İyi çalışmalar.
Hocam Merhaba,
Web api’deki bir metoda web servisten PostAsync ile ulaşmak istiyorum ancak metod yetki ile sınırlı olduğu için statuscode 403 hatası alıyorum. Token ile role de gönderiyorum ancak bu rolleri authorize’nin tanıması için başka şeyler de eklemeli miyim?
Service
Controller
Merhaba,
Elde ettiğiniz token içerisinde rollere karşılık gelen claim’leri barındırması gerekmektedir. Aksi taktirde, token geçerlide olsa dahi istek yapılan endpoint claim tabanlı yahut politika bazlı yetkilendirmeyle korunuyorsa istek geçersiz olacaktır.
Bunu en iyi şu şekilde anlayabiliriz. Siz bana elde ettiğiniz token değerini paylaşınız. Bakalım jwt.io‘da beklenen claim’leri barındırıyor mu?
Normalde policy ile değil Roles ile deniyordum ancak olmayınca policy deneyince de aynı sonuçla karşılaştım. Authorize(Roles=”Member”) ile kullanabilmek için de başka bir şey eklemek gerekiyor mu?
JWT kullanıyorsan policy daha doğru. Sizden bir jwt paylaşmanızı istiyorum.
Ayriyetten ‘Startup.cs’ dosyasındaki ‘ActiveUser’ isimli policy kodlarını da paylaşınız.
JWT :
Startup.cs
Elinizdeki JWT’de
'Role' : 'Member'
claim’i olmalıdır. Lakin sizin gönderdiğiniz JWT’de ilgili claim bulunmamaktadır. İlgili user’a dair JWT üretiminde adı geçen claim’i jwt’ye eklemeniz gerekmektedir. Nasıl yapılacağını biliyor musunuz?token’ı oluşturuyorum rollerinin de tanımlı olmasını bekliyordum, başka bir şekilde mi tanımlama yapmalıyım?
Bakın siz burada claim’a role eklerken
ClaimTypes.Role
komutunu kullanmışsınız. İlgili komuthttp://schemas.microsoft.com/ws/2008/06/identity/claims/role
çıktısını dönecektir. Halbuki ‘ActiveUser’ policy’sin de ise ‘Role’ beklenmektedir. Ya burayı ‘Role’ yapacaksınız ya da ‘ActiveUser’ policy’sini aşağıdaki gibi düzenleyeceksiniz:Bu şekilde yapılan konfigürasyondan sonra yeni aldığınız token değerinde hata almadan başarılı istek göndermeniz muhtemeldir.
Sevgiler.
kıymetli hocam selamlar. User tablom var. claim ile login oluyorum bu kod ile herşey çalışıyor. sonradan user tabloma roleid sütun ekleyip yetkilendirme yapmak istedim. 0-1-2. Kulanıcı ilk kayıt olduğunda 0 yetkisisine sahip. ama ben admin tarafında kullanıcıya 1 yada 2 yetkisi verdiğimde. nasıl bir kontrol yapmam gerek. User.IsInRole(“1”) vs anlamadım. 🙁
.cs