ASP.NET Core Identity به همراه دو قابلیت جدید است که پیاده سازی سطوح دسترسی پویا را با سهولت بیشتری میسر میکند:
الف) Policies
ب) Role Claims
سیاستهای دسترسی یا Policies در ASP.NET Core Identity
ASP.NET Core Identity هنوز هم از مفهوم Roles پشتیبانی میکند. برای مثال میتوان مشخص کرد که اکشن متدی و یا تمام اکشن متدهای یک کنترلر تنها توسط کاربران دارای نقش Admin قابل دسترسی باشند. اما نقشها نیز در این سیستم جدید تنها نوعی از سیاستهای دسترسی هستند.
برای مثال در اینجا دسترسی به امکانات مدیریت نقشهای سیستم، به نقش ثابت و از پیش تعیین شدهی Admin منحصر شدهاست و تمام کاربرانی که این نقش به آنها انتساب داده شود، امکان استفادهی از این قابلیتها را خواهند یافت.
اما نقشهای ثابت، بسیار محدود و غیر قابل انعطاف هستند. برای رفع این مشکل مفهوم جدیدی را به نام Policy اضافه کردهاند.
سیاستهای دسترسی بر اساس Requirements و یا نیازهای سیستم تعیین میشوند و تعیین نقشها، تنها یکی از قابلیتهای آنها هستند.
برای مثال اگر بخواهیم تک نقش Admin را به صورت یک سیاست دسترسی جدید تعریف کنیم، روش کار به صورت ذیل خواهد بود:
در تنظیمات متد AddAuthorization، یک سیاست دسترسی جدید تعریف شدهاست که جهت برآورده شدن نیازمندیهای آن، کاربر سیستم باید دارای نقش Admin باشد که نمونهای از نحوهی استفادهی از آنرا با ذکر [Authorize(Policy="RequireAdministratorRole")] ملاحظه کردید.
و یا بجای اینکه چند نقش مجاز به دسترسی منبعی را با کاما از هم جدا کنیم:
میتوان یک سیاست دسترسی جدید را به نحو ذیل تعریف کرد که شامل تمام نقشهای مورد نیاز باشد و سپس بجای ذکر Roles، از نام این Policy جدید استفاده کرد:
به این صورت
سیاستهای دسترسی تنها به نقشها محدود نیستند:
برای مثال میتوان دسترسی به یک منبع را بر اساس User Claims یک کاربر به نحوی که ملاحظه میکنید، محدود کرد:
سیاستهای دسترسی پویا در ASP.NET Core Identity
مهمترین مزیت کار با سیاستهای دسترسی، امکان سفارشی سازی و تهیهی نمونههای پویای آنها هستند؛ موردی که با نقشهای ثابت سیستم قابل پیاده سازی نبوده و در نگارشهای قبلی، جهت پویا سازی آن، یکی از روشهای بسیار متداول، تهیهی فیلتر Authorize سفارشی سازی شده بود. اما در اینجا دیگر نیازی نیست تا فیلتر Authorize را سفارشی سازی کنیم. با پیاده سازی یک AuthorizationHandler جدید و معرفی آن به سیستم، پردازش سیاستهای دسترسی پویای به منابع، فعال میشود.
پیاده سازی سیاستهای پویای دسترسی شامل مراحل ذیل است:
1- تعریف یک نیازمندی دسترسی جدید
ابتدا باید یک نیازمندی دسترسی جدید را با پیاده سازی اینترفیس IAuthorizationRequirement ارائه دهیم. این نیازمندی مانند روشی که در پروژهی DNT Identity بکار گرفته شدهاست، خالی است و صرفا به عنوان نشانهای جهت یافت AuthorizationHandler استفاده کنندهی از آن استفاده میشود. در اینجا در صورت نیاز میتوان یک سری خاصیت اضافه را تعریف کرد تا آنها را به صورت پارامترهایی ثابت به AuthorizationHandler ارسال کند.
2- پیاده سازی یک AuthorizationHandler استفاده کنندهی از نیازمندی دسترسی تعریف شده
پس از اینکه نیازمندی DynamicPermissionRequirement را تعریف کردیم، در ادامه باید یک AuthorizationHandler استفاده کنندهی از آن را تعریف کنیم:
کار با ارث بری از AuthorizationHandler شروع شده و آرگومان جنریک آن، همان نیازمندی است که پیشتر تعریف کردیم. از این آرگومان جنریک جهت یافتن خودکار AuthorizationHandler متناظر با آن، توسط ASP.NET Core Identity استفاده میشود. بنابراین در اینجا DynamicPermissionRequirement تهیه شده صرفا کارکرد علامتگذاری را دارد.
در کلاس تهیه شده باید متد HandleRequirementAsync آنرا بازنویسی کرد و اگر در این بین، منطق سفارشی ما context.Succeed را فراخوانی کند، به معنای برآورده شدن سیاست دسترسی بوده و کاربر جاری میتواند به منبع درخواستی، بلافاصله دسترسی یابد و اگر context.Fail فراخوانی شود، در همینجا دسترسی کاربر قطع شده و HTTP status code مساوی 401 (عدم دسترسی) را دریافت میکند.
منطق سفارشی پیاده سازی شده نیز به این صورت است:
نام ناحیه، کنترلر و اکشن متد درخواستی کاربر از مسیریابی جاری استخراج میشوند. سپس توسط سرویس سفارشی ISecurityTrimmingService تهیه شده، بررسی میکنیم که آیا کاربر جاری به این سه مؤلفه دسترسی دارد یا خیر؟
3- معرفی سیاست دسترسی پویای تهیه شده به سیستم
معرفی سیاست کاری پویا و سفارشی تهیه شده، شامل دو مرحلهی زیر است:
ابتدا باید DynamicPermissionsAuthorizationHandler تهیه شده را به سیستم تزریق وابستگیها معرفی کنیم.
سپس یک Policy جدید را با نام دلخواه DynamicPermission تعریف کرده و نیازمندی علامتگذار خود را به عنوان یک policy.Requirements جدید، اضافه میکنیم. همانطور که ملاحظه میکنید یک وهلهی جدید از DynamicPermissionRequirement در اینجا ثبت شدهاست. همین وهله به متد HandleRequirementAsync نیز ارسال میشود. بنابراین اگر نیاز به ارسال پارامترهای بیشتری به این متد وجود داشت، میتوان خواص مرتبطی را به کلاس DynamicPermissionRequirement نیز اضافه کرد.
همانطور که مشخص است، در اینجا یک نیازمندی را میتوان ثبت کرد و نه Handler آنرا. این Handler از سیستم تزریق وابستگیها، بر اساس آرگومان جنریک AuthorizationHandler پیاده سازی شده، به صورت خودکار یافت شده و اجرا میشود (بنابراین اگر Handler شما اجرا نشد، مطمئن شوید که حتما آنرا به سیستم تزریق وابستگیها معرفی کردهاید).
پس از آن هر کنترلر یا اکشن متدی که از این سیاست دسترسی پویای تهیه شده استفاده کند:
به صورت خودکار توسط DynamicPermissionsAuthorizationHandler مدیریت میشود.
سرویس ISecurityTrimmingService چگونه کار میکند؟
کدهای کامل ISecurityTrimmingService را در کلاس SecurityTrimmingService میتوانید مشاهده کنید.
پیشنیاز درک عملکرد آن، آشنایی با دو قابلیت زیر هستند:
الف) «روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامهی ASP.NET Core»
دقیقا از همین سرویس توسعه داده شدهی در مطلب فوق، در اینجا نیز استفاده شدهاست؛ با یک تفاوت تکمیلی:
از متد GetAllSecuredControllerActionsWithPolicy جهت یافتن تمام اکشن متدهایی که مزین به ویژگی Authorize هستند و دارای Policy مساوی DynamicPermission میباشند، در کنترلر DynamicRoleClaimsManagerController برای لیست کردن آنها استفاده میشود. اگر این اکشن متد مزین به ویژگی DisplayName نیز بود (مانند مثال فوق و یا کنترلر نمونه DynamicPermissionsSampleController)، از مقدار آن برای نمایش نام این اکشن متد استفاده خواهد شد.
بنابراین همینقدر که تعریف ذیل یافت شود، این اکشن متد نیز در صفحهی مدیریت سطوح دسترسی پویا لیست خواهد شد.
به این ترتیب میتوان به یک نقش، تعدادی اکشن متد را نسبت داد و سطوح دسترسی به آنها را پویا کرد. اما ذخیره سازی آنها چگونه است و چگونه میتوان به اطلاعات نهایی ذخیره شده دسترسی پیدا کرد؟
مفهوم جدید Role Claims در ASP.NET Core Identity
تا اینجا موفق شدیم تمام اکشن متدهای دارای سیاست دسترسی سفارشی سازی شدهی خود را لیست کنیم، تا بتوان آنها را به صورت دلخواهی انتخاب کرد و سطوح دسترسی به آنها را به صورت پویا تغییر داد. اما این اکشن متدهای انتخاب شده را در کجا و به چه صورتی ذخیره کنیم؟
برای ذخیره سازی این اطلاعات نیازی نیست تا جدول جدیدی را به سیستم اضافه کنیم. جدول جدید AppRoleClaims به همین منظور تدارک دیده شدهاست.
وقتی کاربری عضو یک نقش است، به صورت خودکار Role Claims آن نقش را نیز به ارث میبرد. هدف از نقشها، گروه بندی کاربران است. توسط Role Claims میتوان مشخص کرد این نقشها چه کارهایی را میتوانند انجام دهند. اگر از قسمت قبل بخاطر داشته باشید، سرویس توکار UserClaimsPrincipalFactory دارای مرحلهی 5 ذیل است:
«5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه میشوند. در ASP.NET Identity Core نقشها نیز میتوانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).»
به این معنا که با لاگین شخص به سیستم، تمام اطلاعات مرتبط به او که در جدول AppRoleClaims وجود دارند، به کوکی او به صورت خودکار اضافه خواهند شد و دسترسی به آنها فوق العاده سریع است.
در کنترلر DynamicRoleClaimsManagerController، یک Role Claim Type جدید به نام DynamicPermissionClaimType اضافه شدهاست و سپس ID اکشن متدهای انتخابی را به نقش جاری، تحت Claim Type عنوان شده، اضافه میکند (تصویر فوق). این ID به صورت area:controller:action طراحی شدهاست. به همین جهت است که در DynamicPermissionsAuthorizationHandler همین سه جزء از سیستم مسیریابی استخراج و در سرویس SecurityTrimmingService مورد بررسی قرار میگیرد:
در اینجا user همان کاربرجاری سیستم است. HasClaim جزو متدهای استاندارد آن است و Type انتخابی، همان نوع سفارشی مدنظر ما است. currentClaimValue دقیقا همان ID اکشن متد جاری است که توسط کنار هم قرار دادن area:controller:action تشکیل شدهاست.
متد HasClaim هیچگونه رفت و برگشتی را به بانک اطلاعاتی ندارد و اطلاعات خود را از کوکی شخص دریافت میکند. متد user.IsInRole نیز به همین نحو عمل میکند.
Tag Helper جدید SecurityTrimming
اکنون که سرویس ISecurityTrimmingService را پیاده سازی کردهایم، از آن میتوان جهت توسعهی SecurityTrimmingTagHelper نیز استفاده کرد:
عملکرد آن نیز بسیار ساده است. اگر کاربر، به area:controller:action جاری دسترسی داشت، این Tag Helper کاری را انجام نمیدهد. اگر خیر، متد SuppressOutput را فراخوانی میکند. این متد سبب خواهد شد، هر آنچه که داخل تگهای این TagHelper قرار گرفته، در صفحه رندر نشوند و از خروجی آن حذف شوند. به این ترتیب، کاربر به اطلاعاتی که به آن دسترسی ندارد (مانند لینک به مدخلی خاص را) مشاهده نخواهد کرد. به این مفهوم security trimming میگویند.
نمونهای از کاربرد آنرا در ReportsMenu.cshtml_ میتوانید مشاهده کنید:
در اینجا اگر کاربر جاری به کنترلر DynamicPermissionsTest و اکشن متد Products آن دسترسی پویا نداشته باشد، محتوای قرارگرفتهی داخل تگ security-trimming را مشاهده نخواهد کرد.
برای آزمایش آن یک کاربر جدید را به سیستم DNT Identity اضافه کنید. سپس آنرا در گروه نقشی مشخص قرار دهید (منوی مدیریتی،گزینهی مدیریت نقشهای سیستم). سپس به این گروه دسترسی به تعدادی از آیتمهای پویا را بدهید (گزینهی مشاهده و تغییر لیست دسترسیهای پویا). سپس با این اکانت جدید به سیستم وارد شده و بررسی کنید که چه تعدادی از آیتمهای منوی «گزارشات نمونه» را میتوانید مشاهده کنید (تامین شدهی توسط ReportsMenu.cshtml_).
مدیریت اندازهی حجم کوکیهای ASP.NET Core Identity
همانطور که ملاحظه کردید، جهت بالابردن سرعت دسترسی به اطلاعات User Claims و Role Claims، تمام اطلاعات مرتبط با آنها، به کوکی کاربر وارد شدهی به سیستم، اضافه میشوند. همین مساله در یک سیستم بزرگ با تعداد صفحات بالا، سبب خواهد شد تا حجم کوکی کاربر از 5 کیلوبایت بیشتر شده و توسط مرورگرها مورد قبول واقع نشوند و عملا سیستم از کار خواهد افتاد.
برای مدیریت یک چنین مسالهای، امکان ذخیره سازی کوکیهای شخص در داخل بانک اطلاعاتی نیز پیش بینی شدهاست. زیر ساخت آنرا در مطلب «تنظیمات کش توزیع شدهی مبتنی بر SQL Server در ASP.NET Core» پیشتر در این سایت مطالعه کردید و در پروژهی DNT Identity بکارگرفته شدهاست.
اگر به کلاس IdentityServicesRegistry مراجعه کنید، یک چنین تنظیمی در آن قابل مشاهده است:
در ASP.NET Identity Core، امکان تدارک SessionStore سفارشی برای کوکیها نیز وجود دارد. این SessionStore باید با پیاده سازی اینترفیس ITicketStore تامین شود. دو نمونه پیاده سازی ITicketStore را در لایه سرویسهای پروژه میتوانید مشاهده کنید:
الف) DistributedCacheTicketStore
ب) MemoryCacheTicketStore
اولی از همان زیرساخت «تنظیمات کش توزیع شدهی مبتنی بر SQL Server در ASP.NET Core» استفاده میکند و دومی از IMemoryCache توکار ASP.NET Core برای پیاده سازی مکان ذخیره سازی محتوای کوکیهای سیستم، بهره خواهد برد.
باید دقت داشت که اگر حالت دوم را انتخاب کنید، با شروع مجدد برنامه، تمام اطلاعات کوکیهای کاربران نیز حذف خواهند شد. بنابراین استفادهی از حالت ذخیره سازی آنها در بانک اطلاعاتی منطقیتر است.
نحوهی تنظیم سرویس ITicketStore را نیز در متد setTicketStore میتوانید مشاهده کنید و در آن، در صورت انتخاب حالت بانک اطلاعاتی، ابتدا تنظیمات کش توزیع شده، صورت گرفته و سپس کلاس DistributedCacheTicketStore به عنوان تامین کنندهی ITicketStore به سیستم تزریق وابستگیها معرفی میشود.
همین اندازه برای انتقال محتوای کوکیهای کاربران به سرور کافی است و از این پس تنها اطلاعاتی که به سمت کلاینت ارسال میشود، ID رمزنگاری شدهی این کوکی است، جهت بازیابی آن از بانک اطلاعاتی و استفادهی خودکار از آن در برنامه.
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.
الف) Policies
ب) Role Claims
سیاستهای دسترسی یا Policies در ASP.NET Core Identity
ASP.NET Core Identity هنوز هم از مفهوم Roles پشتیبانی میکند. برای مثال میتوان مشخص کرد که اکشن متدی و یا تمام اکشن متدهای یک کنترلر تنها توسط کاربران دارای نقش Admin قابل دسترسی باشند. اما نقشها نیز در این سیستم جدید تنها نوعی از سیاستهای دسترسی هستند.
[Authorize(Roles = ConstantRoles.Admin)] public class RolesManagerController : Controller
اما نقشهای ثابت، بسیار محدود و غیر قابل انعطاف هستند. برای رفع این مشکل مفهوم جدیدی را به نام Policy اضافه کردهاند.
[Authorize(Policy="RequireAdministratorRole")] public IActionResult Get() { /* .. */ }
برای مثال اگر بخواهیم تک نقش Admin را به صورت یک سیاست دسترسی جدید تعریف کنیم، روش کار به صورت ذیل خواهد بود:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Admin")); }); }
و یا بجای اینکه چند نقش مجاز به دسترسی منبعی را با کاما از هم جدا کنیم:
[Authorize(Roles = "Administrator, PowerUser, BackupAdministrator")]
options.AddPolicy("ElevatedRights", policy => policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
[Authorize(Policy = "ElevatedRights")] public IActionResult Shutdown() { return View(); }
سیاستهای دسترسی تنها به نقشها محدود نیستند:
services.AddAuthorization(options => { options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); });
[Authorize(Policy = "EmployeeOnly")] public IActionResult VacationBalance() { return View(); }
سیاستهای دسترسی پویا در ASP.NET Core Identity
مهمترین مزیت کار با سیاستهای دسترسی، امکان سفارشی سازی و تهیهی نمونههای پویای آنها هستند؛ موردی که با نقشهای ثابت سیستم قابل پیاده سازی نبوده و در نگارشهای قبلی، جهت پویا سازی آن، یکی از روشهای بسیار متداول، تهیهی فیلتر Authorize سفارشی سازی شده بود. اما در اینجا دیگر نیازی نیست تا فیلتر Authorize را سفارشی سازی کنیم. با پیاده سازی یک AuthorizationHandler جدید و معرفی آن به سیستم، پردازش سیاستهای دسترسی پویای به منابع، فعال میشود.
پیاده سازی سیاستهای پویای دسترسی شامل مراحل ذیل است:
1- تعریف یک نیازمندی دسترسی جدید
public class DynamicPermissionRequirement : IAuthorizationRequirement { }
2- پیاده سازی یک AuthorizationHandler استفاده کنندهی از نیازمندی دسترسی تعریف شده
پس از اینکه نیازمندی DynamicPermissionRequirement را تعریف کردیم، در ادامه باید یک AuthorizationHandler استفاده کنندهی از آن را تعریف کنیم:
public class DynamicPermissionsAuthorizationHandler : AuthorizationHandler<DynamicPermissionRequirement> { private readonly ISecurityTrimmingService _securityTrimmingService; public DynamicPermissionsAuthorizationHandler(ISecurityTrimmingService securityTrimmingService) { _securityTrimmingService = securityTrimmingService; _securityTrimmingService.CheckArgumentIsNull(nameof(_securityTrimmingService)); } protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, DynamicPermissionRequirement requirement) { var mvcContext = context.Resource as AuthorizationFilterContext; if (mvcContext == null) { return Task.CompletedTask; } var actionDescriptor = mvcContext.ActionDescriptor; var area = actionDescriptor.RouteValues["area"]; var controller = actionDescriptor.RouteValues["controller"]; var action = actionDescriptor.RouteValues["action"]; if(_securityTrimmingService.CanCurrentUserAccess(area, controller, action)) { context.Succeed(requirement); } else { context.Fail(); } return Task.CompletedTask; } }
در کلاس تهیه شده باید متد HandleRequirementAsync آنرا بازنویسی کرد و اگر در این بین، منطق سفارشی ما context.Succeed را فراخوانی کند، به معنای برآورده شدن سیاست دسترسی بوده و کاربر جاری میتواند به منبع درخواستی، بلافاصله دسترسی یابد و اگر context.Fail فراخوانی شود، در همینجا دسترسی کاربر قطع شده و HTTP status code مساوی 401 (عدم دسترسی) را دریافت میکند.
منطق سفارشی پیاده سازی شده نیز به این صورت است:
نام ناحیه، کنترلر و اکشن متد درخواستی کاربر از مسیریابی جاری استخراج میشوند. سپس توسط سرویس سفارشی ISecurityTrimmingService تهیه شده، بررسی میکنیم که آیا کاربر جاری به این سه مؤلفه دسترسی دارد یا خیر؟
3- معرفی سیاست دسترسی پویای تهیه شده به سیستم
معرفی سیاست کاری پویا و سفارشی تهیه شده، شامل دو مرحلهی زیر است:
private static void addDynamicPermissionsPolicy(this IServiceCollection services) { services.AddScoped<IAuthorizationHandler, DynamicPermissionsAuthorizationHandler>(); services.AddAuthorization(opts => { opts.AddPolicy( name: ConstantPolicies.DynamicPermission, configurePolicy: policy => { policy.RequireAuthenticatedUser(); policy.Requirements.Add(new DynamicPermissionRequirement()); }); }); }
سپس یک Policy جدید را با نام دلخواه DynamicPermission تعریف کرده و نیازمندی علامتگذار خود را به عنوان یک policy.Requirements جدید، اضافه میکنیم. همانطور که ملاحظه میکنید یک وهلهی جدید از DynamicPermissionRequirement در اینجا ثبت شدهاست. همین وهله به متد HandleRequirementAsync نیز ارسال میشود. بنابراین اگر نیاز به ارسال پارامترهای بیشتری به این متد وجود داشت، میتوان خواص مرتبطی را به کلاس DynamicPermissionRequirement نیز اضافه کرد.
همانطور که مشخص است، در اینجا یک نیازمندی را میتوان ثبت کرد و نه Handler آنرا. این Handler از سیستم تزریق وابستگیها، بر اساس آرگومان جنریک AuthorizationHandler پیاده سازی شده، به صورت خودکار یافت شده و اجرا میشود (بنابراین اگر Handler شما اجرا نشد، مطمئن شوید که حتما آنرا به سیستم تزریق وابستگیها معرفی کردهاید).
پس از آن هر کنترلر یا اکشن متدی که از این سیاست دسترسی پویای تهیه شده استفاده کند:
[Authorize(Policy = ConstantPolicies.DynamicPermission)] [DisplayName("کنترلر نمونه با سطح دسترسی پویا")] public class DynamicPermissionsSampleController : Controller
سرویس ISecurityTrimmingService چگونه کار میکند؟
کدهای کامل ISecurityTrimmingService را در کلاس SecurityTrimmingService میتوانید مشاهده کنید.
پیشنیاز درک عملکرد آن، آشنایی با دو قابلیت زیر هستند:
الف) «روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامهی ASP.NET Core»
دقیقا از همین سرویس توسعه داده شدهی در مطلب فوق، در اینجا نیز استفاده شدهاست؛ با یک تفاوت تکمیلی:
public interface IMvcActionsDiscoveryService { ICollection<MvcControllerViewModel> MvcControllers { get; } ICollection<MvcControllerViewModel> GetAllSecuredControllerActionsWithPolicy(string policyName); }
بنابراین همینقدر که تعریف ذیل یافت شود، این اکشن متد نیز در صفحهی مدیریت سطوح دسترسی پویا لیست خواهد شد.
[Authorize(Policy = ConstantPolicies.DynamicPermission)]
ابتدا به مدیریت نقشهای ثابت سیستم میرسیم. سپس به هر نقش میتوان یک Claim جدید را با مقدار area:controller:action انتساب داد.
به این ترتیب میتوان به یک نقش، تعدادی اکشن متد را نسبت داد و سطوح دسترسی به آنها را پویا کرد. اما ذخیره سازی آنها چگونه است و چگونه میتوان به اطلاعات نهایی ذخیره شده دسترسی پیدا کرد؟
مفهوم جدید Role Claims در ASP.NET Core Identity
تا اینجا موفق شدیم تمام اکشن متدهای دارای سیاست دسترسی سفارشی سازی شدهی خود را لیست کنیم، تا بتوان آنها را به صورت دلخواهی انتخاب کرد و سطوح دسترسی به آنها را به صورت پویا تغییر داد. اما این اکشن متدهای انتخاب شده را در کجا و به چه صورتی ذخیره کنیم؟
برای ذخیره سازی این اطلاعات نیازی نیست تا جدول جدیدی را به سیستم اضافه کنیم. جدول جدید AppRoleClaims به همین منظور تدارک دیده شدهاست.
وقتی کاربری عضو یک نقش است، به صورت خودکار Role Claims آن نقش را نیز به ارث میبرد. هدف از نقشها، گروه بندی کاربران است. توسط Role Claims میتوان مشخص کرد این نقشها چه کارهایی را میتوانند انجام دهند. اگر از قسمت قبل بخاطر داشته باشید، سرویس توکار UserClaimsPrincipalFactory دارای مرحلهی 5 ذیل است:
«5) اگر یک نقش منتسب به کاربر دارای Role Claim باشد، این موارد نیز واکشی شده و به کوکی او به عنوان یک Claim جدید اضافه میشوند. در ASP.NET Identity Core نقشها نیز میتوانند Claim داشته باشند (امکان پیاده سازی سطوح دسترسی پویا).»
به این معنا که با لاگین شخص به سیستم، تمام اطلاعات مرتبط به او که در جدول AppRoleClaims وجود دارند، به کوکی او به صورت خودکار اضافه خواهند شد و دسترسی به آنها فوق العاده سریع است.
در کنترلر DynamicRoleClaimsManagerController، یک Role Claim Type جدید به نام DynamicPermissionClaimType اضافه شدهاست و سپس ID اکشن متدهای انتخابی را به نقش جاری، تحت Claim Type عنوان شده، اضافه میکند (تصویر فوق). این ID به صورت area:controller:action طراحی شدهاست. به همین جهت است که در DynamicPermissionsAuthorizationHandler همین سه جزء از سیستم مسیریابی استخراج و در سرویس SecurityTrimmingService مورد بررسی قرار میگیرد:
return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType && claim.Value == currentClaimValue);
متد HasClaim هیچگونه رفت و برگشتی را به بانک اطلاعاتی ندارد و اطلاعات خود را از کوکی شخص دریافت میکند. متد user.IsInRole نیز به همین نحو عمل میکند.
Tag Helper جدید SecurityTrimming
اکنون که سرویس ISecurityTrimmingService را پیاده سازی کردهایم، از آن میتوان جهت توسعهی SecurityTrimmingTagHelper نیز استفاده کرد:
public override void Process(TagHelperContext context, TagHelperOutput output) { context.CheckArgumentIsNull(nameof(context)); output.CheckArgumentIsNull(nameof(output)); // don't render the <security-trimming> tag. output.TagName = null; if(_securityTrimmingService.CanCurrentUserAccess(Area, Controller, Action)) { // fine, do nothing. return; } // else, suppress the output and generate nothing. output.SuppressOutput(); }
نمونهای از کاربرد آنرا در ReportsMenu.cshtml_ میتوانید مشاهده کنید:
<security-trimming asp-area="" asp-controller="DynamicPermissionsTest" asp-action="Products"> <li> <a asp-controller="DynamicPermissionsTest" asp-action="Products" asp-area=""> <span class="left5 fa fa-user" aria-hidden="true"></span> گزارش از لیست محصولات </a> </li> </security-trimming>
برای آزمایش آن یک کاربر جدید را به سیستم DNT Identity اضافه کنید. سپس آنرا در گروه نقشی مشخص قرار دهید (منوی مدیریتی،گزینهی مدیریت نقشهای سیستم). سپس به این گروه دسترسی به تعدادی از آیتمهای پویا را بدهید (گزینهی مشاهده و تغییر لیست دسترسیهای پویا). سپس با این اکانت جدید به سیستم وارد شده و بررسی کنید که چه تعدادی از آیتمهای منوی «گزارشات نمونه» را میتوانید مشاهده کنید (تامین شدهی توسط ReportsMenu.cshtml_).
مدیریت اندازهی حجم کوکیهای ASP.NET Core Identity
همانطور که ملاحظه کردید، جهت بالابردن سرعت دسترسی به اطلاعات User Claims و Role Claims، تمام اطلاعات مرتبط با آنها، به کوکی کاربر وارد شدهی به سیستم، اضافه میشوند. همین مساله در یک سیستم بزرگ با تعداد صفحات بالا، سبب خواهد شد تا حجم کوکی کاربر از 5 کیلوبایت بیشتر شده و توسط مرورگرها مورد قبول واقع نشوند و عملا سیستم از کار خواهد افتاد.
برای مدیریت یک چنین مسالهای، امکان ذخیره سازی کوکیهای شخص در داخل بانک اطلاعاتی نیز پیش بینی شدهاست. زیر ساخت آنرا در مطلب «تنظیمات کش توزیع شدهی مبتنی بر SQL Server در ASP.NET Core» پیشتر در این سایت مطالعه کردید و در پروژهی DNT Identity بکارگرفته شدهاست.
اگر به کلاس IdentityServicesRegistry مراجعه کنید، یک چنین تنظیمی در آن قابل مشاهده است:
var ticketStore = provider.GetService<ITicketStore>(); identityOptionsCookies.ApplicationCookie.SessionStore = ticketStore; // To manage large identity cookies
الف) DistributedCacheTicketStore
ب) MemoryCacheTicketStore
اولی از همان زیرساخت «تنظیمات کش توزیع شدهی مبتنی بر SQL Server در ASP.NET Core» استفاده میکند و دومی از IMemoryCache توکار ASP.NET Core برای پیاده سازی مکان ذخیره سازی محتوای کوکیهای سیستم، بهره خواهد برد.
باید دقت داشت که اگر حالت دوم را انتخاب کنید، با شروع مجدد برنامه، تمام اطلاعات کوکیهای کاربران نیز حذف خواهند شد. بنابراین استفادهی از حالت ذخیره سازی آنها در بانک اطلاعاتی منطقیتر است.
نحوهی تنظیم سرویس ITicketStore را نیز در متد setTicketStore میتوانید مشاهده کنید و در آن، در صورت انتخاب حالت بانک اطلاعاتی، ابتدا تنظیمات کش توزیع شده، صورت گرفته و سپس کلاس DistributedCacheTicketStore به عنوان تامین کنندهی ITicketStore به سیستم تزریق وابستگیها معرفی میشود.
همین اندازه برای انتقال محتوای کوکیهای کاربران به سرور کافی است و از این پس تنها اطلاعاتی که به سمت کلاینت ارسال میشود، ID رمزنگاری شدهی این کوکی است، جهت بازیابی آن از بانک اطلاعاتی و استفادهی خودکار از آن در برنامه.
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.