C# İle Öznitelik Tabanlı Dinamik Yetkilendirme Sistemi Nasıl Yapılır?

C# İle Öznitelik Tabanlı Dinamik Yetkilendirme Sistemi Nasıl Yapılır?
C# İle Öznitelik Tabanlı Dinamik Yetkilendirme Sistemi Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu rehberdeki uygulamaları gerçekleştirebilmek için bilgisayarınızda .NET 8 veya .NET 9 SDK'nın yüklü olması gerekmektedir. Uygulama geliştirme ortamı olarak Visual Studio 2022 veya JetBrains Rider kullanmanız önerilir. Projenizin ASP.NET Core Web API şablonu ile oluşturulduğunu varsayıyoruz.

  • .NET 8.0+ SDK
  • ASP.NET Core Web API Projesi
  • Temel C# nesne yönelimli programlama bilgisi
  • Dependency Injection (Bağımlılık Enjeksiyonu) kavramlarına aşinalık

Adım 1: Özel Yetkilendirme Özniteliği Oluşturma

İlk adımda, controller veya action metotları üzerinde kullanacağımız özel bir öznitelik sınıfı oluşturuyoruz. Bu sınıf, AuthorizeAttribute sınıfından türetilmeli ve gerekli izinleri parametre olarak almalıdır.

using Microsoft.AspNetCore.Authorization;

public class PermissionAttribute : AuthorizeAttribute
{
    public string Permission { get; }

    public PermissionAttribute(string permission)
    {
        Permission = permission;
        Policy = "PermissionPolicy"; // Yetkilendirme politikasının adı
    }
}

Burada Permission adında bir özellik tanımladık. Bu özellik, ilgili metodun hangi izne sahip kullanıcılara açık olduğunu belirtecek. Policy özelliği ise ASP.NET Core'un yetkilendirme sistemindeki merkezi bir kurala işaret eder.

Adım 2: Yetkilendirme Gereksinimi ve İşleyici Tanımlama

Özniteliğin çalışması için bir IAuthorizationRequirement ve bunu işleyecek bir AuthorizationHandler oluşturmalısınız. Bu, yetkilendirme mantığını merkezi bir noktada tutmanızı sağlar.

using Microsoft.AspNetCore.Authorization;

public class PermissionRequirement : IAuthorizationRequirement
{
    public string Permission { get; }
    public PermissionRequirement(string permission) => Permission = permission;
}

public class PermissionHandler : AuthorizationHandler
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var permissions = context.User.Claims.Where(c => c.Type == "Permission").Select(c => c.Value);
        
        if (permissions.Contains(requirement.Permission))
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

Bu kod bloğu, kullanıcının token'ı içerisinde "Permission" türünde bir claim (iddia) olup olmadığını kontrol eder. Eğer kullanıcının sahip olduğu izinler, talep edilen izinle eşleşirse erişime izin verilir.

Adım 3: Servis Kayıtlarını Yapılandırma

Oluşturduğumuz PermissionHandler sınıfını Dependency Injection konteynerine kaydetmemiz gerekir. Ayrıca, yetkilendirme politikasını Program.cs dosyasında tanımlamalıyız.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("PermissionPolicy", policy =>
        policy.Requirements.Add(new PermissionRequirement("DynamicPermission")));
});

builder.Services.AddSingleton();

Bu yapılandırma, uygulamanın yetkilendirme sistemine "PermissionPolicy" adında bir kural ekler. Artık özniteliklerde bu ismi kullandığınızda, sistem otomatik olarak PermissionHandler sınıfını tetikleyecektir.

Adım 4: Controller Üzerinde Kullanım

Artık sistemimiz hazır. Şimdi controller üzerinde [Permission("Admin")] gibi bir kullanım ile yetkilendirmeyi aktif hale getirebiliriz.

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    [HttpGet("list")]
    [Permission("User.Read")]
    public IActionResult GetUsers()
    {
        return Ok("Kullanıcı listesi erişimi başarılı.");
    }
}

Bu örnekte, GetUsers metoduna sadece "User.Read" izni olan kullanıcılar erişebilir. Bu yöntem, kodun okunabilirliğini artırır ve yetkilendirme mantığını action metotlarının dışına taşır.

Karşılaştırma: Rol Tabanlı vs Öznitelik Tabanlı Yetkilendirme

Özellik Rol Tabanlı (RBAC) Öznitelik Tabanlı (ABAC/Dynamic)
Esneklik Düşük Yüksek
Yönetim Basit Gelişmiş
Kod Tekrarı Fazla Az

Güvenlik ve İpuçları

Kritik Uyarı: Yetkilendirme mantığınızda asla kullanıcıdan gelen veriye (QueryString veya Body) doğrudan güvenmeyin. İzin kontrollerini her zaman sunucu tarafında, doğrulanmış JWT (JSON Web Token) veya Session üzerinden gelen Claims verileriyle yapın. SQL Injection riskine karşı, veritabanı sorgularında her zaman parametreli sorgular veya Entity Framework Core kullanın.

Sistemi test ederken, kullanıcının yetkisi olmadığında 403 Forbidden hatası alıp almadığınızı kontrol edin. Eğer 401 Unauthorized alıyorsanız, kimlik doğrulama (Authentication) katmanınızda bir sorun olabilir.

Sıkça Sorulan Sorular

Öznitelik tabanlı yetkilendirme performansı etkiler mi?

Handler yapısı oldukça hafiftir, ancak her istekte veritabanına giderek izin kontrolü yapıyorsanız, mutlaka bir MemoryCache kullanarak izinleri önbelleğe almalısınız.

Birden fazla izin tanımlayabilir miyim?

Evet, özniteliğinizi genişleterek bir dizi (array) parametre almasını sağlayabilir ve handler içinde Any() metodu ile kontrol edebilirsiniz.

JWT kullanmadan bu sistemi kurabilir miyim?

Evet, ancak bu durumda kullanıcı bilgilerini HttpContext.User nesnesine manuel olarak enjekte eden özel bir AuthenticationHandler yazmanız gerekir.

Neden Policy tabanlı yaklaşım kullanmalıyım?

Policy'ler, yetkilendirme kurallarını merkezi bir şekilde yönetmenize olanak tanır. İzin adlarını değiştirmek istediğinizde tüm controller'ları tek tek gezmek yerine sadece policy tanımını güncellemeniz yeterlidir.

Hata ayıklama (Debug) yaparken nelere dikkat etmeliyim?

PermissionHandler sınıfına bir breakpoint koyarak context.User.Claims koleksiyonunu inceleyin. Beklediğiniz izin claim'inin orada olup olmadığını doğrulamak sorunun kaynağını hızla bulmanızı sağlar.

İleri Seviye: Veritabanı Destekli Dinamik İzin Sağlayıcı

Statik özniteliklerin kod içerisinde sabitlenmesi, küçük projeler için yeterli olsa da, kurumsal uygulamalarda izinlerin veritabanından yönetilmesi bir gerekliliktir. Bu yaklaşımda, IAuthorizationPolicyProvider arayüzünü özelleştirerek, çalışma zamanında (runtime) veritabanındaki izinleri politikalara dönüştürebilirsiniz.

Aşağıdaki örnek, veritabanından dinamik olarak çekilen izinlerin PolicyProvider üzerinden nasıl işleneceğini göstermektedir:

public class DynamicPolicyProvider : IAuthorizationPolicyProvider
{
    public DefaultAuthorizationPolicyProvider BackupPolicyProvider { get; }

    public DynamicPolicyProvider(IOptions options)
    {
        BackupPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    public async Task GetPolicyAsync(string policyName)
    {
        // Veritabanı veya Cache kontrolü
        if (policyName.StartsWith("Permission_"))
        {
            var policy = new AuthorizationPolicyBuilder();
            policy.AddRequirements(new PermissionRequirement(policyName));
            return policy.Build();
        }

        return await BackupPolicyProvider.GetPolicyAsync(policyName);
    }

    public Task GetDefaultPolicyAsync() => BackupPolicyProvider.GetDefaultPolicyAsync();
    public Task GetFallbackPolicyAsync() => BackupPolicyProvider.GetFallbackPolicyAsync();
}

Yetkilendirme Süreçlerinde Birim Testi (Unit Testing)

Öznitelik tabanlı yetkilendirme sisteminizin doğru çalışıp çalışmadığını doğrulamak için AuthorizationService'i mock'layarak testler yazmalısınız. Bu, özellikle karmaşık izin senaryolarında beklenmedik erişim hatalarını önlemek için kritiktir.

Aşağıdaki test örneği, bir kullanıcının belirli bir yetkiye sahip olup olmadığını IAuthorizationService üzerinden nasıl doğrulayacağınızı gösterir:

[Fact]
public async Task User_With_Correct_Permission_Should_Authorize()
{
    // Arrange
    var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim("Permission", "Edit_Record") }));
    var authService = new Mock();
    
    authService.Setup(x => x.AuthorizeAsync(user, null, "Edit_Record"))
               .ReturnsAsync(AuthorizationResult.Success());

    // Act
    var result = await authService.Object.AuthorizeAsync(user, null, "Edit_Record");

    // Assert
    Assert.True(result.Succeeded);
}

Test Stratejileri ve İpuçları

  • Negatif Testler: Yetkisi olmayan bir kullanıcının Forbid veya Challenge sonucu döndüğünü mutlaka doğrulayın.
  • Sınır Durumları: Kullanıcının hiç claim'i olmadığı veya geçersiz bir politika ismi girildiği durumları test edin.
  • Entegrasyon Testleri: WebApplicationFactory kullanarak, gerçek HTTP istekleri ile yetkilendirme katmanının tetiklendiğinden emin olun.

Bu test yaklaşımları, sisteminizin ölçeklenebilirliğini artırırken, yeni izinler eklediğinizde mevcut yapının bozulmadığından emin olmanızı sağlar. Özellikle CI/CD süreçlerinde bu testlerin otomatik olarak çalıştırılması, güvenlik açıklarını minimize edecektir.

Sonuç

C# ile öznitelik tabanlı dinamik yetkilendirme sistemi kurmak, ASP.NET Core'un sunduğu güçlü altyapı sayesinde oldukça sürdürülebilir bir süreçtir. Bu rehberde öğrendiğiniz yöntemleri kullanarak, uygulamanızda merkezi, esnek ve güvenli bir erişim denetimi mekanizması oluşturabilirsiniz. Bir sonraki adım olarak, bu izinleri veritabanından dinamik olarak çeken ve bir cache mekanizması ile destekleyen bir "Permission Provider" sınıfı geliştirerek sisteminizi daha profesyonel bir seviyeye taşıyabilirsiniz.

Yasal Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında kullanmadan önce tüm güvenlik katmanlarını, özellikle veri doğrulama ve yetkilendirme mantığını, projenizin özel ihtiyaçlarına göre profesyonel bir güvenlik denetiminden geçirmeniz önerilir.

Bu yazıya tepkinizi paylaşın:
Deniz Arslan

Yedi yıllık dijital içerik deneyimiyle karmaşık süreçleri herkesin anlayabileceği basit adımlara dönüştürüyorum. Pratik yaşam hackleri ve ev yönetimi konularında uzmanım.

Yorumlar (0)

Yorum Yaz