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.

Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII

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.
Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII

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;
    Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII

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.
Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII

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.

Bunlar da hoşunuza gidebilir...

9 Cevaplar

  1. Murat Fatih ARKAN dedi ki:

    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.

    • Gençay dedi ki:

      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.

  2. borahan dedi ki:

    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.

    var user = this.Find(x =&gt; x.Email == model.email &amp;&amp; x.Password==model.Password);
            
     if (user == null)
    {
      return null;
    }
    
     var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    
                    identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
                    identity.AddClaim(new Claim(ClaimTypes.GivenName, user.Name));
                    identity.AddClaim(new Claim(ClaimTypes.Surname, user.Surname));
                    identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
    
                    foreach (var role in _userManager.GetRolesAsync(user).Result)
                    {
                        identity.AddClaim(new Claim(ClaimTypes.Role, role));
                    }
                    ClaimsPrincipal principal = new ClaimsPrincipal(identity);
                    AuthenticationProperties _authentication = new AuthenticationProperties
                    {
                        IsPersistent = true,
                        ExpiresUtc = DateTimeOffset.UtcNow
                    };
    
                    await Current.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, _authentication);
                    await _userManager.GetUserAsync(principal);
    
      Current.Session.SetObject("Users", user);
                    return user;
    

    Ama yinede User.Identity.IsAuthenticated her zaman false oluyor, hiçbirşekilde User.Name bunlara erişemiyorum. Nerede yanlış yapıyorum

    • borahan dedi ki:

      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ı

       services.AddAuthentication(options =&gt;
                      {
                          options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                          options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                          options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                      })
                      .AddCookie(config =&gt;
                      {
                          config.Cookie.Name = "login";
                          config.LoginPath = "/Account/Login";
                          config.ExpireTimeSpan = TimeSpan.FromMinutes(5);
                      });
      
      Sonra login olduktan sonraki taraf 
      var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
      
                      identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.UserName));
                      identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
                      identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
                      foreach (var role in _userManager.GetRolesAsync(user).Result)
                      {
                          identity.AddClaim(new Claim(ClaimTypes.Role, role));
                      }
                      ClaimsPrincipal principal = new ClaimsPrincipal(identity);
                      await _HttpContextAccessor.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties { IsPersistent = true });
      
      

      Bu işlemlerden sonra oldu 🙂

      • borahan dedi ki:

        Yazmayı unuttum Logout işlemide aşağıdaki gibi olmalı

          await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        
        • Ali dedi ki:

          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.

          • Gençay dedi ki:

            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/

          • Ali dedi ki:

            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.

  1. 30 Kasım 2019

    […] Asp.NET Core Identity – Claim Bazlı Kimlik Doğrulama – XVII […]

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*