مطالب
Cookie - قسمت سوم

Cookie - قسمت اول: مقدمه، تاریخچه، معرفی، و شرح کامل

Cookie - قسمت دوم: کوکی در جاوا اسکریپت

نکته مهم: خواندن قسمت‌های قبلی این سری (مخصوصا قسمت اول) برای درک بهتر مطالب پیشنهاد می‌شود.



کوکی در ASP.NET - بخش اول

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

در ادامه این سری مطالب به نحوه برخورد ASP.NET با کوکی‌ها و چگونگی کار کردن با کوکی در سمت سرور آشنا خواهیم شد. در بخش اول این قسمت مباحث ابتدایی و اولیه برای کار با کوکی‌ها در ASP.NET ارائه می‌شود. در بخش دوم مباحث پیشرفته‌تر همچون SubCookieها در ASP.NET و نیز سایر نکات ریز کار با کوکی‌ها در ASP.NET بحث خواهد شد.

.


Response و Request در ASP.NET

در قسمت اول این سری به مفاهیم Http Response و Http Request اشاره کوتاهی شده بود. به‌صورت خلاصه، درخواستی که از سمت یک کلاینت به یک وب سرور ارسال می‌شود Request و پاسخی که وب سرور به آن درخواست می‌دهد Response نامیده می‌شود.

در ASP.NET، کلیه اطلاعات مرتبط با درخواست رسیده از سمت یک کلاینت در نمونه‌ای منحصر به فرد از کلاس HttpRequest نگه‌داری می‌شود. محل اصلی نگه‌داری این نمونه در پراپرتی Request از نمونه جاری کلاس System.Web.HttpContext (قابل دسترسی ازطریق HttpContext.Current) است. البته کلاس Page هم یک پراپرتی با نام Request دارد که دقیقا از همین پراپرتی کلاس HttpContext استفاده می‌کند.

هم‌چنین کلیه اطلاعات مرتبط با پاسخ ارسالی وب سرور به سمت کلاینت مربوطه در نمونه‌ای از کلاس HttpResponse ذخیره می‌شود. محل اصلی نگه‌داری این نمونه نیز در پراپرتی Response از نمونه جاری کلاس HttpContext است. همانند Request، کلاس Page یک پراپرتی با نام Response برای نگه‌داری این نمونه دارد که این هم دقیقا از پراپرتی متناظر در کلاس HttpContext استفاده می‌کند.



کوکی‌ها در Response و Request

هر دو کلاس HttpResponse و HttpRequest یک پراپرتی با عنوان Cookies (^ و ^) دارند که مخصوص نگهداری کوکی‌های مربوطه هستند. این پراپرتی از نوع System.Web.HttpCookieCollection است که یک کالکشن مخصوص برای ذخیره کوکی‌هاست.

- این پراپرتی (Cookies) در کلاس HttpRequest محل نگه‌داری کوکی‌های ارسالی توسط مرورگر در درخواست متناظر آن است. کوکی‌هایی که مرورگر با توجه به شرایط جاری و تنظیمات کوکی‌ها اجازه ارسال به سمت سرور را به آن‌ها داده و در درخواست ارسالی ضمیمه کرده است (با استفاده از هدر :Cookie که در قسمت اول شرح داده شد) و ASP.NET پس از پردازش و Parse داده‌ها، درون این پراپرتی اضافه کرده است.

- این پراپرتی (Cookies) در کلاس HttpResponse محل ذخیره کوکی‌های ارسالی از وب سرور به سمت مرورگر کلاینت در پاسخ به درخواست متناظر است. کوکی‌های درون این پراپرتی پس از بررسی و استخراج داده‌های موردنیاز توسط ASP.NET در هدر پاسخ ارسالی ضمیمه خواهند شد (با استفاده از هدر :Set-Cookie که در قسمت اول توضیح داده شد).

.


ایجاد و به‌روزرسانی کوکی در ASP.NET

برای ایجاد یک کوکی و ارسال آن به سمت کلاینت همان‌طور که در بالا نیز اشاره شد، باید از پراپرتی Response.Cookies از کلاس HttpContext استفاده کرد. برای ایجاد یک کوکی روش‌های مختلفی وجود دارد.

  • در روش اول با استفاده از ویژگی مخصوص ایندکسر کلاس HttpCookieCollection عملیات تولید کوکی انجام می‌شود. در این روش، ابتدا بررسی می‌شود که کوکی موردنظر در لیست کوکی‌های جاری وجود دارد یا خیر. درصورتی‌که با این نام قبلا یک کوکی ثبت شده باشد، مقدار کوکی موجود بروزرسانی خواهد شد. اما اگر این نام وجود نداشته باشد یک کوکی جدید با این نام به لیست افزوده شده و مقدار آن ثبت می‌شود. مثال:
HttpContext.Current.Response.Cookies["myCookie"].Value = "myCookieValue";
  • روش بعدی استفاده از متد Add در کلاس HttpCookieCollection است. در این روش ابتدا یک نمونه از کلاس HttpCookie ایجاد شده و سپس این نمونه به لیست کوکی‌ها اضافه می‌شود. کد زیر چگونگی استفاده از این روش را نشان می‌دهد:
var myCookie = new HttpCookie("myCookie", "myCookieValue");
HttpContext.Current.Response.Cookies.Add(myCookie);
  • روش دیگر استفاده از متد Set کلاس HttpCookieCollection است. تفاوت این متد با متد Add در این است که متد Set ابتدا سعی می‌کند عملیات update انجام دهد. یعنی عملیات افزودن تنها وقتی‌که نام کوکی موردنظر در لیست کوکی‌ها یافته نشود انجام خواهد شد. برای مثال:
HttpContext.Current.Response.Cookies.Set(new HttpCookie("myCookie", "myCookieValue"));

نکته: باتوجه به توضیحات بالا، متد Set اجازه افزودن دو کوکی با یک نام را نمی‌دهد. برای اینکار باید از متد Add استفاده کرد. درباره این موضوع در قسمت بعدی بیشتر توضیح داده خواهد شد.

  • روش دیگری که برای ایجاد یکی کوکی می‌توان از آن استفاده کرد، بکارگیری متد AppnedCookie از کلاس HttpResponse است. در این روش نیز ابتدا باید یک نمونه از کلاس HttpCookie تولید شود. این روش همانند استفاده از متد Add از کلاس HttpCookieCollection است. کد زیر مثالی از این روش را نشان می‌دهد:

HttpContext.Current.Response.AppendCookie(new HttpCookie("myCookie", "myCookieValue"));
  • روش بعدی استفاده از متد SetCookie از کلاس HttpResponse است. فرق این متد با متد AppendCookie در این است که در متد SetCookie ابتدا وجود یک کوکی با نام ارائه شده بررسی می‌شود و درصورت وجود، مقدار این کوکی بروزرسانی می‌شود. درصورتی‌که قبلا یک کوکی با این نام وجود نداشته باشد، یک کوکی جدید به لیست کوکی‌ها اضافه می‌شود. این روش همانند استفاده از متد Set از کلاس HttpCookieCollection است. نمونه‌ای از نحوه استفاده از این متد در زیر آورده شده است:
HttpContext.Current.Response.SetCookie(new HttpCookie("myCookie", "myCookieValue"));

نکته: تمامی فرایندهای نشان داده شده در بالا تنها موجب تغییر محتویات کالکشن کوکی‌ها درون HttpContext می‌شود و تا زمانی‌که توسط وب سرور با استفاده از دستور Set-Cookie به سمت مرورگر ارسال نشوند تغییری در کلاینت بوجود نخواهند آورد.

برای آشنایی بیشتر با این روند کد زیر را برای تعریف یک کوکی جدید درنظر بگیرید:

HttpContext.Current.Response.Cookies["myCookie"].Value = "myValue";
برای مشاهده هدر تولیدی توسط وب سرور می‌توان از نرم افزار محبوب Fiddler استفاده کرد (از اواخر سال 2012 که نویسنده این ابزار به Telerik پیوسته، توسعه آن بسیار فعال‌تر شده و نسخه‌های جدید با لوگوی جدید! ارائه شده است).
تصویر زیر مربوط به مثال بالاست:

همانطور که مشاهده می‌کنید دستور ایجاد یک کوکی با نام و مقدار وارده در هدر پاسخ تولیدی توسط وب سرور گنجانیده شده است.

نکته: در ASP.NET به صورت پیش فرض از مقدار "/" برای پراپرتی Path استفاده می‌شود.


خواص کوکی در ASP.NET

برای تعیین یا تغییر خواص یک کوکی در ASP.NET باید به نمونه HttpCookie مربوطه دست یافت. سپس با استفاده از پراپرتی‌های این کلاس می‌توان خواص موردنظر را تعیین کرد. برای مثال:

var myCookie = new HttpCookie(string.Empty);
myCookie.Name = "myCookie";
myCookie.Value = "myCookieValue";
myCookie.Domain = "dotnettip.info";
myCookie.Path = "/post";
myCookie.Expires = new DateTime(2015, 1, 1);
myCookie.Secure = true;
myCookie.HttpOnly = true;

نکته مهم: امکان تغییر خواص یک کوکی به صورت مستقیم در سمت سرور وجود ندارد. درواقع برای اعمال این تغییرات در سمت کلاینت باید به ازای هر کوکی موردنظر یک کوکی جدید با مقادیر جدید ایجاد و به کالکشن کوکی‌ها در Http Response مربوطه اضافه شود تا پس از قرار دادن دستور Set-Cookie متناظر در هدر پاسخ ارسالی به سمت کلاینت و اجرای آن توسط مرورگر، مقادیر خواص مورنظر در سمت کلاینت بروزرسانی شوند. دقت کنید که تمامی نکات مرتبط با هویت یک کوکی که در قسمت اول شرح داده شد در اینجا نیز کاملا صادق است.

روش دیگری نیز برای تعیین برخی خواص کوکی‌ها به صورت کلی در فایل وب کانفیگ وجود دارد. برای اینکار از تگ httpCookies در قسمت system.web استفاده می‌شود. برای مثال:

<httpCookies domain="www.example.com" httpOnlyCookies="true" requireSSL="true" />

این امکان از ASP.NET 2.0 به بعد اضافه شده است. با استفاده از این تگ، تنظیمات اعمال شده برای تمامی کوکی‌ها درنظر گرفته می‌شود. البته درصورتی‌که تنظیم موردنظر برای کوکی به صورت صریح آورده نشده باشد. برای نمونه به کد زیر دقت کنید:

var myCookie = new HttpCookie("myCookie", "myCookieValue");
myCookie.Domain = "test.com";
HttpContext.Current.Response.Cookies.Add(myCookie);
var myCookie2 = new HttpCookie("myCookie2", "myCookieValue2");
myCookie2.HttpOnly = false;
myCookie2.Secure = false;
HttpContext.Current.Response.Cookies.Add(myCookie2);

با استفاده از تنظیمات تگ httpCookies که در بالا نشان داده شده است، هدر پاسخ تولیدی توسط وب سرور به صورت زیر خواهد بود:

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

. 


حذف کوکی در ASP.NET

برای حذف یک کوکی در ASP.NET یک روش کلی وجود دارد که در قسمت‌های قبلی نیز شرح داده شده است، یعنی تغییر خاصیت Expires کوکی به تاریخی در گذشته. برای نمونه داریم:

var myCookie = new HttpCookie("myCookie", "myCookieValue");
myCookie.Expires = DateTime.Now.AddYears(-1);

نکته مهم: در کلاس HttpCookieCollection یک متد با نام Remove وجود دارد. از این متد برای حذف یک کوکی از لیست موجود در این کلاس استفاده می‌شود. دقت کنید که حذف یک کوکی از لیست کوکی‌ها با استفاده از این متد تاثیری بر موجودیت آن کوکی در سمت کلاینت نخواهد گذاشت و تنها روش موجود برای حذف یک کوکی در سمت کلاینت همان تنظیم مقدار خاصیت Expires است.


خواندن کوکی در ASP.NET

برای خواندن مقدار یک کوکی ارسالی از مرورگر کلاینت در ASP.NET، باتوجه به توضیحات ابتدای این مطلب، طبیعی است که باید از پراپرتی Request.Cookies در نمونه جاری از کلاس HttpContext استفاده کرد. برای این کار نیز چند روش وجود دارد.

  • روش اول استفاده از ایندکسر کلاس HttpCookieCollection است. برای اینکار نیاز به نام یا ایندکس کوکی موردنظر در لیست مربوطه داریم. برای مثال:
var myCookie = HttpContext.Current.Request.Cookies["myCookie"];
  • یا این نمونه با استفاده از ایندکسر عددی:
var myCookie = HttpContext.Current.Request.Cookies[0];
  • روش دیگری که برای خواند مقدار یک کوکی می‌توان بکار برد، استفاده از متد Get از کلاس HttpCookieCollection است. این متد همانند ایندکسر این کلاس نیاز به نام یا ایندکس کوکی موردنظر دارد. برای نمونه:
var myCookie = HttpContext.Current.Request.Cookies.Get("myCookie");
var myCookie = HttpContext.Current.Request.Cookies.Get(0);

.


بحث و نتیجه گیری

تا اینجا با مفاهیم اولیه درباره نحوه برخورد ASP.NET با کوکی‌ها آشنا شدیم. روش‌های مختلف ایجاد و یا به‌روزرسانی کوکی‌ها نشان داده شد. با تعیین انواع خواص کوکی‌ها آشنا شدیم. نحوه حذف یک کوکی در ASP.NET را دیدیم. روش‌های خواندن مقادیر کوکی‌ها را نیز مشاهده کردیم.

باز هم تاکید می‌کنم که تمامی تغییرات اعمالی در سمت سرور تا زمانی‌که به‌صورت دستورات Set-Cookie در هدر پاسخ وب سرور قرار نگیرند هیچ کاری در سمت کلاینت انجام نمی‌دهند.

در قسمت بعدی این سری مطالب به مباحث پیشرفته‌تری چون SubCookieها در ASP.NET و هویت منحصر به فرد کوکی‌ها در سمت سرور پرداخته می‌شود.



منابع

http://msdn.microsoft.com/en-us/library/ms178194(v=vs.100).aspx

http://msdn.microsoft.com/en-us/library/aa289495(v=vs.71).aspx

http://www.codeproject.com/Articles/31914/Beginner-s-Guide-To-ASP-NET-Cookies

http://www.codeproject.com/Articles/244904/Cookies-in-ASP-NET
مطالب
پیاده سازی سیاست‌های دسترسی پویای سمت سرور و کلاینت در برنامه‌های Blazor WASM
فرض کنید در حال توسعه‌ی یک برنامه‌ی Blazor WASM هاست شده هستید و می‌خواهید که نیازی نباشد تا به ازای هر صفحه‌ای که به برنامه اضافه می‌کنید، یکبار منوی آن‌را به روز رسانی کنید و نمایش منو به صورت خودکار توسط برنامه صورت گیرد. همچنین در این حالت نیاز است در قسمت مدیریتی برنامه، بتوان به صورت پویا، به ازای هر کاربری، مشخص کرد که به کدامیک از صفحات برنامه دسترسی دارد و یا خیر و به علاوه اگر به صفحاتی دسترسی ندارد، مشخصات این صفحه، در منوی پویا برنامه ظاهر نشود و همچنین با تایپ آدرس آن در نوار آدرس مرورگر نیز قابل دسترسی نباشد. امن سازی پویای سمت کلاینت، یک قسمت از پروژه‌است؛ قسمت دیگر چنین پروژه‌ای، لیست کردن اکشن متدهای API سمت سرور پروژه و انتساب دسترسی‌های پویایی به این اکشن متدها، به کاربران مختلف برنامه‌است.


دریافت کدهای کامل این پروژه

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


پیشنیازها

در پروژه‌ی فوق برای شروع به کار، از اطلاعات مطرح شده‌ی در سلسله مطالب زیر استفاده شده‌است:

- «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- پیاده سازی اعتبارسنجی کاربران در برنامه‌های Blazor WASM؛ قسمت‌های 31 تا 33 .
- «غنی سازی کامپایلر C# 9.0 با افزونه‌ها»
- «مدیریت مرکزی شماره نگارش‌های بسته‌های NuGet در پروژه‌های NET Core.»
- «کاهش تعداد بار تعریف using‌ها در C# 10.0 و NET 6.0.»
- «روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامه‌ی ASP.NET Core»


  نیاز به علامتگذاری صفحات امن شده‌ی سمت کلاینت، جهت نمایش خودکار آن‌ها 

صفحات امن سازی شده‌ی سمت کلاینت، با ویژگی Authorize مشخص می‌شوند. بنابراین قید آن الزامی است، تا صرفا جهت کاربران اعتبارسنجی شده، قابل دسترسی شوند. در اینجا می‌توان یک نمونه‌ی سفارشی سازی شده‌ی ویژگی Authorize را به نام ProtectedPageAttribute نیز مورد استفاده قرار داد. این ویژگی از AuthorizeAttribute ارث‌بری کرده و دقیقا مانند آن عمل می‌کند؛ اما این اضافات را نیز به همراه دارد:
- به همراه یک Policy از پیش تعیین شده به نام CustomPolicies.DynamicClientPermission است تا توسط قسمت‌های بررسی سطوح دسترسی پویا و همچنین منوساز برنامه، یافت شده و مورد استفاده قرار گیرد.
- به همراه خواص اضافه‌تری مانند GroupName و Title نیز هست. GroupName نام سرتیتر منوی dropdown نمایش داده شده‌ی در منوی اصلی برنامه‌است و Title همان عنوان صفحه که در این منو نمایش داده می‌شود. اگر صفحه‌ی محافظت شده‌ای به همراه GroupName نباشد، یعنی باید به صورت یک آیتم اصلی نمایش داده شود. همچنین در اینجا یک سری Order هم درنظر گرفته شده‌اند تا بتوان ترتیب نمایش صفحات را نیز به دلخواه تغییر داد.


نمونه‌ای از استفاده‌ی از ویژگی فوق را در مسیر src\Client\Pages\Feature1 می‌توانید مشاهده کنید که خلاصه‌ی آن به صورت زیر است:
 @attribute [ProtectedPage(GroupName = "Feature 1", Title = "Page 1", GlyphIcon = "bi bi-dot", GroupOrder = 1, ItemOrder = 1)]

ویژگی ProtectedPage را معادل یک ویژگی Authorize سفارشی، به همراه چند خاصیت بیشتر، جهت منوساز پویای برنامه درنظر بگیرید.


نیاز به لیست کردن صفحات علامتگذاری شده‌ی با ویژگی ProtectedPage

پس از اینکه صفحات مختلف برنامه را توسط ویژگی ProtectedPage علامتگذاری کردیم، اکنون نوبت به لیست کردن پویای آن‌ها است. اینکار توسط سرویس ProtectedPagesProvider صورت می‌گیرد. این سرویس با استفاده از Reflection، ابتدا تمام IComponentها یا همان کامپوننت‌های تعریف شده‌ی در برنامه را از اسمبلی جاری استخراج می‌کند. بنابراین اگر نیاز دارید که این جستجو در چندین اسمبلی صورت گیرد، فقط کافی است ابتدای این کدها را تغییر دهید. پس از یافت شدن IComponent ها، فقط آن‌هایی که دارای RouteAttribute هستند، پردازش می‌شوند؛ یعنی کامپوننت‌هایی که به همراه مسیریابی هستند. پس از آن بررسی می‌شود که آیا این کامپوننت دارای ProtectedPageAttribute هست یا خیر؟ اگر بله، این کامپوننت در لیست نهایی درج خواهد شد.


نیاز به یک منوساز پویا جهت نمایش خودکار صفحات امن سازی شده‌ی با ویژگی ProtectedPage

اکنون که لیست صفحات امن سازی شده‌ی توسط ویژگی ProtectedPage را در اختیار داریم، می‌توانیم آن‌ها را توسط کامپوننت سفارشی NavBarDynamicMenus به صورت خودکار نمایش دهیم. این کامپوننت لیست صفحات را توسط کامپوننت NavBarDropdownMenu نمایش می‌دهد.


تهیه‌ی جداول و سرویس‌های ثبت دسترسی‌های پویای سمت کلاینت


جداول و فیلدهای مورد استفاده‌ی در این پروژه را در تصویر فوق ملاحظه می‌کنید که در پوشه‌ی src\Server\Entities نیز قابل دسترسی هستند. در این برنامه نیاز به ذخیره سازی اطلاعات نقش‌های کاربران مانند نقش Admin، ذخیره سازی سطوح دسترسی پویای سمت کلاینت و همچنین سمت سرور است. بنابراین بجای اینکه به ازای هر کدام، یک جدول جداگانه را تعریف کنیم، می‌توان از همان طراحی ASP.NET Core Identity مایکروسافت با استفاده از جدول UserClaimها ایده گرفت. یعنی هر کدام از این موارد، یک Claim خواهند شد:


در اینجا نقش‌ها با Claim استانداردی به نام http://schemas.microsoft.com/ws/2008/06/identity/claims/role که توسط خود مایکروسافت نامگذاری شده و سیستم‌های اعتبارسنجی آن بر همین اساس کار می‌کنند، قابل مشاهده‌است. همچنین دو Claim سفارشی دیگر ::DynamicClientPermission:: برای ذخیره سازی اطلاعات صفحات محافظت شده‌ی سمت کلاینت و ::DynamicServerPermission::  جهت ذخیره سازی اطلاعات اکشن متدهای محافظت شده‌ی سمت سرور نیز تعریف شده‌اند. رابطه‌ای این اطلاعات با جدول کاربران، many-to-many است.


به این ترتیب است که مشخص می‌شود کدام کاربر، به چه claimهایی دسترسی دارد.

برای کار با این جداول، سه سرویس UsersService، UserClaimsService و UserTokensService پیش بینی شده‌اند. UserTokens اطلاعات توکن‌های صادر شده‌ی توسط برنامه را ذخیره می‌کند و توسط آن می‌توان logout سمت سرور را پیاده سازی کرد؛ از این جهت که JWTها متکی به خود هستند و تا زمانیکه منقضی نشوند، در سمت سرور پردازش خواهند شد، نیاز است بتوان به نحوی اگر کاربری غیرفعال شد، از آن ثانیه به بعد، توکن‌های او در سمت سرور پردازش نشوند که به این نکات در مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» پیشتر پرداخته شده‌است.
اطلاعات این سرویس‌ها توسط اکشن متدهای UsersAccountManagerController، در اختیار برنامه‌ی کلاینت قرار می‌گیرند.


نیاز به قسمت مدیریتی ثبت دسترسی‌های پویای سمت کلاینت و سرور

قبل از اینکه بتوان قسمت‌های مختلف کامپوننت NavBarDynamicMenus را توضیح داد، نیاز است ابتدا یک قسمت مدیریتی را جهت استفاده‌ی از لیست ProtectedPageها نیز تهیه کرد:


در این برنامه، کامپوننت src\Client\Pages\Identity\UsersManager.razor کار لیست کردن کاربران، که اطلاعات آن‌را از کنترلر UsersAccountManagerController دریافت می‌کند، انجام می‌دهد. در مقابل نام هر کاربر، دو دکمه‌ی ثبت اطلاعات پویای دسترسی‌های سمت کلاینت و سمت سرور وجود دارد. سمت کلاینت آن توسط کامپوننت UserClientSidePermissions.razor مدیریت می‌شود و سمت سرور آن توسط UserServerSidePermissions.razor.
کامپوننت UserClientSidePermissions.razor، همان لیست صفحات محافظت شده‌ی توسط ویژگی ProtectedPage را به صورت گروه بندی شده و به همراه یک سری chekmark، ارائه می‌دهد. اگر در اینجا صفحه‌ای انتخاب شد، اطلاعات آن به سمت سرور ارسال می‌شود تا توسط Claim ای به نام ::DynamicClientPermission:: به کاربر انتخابی انتساب داده شود.


شبیه به همین عملکرد در مورد دسترسی سمت سرور نیز برقرار است. UserServerSidePermissions.razor، لیست اکشن متدهای محافظت شده را از کنترلر DynamicPermissionsManagerController دریافت کرده و نمایش می‌دهد. این اطلاعات توسط سرویس ApiActionsDiscoveryService جمع آوری می‌شود. همچنین این اکشن متدهای ویژه نیز باید با ویژگی Authorize(Policy = CustomPolicies.DynamicServerPermission) مزین شده باشند که نمونه مثال آن‌ها را در مسیر src\Server\Controllers\Tests می‌توانید مشاهده کنید. اگر در سمت کلاینت و قسمت مدیریتی آن، اکشن متدی جهت کاربر خاصی انتخاب شد، اطلاعات آن ذیل Claimای به نام ::DynamicServerPermission::  به کاربر انتخابی انتساب داده می‌شود.



بازگشت اطلاعات پویای دسترسی‌های سمت کلاینت از API

تا اینجا کامپوننت‌های امن سازی شده‌ی سمت کلاینت و اکشن متدهای امن سازی شده‌ی سمت سرور را توسط صفحات مدیریتی برنامه، به کاربران مدنظر خود انتساب دادیم و توسط سرویس‌های سمت سرور، اطلاعات آن‌ها را در بانک اطلاعاتی ذخیره کردیم. اکنون نوبت به استفاده‌ی از claims تعریف شده و مرتبط با هر کاربر است. پس از یک لاگین موفقیت آمیز توسط UsersAccountManagerController، سه توکن به سمت کاربر ارسال می‌شوند:
- توکن دسترسی: اطلاعات اعتبارسنجی کاربر به همراه نام و نقش‌های او در این توکن وجود دارند.
- توکن به روز رسانی: هدف از آن، دریافت یک توکن دسترسی جدید، بدون نیاز به لاگین مجدد است. به این ترتیب کاربر مدام نیاز به لاگین مجدد نخواهد داشت و تا زمانیکه refresh token منقضی نشده‌است، برنامه می‌تواند از آن جهت دریافت یک access token جدید استفاده کند.
- توکن سطوح دسترسی پویای سمت کلاینت: در اینجا لیست ::DynamicClientPermission::ها به صورت یک توکن مجزا به سمت کاربر ارسال می‌شود. این اطلاعات به توکن دسترسی اضافه نشده‌اند تا بی‌جهت حجم آن اضافه نشود؛ از این جهت که نیازی نیست تا به ازای هر درخواست HTTP به سمت سرور، این لیست حجیم claims پویای سمت کلاینت نیز به سمت سرور ارسال شود. چون سمت سرور از claims دیگری به نام ::DynamicServerPermission:: استفاده می‌کند.


اگر دقت کنید، هم refresh-token و هم DynamicPermissions هر دو به صورت JWT ارسال شده‌اند. می‌شد هر دو را به صورت plain و ساده نیز ارسال کرد. اما مزیت refresh token ارسال شده‌ی به صورت JWT، انجام اعتبارسنجی خودکار سمت سرور اطلاعات آن است که دستکاری سمت کلاینت آن‌را مشکل می‌کند.
این سه توکن توسط سرویس BearerTokensStore، در برنامه‌ی سمت کلاینت ذخیره و بازیابی می‌شوند. توکن دسترسی یا همان access token، توسط ClientHttpInterceptorService به صورت خودکار به تمام درخواست‌های ارسالی توسط برنامه الصاق خواهد شد.


مدیریت خودکار اجرای Refresh Token در برنامه‌های Blazor WASM

دریافت refresh token از سمت سرور تنها قسمتی از مدیریت دریافت مجدد یک access token معتبر است. قسمت مهم آن شامل دو مرحله‌ی زیر است:
الف) اگر خطاهای سمت سرور 401 و یا 403 رخ دادند، ممکن است نیاز به refresh token باشد؛ چون احتمالا یا کاربر جاری به این منبع دسترسی ندارد و یا access token دریافتی که طول عمر آن کمتر از refresh token است، منقضی شده و دیگر قابل استفاده نیست.
ب) پیش از منقضی شدن access token، بهتر است با استفاده از refresh token، یک access token جدید را دریافت کرد تا حالت الف رخ ندهد.

- برای مدیریت حالت الف، یک Policy ویژه‌ی Polly طراحی شده‌است که آن‌را در کلاس ClientRefreshTokenRetryPolicy مشاهده می‌کنید. در این Policy ویژه، هرگاه خطاهای 401 و یا 403 رخ دهند، با استفاده از سرویس جدید IClientRefreshTokenService، کار به روز رسانی توکن انجام خواهد شد. این Policy در کلاس program برنامه ثبت شده‌است. مزیت کار با Policyهای Polly، عدم نیاز به try/catch نوشتن‌های تکراری، در هر جائیکه از سرویس‌های HttpClient استفاده می‌شود، می‌باشد.

- برای مدیریت حالت ب، حتما نیاز به یک تایمر سمت کلاینت است که چند ثانیه پیش از منقضی شدن access token دریافتی پس از لاگین، کار دریافت access token جدیدی را به کمک refresh token موجود، انجام دهد. پیاده سازی این تایمر را در کلاس ClientRefreshTokenTimer مشاهده می‌کنید که محل فراخوانی و راه اندازی آن یا پس از لاگین موفق در سمت کلاینت و یا با ریفرش صفحه (فشرده شدن دکمه‌ی F5) و در کلاس آغازین ClientAuthenticationStateProvider می‌باشد.



نیاز به پیاده سازی Security Trimming سمت کلاینت

از داخل DynamicPermissions دریافتی پس از لاگین، لیست claimهای دسترسی پویای سمت کلاینت کاربر لاگین شده استخراج می‌شود. بنابراین مرحله‌ی بعد، استخراج، پردازش و اعمال این سطوح دسترسی پویای دریافت شده‌ی از سرور است.
سرویس BearerTokensStore، کار ذخیره سازی توکن‌های دریافتی پس از لاگین را انجام می‌دهد و سپس با استفاده از سرویس DynamicClientPermissionsProvider، توکن سوم دریافت شده که مرتبط با لیست claims دسترسی کاربر جاری است را پردازش کرده و تبدیل به یک لیست قابل استفاده می‌کنیم تا توسط آن بتوان زمانیکه قرار است آیتم‌های منوها را به صورت پویا نمایش داد، مشخص کنیم که کاربر، به کدامیک دسترسی دارد و به کدامیک خیر. عدم نمایش قسمتی از صفحه که کاربر به آن دسترسی ندارد را security trimming گویند. برای نمونه کامپوننت ویژه‌ی SecurityTrim.razor، با استفاده از نقش‌ها و claims یک کاربر، می‌تواند تعیین کند که آیا قسمت محصور شده‌ی صفحه توسط آن قابل نمایش به کاربر است یا خیر. این کامپوننت از متدهای کمکی AuthenticationStateExtensions که کار با user claims دریافتی از طریق JWTها را ساده می‌کنند، استفاده می‌کند. یک نمونه از کاربرد کامپوننت SecurityTrim را در فایل src\Client\Shared\MainLayout.razor می‌توانید مشاهده کنید که توسط آن لینک Users Manager، فقط به کاربران دارای نقش Admin نمایش داده می‌شود.
نحوه‌ی مدیریت security trimming منوی پویای برنامه، اندکی متفاوت است. DynamicClientPermissionsProvider لیست claims متعلق به کاربر را بازگشت می‌دهد. این لیست پس از لاگین موفقیت آمیز دریافت شده‌است. سپس لیست کلی صفحاتی را که در ابتدای برنامه استخراج کردیم، در طی حلقه‌ای از سرویس ClientSecurityTrimmingService عبور می‌دهیم. یعنی مسیر صفحه و همچنین دسترسی‌های پویای کاربر، مشخص هستند. در این بین هر مسیری که در لیست claims پویای کاربر نبود، در لیست آیتم‌های منوی پویای برنامه، نمایش داده نمی‌شود.


نیاز به قطع دسترسی به مسیرهایی در سمت کلاینت که کاربر به صورت پویا به آن‌ها دسترسی ندارد

با استفاده از ClientSecurityTrimmingService، در حلقه‌ای که آیتم‌های منوی سایت را نمایش می‌دهد، موارد غیرمرتبط با کاربر جاری را حذف کردیم و نمایش ندادیم. اما این حذف، به این معنا نیست که اگر این آدرس‌ها را به صورت مستقیم در مرورگر وارد کند، به آن‌ها دسترسی نخواهد داشت. برای رفع این مشکل، نیاز به پیاده سازی یک سیاست دسترسی پویای سمت کلاینت است. روش ثبت این سیاست را در کلاس DynamicClientPermissionsPolicyExtensions مشاهده می‌کنید. کلید آن همان CustomPolicies.DynamicClientPermission که در حین تعریف ProtectedPageAttribute به عنوان مقدار Policy پیش‌فرض مقدار دهی شد. یعنی هرگاه ویژگی ProtectedPage به صفحه‌ای اعمال شد، از این سیاست دسترسی استفاده می‌کند که پردازشگر آن DynamicClientPermissionsAuthorizationHandler است. این هندلر نیز از ClientSecurityTrimmingService استفاده می‌کند. در هندلر context.User جاری مشخص است. این کاربر را به متد تعیین دسترسی مسیر جاری به سرویس ClientSecurityTrimming ارسال می‌کنیم تا مشخص شود که آیا به مسیر درخواستی دسترسی دارد یا خیر؟


نیاز به قطع دسترسی به منابعی در سمت سرور که کاربر به صورت پویا به آن‌ها دسترسی ندارد

شبیه به ClientSecurityTrimmingService سمت کلاینت را در سمت سرور نیز داریم؛ به نام ServerSecurityTrimmingService که کار آن، پردازش claimهایی از نوع ::DynamicServerPermission::  است که در صفحه‌ی مدیریتی مرتبطی در سمت کلاینت، به هر کاربر قابل انتساب است. هندلر سیاست دسترسی پویایی که از آن استفاده می‌کند نیز DynamicServerPermissionsAuthorizationHandler می‌باشد. این سیاست دسترسی پویا با کلید CustomPolicies.DynamicServerPermission در کلاس ConfigureServicesExtensions تعریف شده‌است. به همین جهت هر اکشن متدی که Policy آن با این کلید مقدار دهی شده باشد، از هندلر پویای فوق جهت تعیین دسترسی پویا عبور خواهد کرد. منطق پیاده سازی شده‌ی در اینجا، بسیار شبیه به مطلب «سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا» است؛ اما بدون استفاده‌ی از ASP.NET Core Identity.


روش اجرای برنامه

چون این برنامه از نوع Blazor WASM هاست شده‌است، نیاز است تا برنامه‌ی Server آن‌را در ابتدا اجرا کنید. با اجرای آن، بانک اطلاعاتی SQLite برنامه به صورت خودکار توسط EF-Core ساخته شده و مقدار دهی اولیه می‌شود. لیست کاربران پیش‌فرض آن‌را در اینجا می‌توانید مشاهده کنید. ابتدا با کاربر ادمین وارد شده و سطوح دسترسی سایر کاربران را تغییر دهید. سپس بجای آن‌ها وارد سیستم شده و تغییرات منوها و سطوح دسترسی پویا را بررسی کنید.
نظرات اشتراک‌ها
برنامه‌ی تیم WPF برای آینده
من هم امیدوارم سیلورلایت هم توسعه ای داشته باشد. بسیار بسیار کم نسبت به این موضوع امیدوار شدم.
مطالب
قابلیت های جدید VisualStudio.NET 2012 - قسمت سوم
اگر شما در زمینه طراحی وب سایت و برنامه‌های کاربردی تحت وب فعالیت دارید حتماً با ابزارهایی مانند Firebug آشنا هستید. معمولاً فرآیند بررسی مشکلات رابط کاربری و موضوعات مشابه آن بصورت زیر بوده است:
 
 

توجه داشته باشید که یک صفحه وب که در مرورگر به نمایش در می‌آید، برآیند فایل‌های جاوا اسکریپت، شیوه نامه ها، User control ها، صفحات ASPX و Master Page‌ها و ... است. افزون بر این آنچه در مرورگر نمایش داده می‌شود با چیزی که ما در محیط طراحی (Visual Studio.NET (Design View  می‌بینیم متفاوت است.
تمام مشکلات و سختی‌های بالا دست به دست هم دادند تا در نسخه جدید نرم افزار Visual Studio.NET شاهد ابزار جادویی با عنوان Page Inspector باشیم.
این ابزار بصورت Real-time امکان نگاشت (mapping) عناصر موجود در نتیجه نهایی برنامه وب را با سورس کد مهیا می‌کند. بدین معنا توسط Page Inspector با حرکت ماوس روی عناصر صفحه در مرورگر، Visual Studio.NET بخشی را که آن عنصر را تولید کرده است (User Control, Master Page, View, و ...) نمایش می‌دهد و شما می‌توانید بلافاصله پس از اعمال تغییرات جدید بر روی سورس کد، نتیجه را روی مرورگر ببنید. البته عکس این موضوع نیز صادق است و شما می‌توانید با حرکت در سورس کد، نتیجه بصری و عناصر HTML ی که در نتیجه تولید می‌شوند را مشاهده کنید. (عناصر متناظر به حالت Select در می‌آیند.)
 

 
از دیگر قابلیت‌های این ابزار نمایش CSS‌های متناظر هر عنصر است. شما می‌توانید هر یک از قوانینی که در Style هر عنصر تعریف کرده اید را فعال و یا غیر فعال کنید. همچنین امکان ویرایش آن‌ها وحود دارد.
همچنین از طریق گزینه File می‌توان لیست تمام فایل‌های سورس صفحه را مشاهده کرد.
 

 
با وجود چنین ابزاری یقیناً داشتن دو مانیتور برای برنامه نویسان و توسعه دهندگان وب کاملاً حیاتی است. چراکه Visual Studio.NET به شما این امکان را می‌دهد تا Page Inspector را در یک مانیتور و نمای سورس را در مانیتور دیگر به نمایش در آورید.
 
نکته:
جهت استفاده از تمام امکانات این ابزار باید دستور زیر را در تگ appsettings فایل web.config اضافه کنید:
 <add key="VisualStudioDesignTime:Enabled" value="true" />
پیشنهاد می‌کنم برای درک بهتر این ابزار و آشنایی با آن ویدئو مربوطه در کانال 9  را از دست ندهید.
 
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
برای افزودن لیست Claims کاربر موجود در بانک اطلاعاتی به لیست Claims کاربر وارد شده‌ی توسط اکتیو دایرکتوری، باید از یک IClaimsTransformation سفارشی استفاده کنید تا این نگاشت را انجام دهد (نمونه‌اش در مطلب « سفارشی سازی ASP.NET Core Identity - قسمت چهارم - User Claims » به نحو دیگری استفاده شده‌است):
public class ApplicationClaimsTransformation : IClaimsTransformation
{
}
پیاده سازی کامل آن در اینجا
و برای ثبت آن:
services.AddScoped<IClaimsTransformation, ApplicationClaimsTransformation>();
بازخوردهای دوره
مدیریت نگاشت ConnectionIdها در SignalR به کاربران واقعی سیستم
با توجه به اینکه این کد سمت کلاینت قابل ویرایش است ، راه حل امنی برای تعیین متصل بودن و یا غیرمتصل بودن یک ConnectionId محسوب نمی‌شود و ظاهرا تنها راه حل برای بررسی وضعیت اتصال یک ConnectionId ، چک کردن اتصال آن در دوره‌های زمانی مشخص است .
نظرات اشتراک‌ها
سایت NET Core Show.
من چند تا از قسمت‌ها را چک کردم. انگار همه آنها دارای transcript هم هستند. یعنی چیزی که در پادکست شنیده میشه به صورت متن هم در آمده است. این موضوع برای کسانی که با انگلیسی مشکل دارند خیلی خوب و کمک کننده هستش.