تعیین سطح دسترسی روی داده ها در ASP.net Core با استفاده از Entity Framework Core

به نام خدا. در این آموزش قصد دارم نحوه ی تعیین سطح دسترسی روی داده های دیتابیس را در ASP.net Core با استفاده از Entity Framework Core به شما آموزش دهم. در این آموزش Entity Framework Core به اختصار EF نوشته خواهد شد.

کدهای این آموزش را می توانید از لینک زیر دانلود و استفاده کنید:

کدهای این آموزش در آدرس زیر قابل مشاهده و دانلود است
https://github.com/MohammadMoeinFazeli/ASP.netCore-DataAuthorization

این یک آموزش پیشرفته است و به بررسی تعیین دسترسی کاربران روی سطرهای جداول پایگاه داده می پردازد. برای یادگیری مباحث مقدماتی و انواع روش های Authorization و Authentication در ASP.net Core، لطفا آموزش زیر را مشاهده نمایید:

مقدمه و توضیح ساختار

ما در آموزش ویدئویی بالا به تعیین سطح دسترسی روی بخش های مختلف برنامه پرداختم. به این نوع سطح دسترسی Feature Authorization گفته می شود که عملا ویژگی ها و بخش های مختلف کدهای برنامه دارای سطح دسترسی هستند.

بخش مهم دیگری که در تعیین سطح دسترسی وجود دارد، تعیین دسترسی روی داده هاست که به آن Data Authorization گفته می شود. در Data Authorization رکوردهای جداول پایگاه داده و داده های ذخیره شده دارای مالکیت و سطح دسترسی هستند. بدین معنی که هرکسی فقط داده هایی را می تواند ببیند یا دستکاری کند که به دسترسی لازم برای مشاهده ی آن ها را دارد. این نوع سطح دسترسی به خصوص در پروژه های فروشگاهی، انبارداری و… که چندین نفر روی یک جدول کار می کنند ولی به داده های دیگران دسترسی ندارد بسیار مهم است.

چندین دسته بندی در تعیین سطح دسترسی روی داده ها یا Data Authorization وجود دارد که عبارتند از:

1- Personal Data: هر فرد مالک داده هایی است که خودش ایجاد می کند.

2- Multi Tenant Data: چندین نفر به یک بخش از داده ها دسترسی دارند.

3- Hierarchical Data: سلسه مراتب دسترسی روی داده ها وجود دارد. برای مثال مدیر می تواند به تمام داده ها دسترسی داشته باشد و مسئولان بخش ها فقط به داده های بخش خود و کارمندان فقط به داده های ایجاد شده توسط خودشان دسترسی دارند.

در این آموزش برای سادگی کار، حالت Personal Data را بررسی خواهیم کرد. سایر موارد عینا با همین راهکار و با کمی تغییرات در ساختار برنامه قابل انجام هستند.

برای بررسی این که یک فرد به داده ای دسترسی دارد باید آن فرد نشانه ای را به ما اعلام کند که از طریق آن بتواند ادعای خود مبنی بر دسترسی روی داده ها را ثابت کند. بدین منظور باید هنگام ایجاد داده در جدول، فیلد کلدی دسترسی را به آن اضافه کنیم. همچنین هنگام واکشی داده ها از دیتابیس نیز، کلید دسترسی داده ها را با کلید کاربر(که در این آموزش همان ID او است) مقایسه و در صورت مطابقت داده را به وی نمایش دهیم یا دستکاری مدنظرش را روی آن داده انجام دهیم.

افزودن شناسه ی کاربر به Claimهای او در هنگام ورود

در قدم اول باید شناسه ی کاربر را به عنوان یکی از Claimهای او در هنگام ورود وی به سیستم، تنظیم کنیم. چون این آموزش در ادامه ی آموزش ویدئویی کامل Authorization و Authentication در ASP.net Core همراه با JWT است ما از JWT برای بحث ورود و Authentication استفاده کردیم. کدهای زیر مربوط به تابع Authenticate یا همان Login و افزودن Claim مدنظر است:

public User Authenticate(string username, string password)
{
    var user = _dbContext.Users.SingleOrDefault(x => x.Username == username && x.Password == password);

    // return null if user not found
    if (user == null)
        return null;

    // authentication successful so generate jwt token
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
    var claims = new ClaimsIdentity();
    claims.AddClaims(new[]
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString())
    });
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = claims,
        Expires = DateTime.UtcNow.AddDays(7),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
            SecurityAlgorithms.HmacSha256Signature)
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    user.Token = tokenHandler.WriteToken(token);

    // remove password before returning
    user.Password = null;

    return user;
}

ایجاد فیلد مالکیت برای مدل های داده

در قدم اول باید یک property به عنوان مالکیت داده ها به مدل های داده (که توسط EF به جداول پایگاه داده نگاشت می شوند) اضافه کنید. برای این کار ابتدا interface زیر را ایجاد کنید:

public interface IOwnedBy
{
    //This holds the UserId of the person who created it
    string OwnedBy { get; }
    //This the method to set it
    void SetOwnedBy(string protectKey);
}

سپس آن را توسط کلاسی با نام OwnedByBase پیاده سازی نمایید. کلاس OwnedByBase مدل پایه ای ما برای مدل های است که نیاز به تعیین دسترسی روی داده ها دارند:

public class OwnedByBase : IOwnedBy
{
    public string OwnedBy { get; private set; }

    public void SetOwnedBy(string protectKey)
    {
        OwnedBy = protectKey;
    }
}

اکنون به عنوان مثال مدلی به نام Request مانند زیر ایجاد کنید که از مدل OwnedByBase ارثبری می کند و قرار است روی داده های آن سطح دسترسی تعیین کنیم:

public class Request: OwnedByBase
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int RequestId { get; set; }
    public string Message { get; set; }
}

افزودن خودکار فیلد مالکیت در هنگام ذخیره سازی داده ها

اکنون می خواهیم به صورت خودکار فیلد مالکیت داده را متناسب با این که چه کاربری داده را ایجاد می کند به جدول مربوطه اضافه کنیم. بدین منظور ابتدا یک افزونه ( Extention ) برای DbContext بنویسید که Id کاربر را در فیلد OwnedBy جدول مدنظر ما قرار دهد:

public static class MarkCreatedItem
{
    public static void MarkCreatedItemAsOwnedBy(
        this DbContext context, string userId)
    {
        foreach (var entityEntry in context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Added))
        {
            if (entityEntry.Entity is IOwnedBy entityToMark)
            {
                entityToMark.SetOwnedBy(userId);
            }
        }
    }
}

اکنون DbContext اتصال به پایگاه داده را به صورت زیر تغییر دهید. در این کد ما تابع SaveChange را بازنویسی کردیم تا در هنگام ذخیره سازی داده ها، با استفاده از افزونه ی فوق، فیلد مالکیت را به صورت خودکار در جدول ما تنظیم کند:

public class AppDbContext : DbContext
{
    private readonly string _userId;
    public AppDbContext(DbContextOptions<AppDbContext> options, IGetClaimsProvider userData)
        : base(options)
    {
        _userId = userData.UserId;
    }

    public DbSet<User> Users { get; set; }
    public DbSet<Request> Requests { get; set; }

    public override int SaveChanges
        (bool acceptAllChangesOnSuccess)
    {
        this.MarkCreatedItemAsOwnedBy(_userId);
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

....
....

بسیار عالی. اکنون یک بخش از کار مانده است و آن تعیین روشی برای دسترسی به Claimهای کاربر و بخصوص userId در کلاس AppDbContext است. همانطور که در کدبالا مشاهده می کنید از یک interface به نام IGetClaimsProvider که خودمان آن را ایجاد و پیاده سازی کرده ایم استفاده می کنیم…

دسترسی به Claimها در DbContext

ابتدا یک interface با نام IGetClaimsProvider یا هرنام دلخواه دیگری به صورت زیر ایجاد کنید:

public interface IGetClaimsProvider
{
    string UserId { get; }
}

خوب اکنون آن را توسط کدهای زیر پیاده سازی کنید:

public class GetClaimsFromUser : IGetClaimsProvider
{
    public GetClaimsFromUser(IHttpContextAccessor accessor)
    {
        UserId = accessor.HttpContext?
            .User.Claims.SingleOrDefault(x =>
                x.Type == ClaimTypes.NameIdentifier)?.Value;
    }

    public string UserId { get; private set; }
}

در اینجا ما از IHttpContextAccessor برای دسترسی به شی HttpContext و محتوای آن که شامل Claimها کاربر نیز هست استفاده کردیم. اکنون باید کدهای زیر را به تابع ConfigureService در کلاس Startup اضافه کنیم تا توسط قابلیت Dependency Injection نمونه های از کلاس های فوق در پروژه ی ما در هنگام لزوم ایجاد شود:

public void ConfigureServices(IServiceCollection services)
{
   ....
   ....

    // DI services
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddScoped<IGetClaimsProvider, GetClaimsFromUser>();

   ....

اعمال دسترسی داده ها با استفاده از QueryFilter

حالا می توانیم با بازنویسی تابع OnModelCreating کلاس AppDbContext که از DbContext ارثبری می کند، در هنگام فرخوانی داده ها توسط کاربران، فقط داده های مربوط به خودشان (که به آن ها دسترسی دارند) را به ایشان برگردانیم: بدین منظور تابع زیر را به کلاس AppDbContext اضافه کنید:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Request>()
        .HasQueryFilter(x => x.OwnedBy == _userId);

}

این تابع محدودیت مورد نظر ما را روی مدل Request اعمال می کند. برای سایر مدلهای مدنظرمان نیز می توانیم از همین روش استفاده کنیم.

تبریک می گویم. شما توانستید روی داده های خود با یک روش استاندارد و حرفه ای محدودیت دسترسی ایجاد کنید. این روش قابل تعمیم و استفاده در انواع سناریوهای مختلف است.

اگر سوالی داشتید لطفا آن را در بخش دیدگاه های همین پست مطرح بفرمایید. مجددا اعلام می کنم که کدهای این پروژه از این آدرس به صورت رایگان قابل دانلود و استفاده است.

موفق باشید. التماس دعا

2 دیدگاه برای “تعیین سطح دسترسی روی داده ها در ASP.net Core با استفاده از Entity Framework Core

  1. سلام تشکر از اموزش شما من مدتی درگیرjwtبودم .
    وقتی توی apicontoller ها من یک authorize تعریف می کنم خب چجوری این اتفاق بیفته؟ شما با swagger تست کردید. اما خب در برنامه واقعی چجوریه من برای وب اپلیکیشن میخوام صفحات api ها authorize بشه.
    باید از این اموزش استفاده کنم یا راه دیگه ای داره؟

    1. با سلام و عرض ادب
      این آموزش فقط برای تعیین سطح دسترسی روی داده ها است. لطفا آموزش ویدئویی کامل Authorization و Authentication در ASP.net Core همراه با JWT را مشاهده بفرمایید. در صورتی که پاسخ بنده مناسب نیست، لطفا سوال خود را به صورت کاملتر مطرح بفرمایید

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *