اشتراکها
از مخفی و غیرفعال کردن بهتر است (البته تجربه این روش رو ندارم). اما در هر صورت راه حل اصلی همان است که گفتم.
نظرات مطالب
معرفی DNTProfiler
با سلام و تبریک سال نو
نرمافزار تو ویندوز 8.1 کارنمیده
فایل ErrorsLog ظاهرا ایجاد نشد
windows 8.1 64bit,vs2013sp4
مرورگرهای جدید تحت زیر مجموعهای به نام Content Security Policy، قابلیتهای توکاری را اضافه کردهاند تا حملاتی مانند XSS را حتی در برنامهی وبی که برای این نوع حملات تمهیداتی را درنظر نگرفتهاست، خنثی کنند. این قابلیتها به صورت پیش فرض فعال نبوده و نیاز است برنامه نویس صراحتا درخواست فعال شدن آنها را از طریق افزودن تعدادی هدر مشخص به Response، ارائه دهد. در ادامه این هدرها را بررسی خواهیم کرد.
غیرفعال کردن اجرای اسکریپتهای inline
عمدهی حملات XSS زمانی قابلیت اجرا پیدا میکنند که مهاجم بتواند به طریقی (ورودیهای اعتبارسنجی نشده)، اسکریپتی را به درون صفحهی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپتهای inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کردهایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایتهایی را که عادت کردهاند اسکریپتهای خود را داخل صفحات وب مدفون کنند، از کار میاندازد. این نوع سایتها باید به روز شده و اسکریپتها را از طریق فایلهای خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
سطر اول به زودی تبدیل به یک استاندارد W3 خواهد شد؛ اما فعلا فقط توسط کروم 25 به بعد پشتیبانی میشود. سطر دوم توسط مرورگرهایی که از موتور WebKit استفاده میکنند، پشتیبانی میشود و سطر سوم مخصوص فایرفاکس است و IE 10 به بعد.
بعد از فعال شدن این قابلیت، فقط اسکریپتهایی که از طریق دومین شما به صفحه الحاق شدهاند، قابلیت اجرا را خواهند یافت و کلیه اسکریپتهای مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده میکنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آنرا صراحتا در این هدر ذکر کنید:
علاوه بر آن حتی میشود پردازش تمام منابع مورد استفاده را نیز مانند تصاویر، شیوهنامهها، فایلهای فلش و غیره، به دومین جاری محدود کرد:
بدیهی است پس از آشنایی با این مورد، احتمالا در پروژههای جدید خود از آن استفاده کنید (چون inline scriptهای فعلی شما را کاملا از کار میاندازد).
نحوهی اضافه کردن هدرهای Content Security Policy به برنامههای ASP.NET
روشی که با هر دو برنامههای وب فرم و MVC کار میکند، تهیه یک HTTP module است؛ به شرح ذیل:
و یا در برنامههای ASP.NET MVC میتوان یک فیلتر جدید را تعریف کرد و سپس آنرا به صورت عمومی معرفی نمود:
در ماژول تهیه شده چند مورد دیگر را نیز مشاهده میکنید:
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب) تنظیم هدر X-Frame-Options به SameOrigin سبب میشود تا صفحات سایت شما دیگر توسط Iframeها در سایتهای دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحهای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب میشود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.
برای مطالعه بیشتر
Security through HTTP response headers
پروژهی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/
یک نکته تکمیلی
توصیه شدهاست تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازشهای غیرهمزمان تداخل ایجاد میکند.
غیرفعال کردن اجرای اسکریپتهای inline
عمدهی حملات XSS زمانی قابلیت اجرا پیدا میکنند که مهاجم بتواند به طریقی (ورودیهای اعتبارسنجی نشده)، اسکریپتی را به درون صفحهی جاری تزریق کند. بنابراین اگر ما به مرورگر اعلام کنیم که دیگر اسکریپتهای inline را پردازش نکن، سایت را تا حد زیادی در مقابل حملات XSS مقاوم کردهایم. این قابلیت به صورت پیش فرض خاموش است؛ چون به طور قطع فعال سازی آن بسیاری از سایتهایی را که عادت کردهاند اسکریپتهای خود را داخل صفحات وب مدفون کنند، از کار میاندازد. این نوع سایتها باید به روز شده و اسکریپتها را از طریق فایلهای خارجی js، به سایت و صفحات خود الحاق کنند.
برای فعال سازی این قابلیت، فقط کافی است هدرهای زیر به Response اضافه شوند:
Content-Security-Policy: script-src 'self' X-WebKit-CSP: script-src 'self' X-Content-Security-Policy: script-src 'self'
بعد از فعال شدن این قابلیت، فقط اسکریپتهایی که از طریق دومین شما به صفحه الحاق شدهاند، قابلیت اجرا را خواهند یافت و کلیه اسکریپتهای مدفون شده داخل صفحات، دیگر اجرا نخواهد شد. در این حالت اگر از CDN برای الحاق اسکریپتی استفاده میکنید، مثلا مانند الحاق jQuery به صفحه، نیاز است مسیر آنرا صراحتا در این هدر ذکر کنید:
Content-Security-Policy: script-src 'self' https://youcdn.com X-WebKit-CSP: script-src 'self' https://yourcdn.com X-Content-Security-Policy: script-src 'self' https://yourcdn.com
Content-Security-Policy: default-src 'self' https://youcdn.com X-WebKit-CSP: default-src 'self' https://yourcdn.com X-Content-Security-Policy: default-src 'self' https://yourcdn.com
نحوهی اضافه کردن هدرهای Content Security Policy به برنامههای ASP.NET
روشی که با هر دو برنامههای وب فرم و MVC کار میکند، تهیه یک HTTP module است؛ به شرح ذیل:
using System; using System.Web; namespace AntiXssHeaders { public class SecurityHeadersConstants { public static readonly string XXssProtectionHeader = "X-XSS-Protection"; public static readonly string XFrameOptionsHeader = "X-Frame-Options"; public static readonly string XWebKitCspHeader = "X-WebKit-CSP"; public static readonly string XContentSecurityPolicyHeader = "X-Content-Security-Policy"; public static readonly string ContentSecurityPolicyHeader = "Content-Security-Policy"; public static readonly string XContentTypeOptionsHeader = "X-Content-Type-Options"; } public class ContentSecurityPolicyModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication app) { app.BeginRequest += AppBeginRequest; } void AppBeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; var response = app.Context.Response; setHeaders(response); } private static void setHeaders(HttpResponse response) { response.Headers.Set(SecurityHeadersConstants.XFrameOptionsHeader, "SameOrigin"); // For IE 8+ response.Headers.Set(SecurityHeadersConstants.XXssProtectionHeader, "1; mode=block"); response.Headers.Set(SecurityHeadersConstants.XContentTypeOptionsHeader, "nosniff"); //todo: Add /Home/Report --> public JsonResult Report() { return Json(true); } const string cspValue = "default-src 'self';"; // For Chrome 16+ response.Headers.Set(SecurityHeadersConstants.XWebKitCspHeader, cspValue); // For Firefox 4+ response.Headers.Set(SecurityHeadersConstants.XContentSecurityPolicyHeader, cspValue); response.Headers.Set(SecurityHeadersConstants.ContentSecurityPolicyHeader, cspValue); } } }
//// RegisterGlobalFilters -> filters.Add(new ContentSecurityPolicyFilterAttribute()); public class ContentSecurityPolicyFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var response = filterContext.HttpContext.Response; response.AddHeader("Content-Security-Policy", "script-src 'self'"); // the rest ... base.OnActionExecuting(filterContext); } }
الف) X-XSS-Protection مربوط است به IE 8 به بعد
ب) تنظیم هدر X-Frame-Options به SameOrigin سبب میشود تا صفحات سایت شما دیگر توسط Iframeها در سایتهای دیگر قابل نمایش نباشد و فقط در سایت جاری بتوان صفحهای را از همان دومین در صورت نیاز توسط Iframeها نمایش داد.
ج) تنظیم X-Content-Type-Options به nosniff سبب میشود تا IE سعی نکند با اجرای یک محتوا سعی در تشخیص mime-type آن کند و به این ترتیب امنیت دسترسی و مشاهده اشیاء قرار گرفته در صفحه (و یا تزریق شده توسط مهاجمین) به شدت بالا خواهد رفت.
برای مطالعه بیشتر
Security through HTTP response headers
پروژهی کاملی مخصوص افزودن هدرهای یاد شده
https://nwebsec.codeplex.com/
یک نکته تکمیلی
توصیه شدهاست تا دیگر از روال رویدادگردان PreSendRequestHeaders برای ارسال هدرها استفاده نکنید؛ چون با پردازشهای غیرهمزمان تداخل ایجاد میکند.
WhatsUp Gold Syslog Server
- یک ابزار غنی تجمیع پیامهای (لاگهای) شبکه ای با کمک پروتکلهای SNMP و Syslog
- مشاهده پیامهای شبکه در زمان واقعی همراه با فیلتر اطلاعات
- قابل پردازش تا 6 میلیون پیام در ساعت (حدودا 100000 تراکنش در دقیقه)
- ارسال پیامهای شبکه به طور مستقیم و آنلاین به فایل ویندوز
- دریافت پیام از دستگاههای مختلف از طریق پروتکلهای UDP و TCP
- ارسال پیامهای شبکه به صورت آنلاین به سرورهای دیگر با استفاده از پروتکل UDP و TCP
- حفظ آدرس منبع در هنگام ارسال پیام به یک syslog Server دیگر
- اجرا به عنوان یک سرویس ویندوزی (Windows Service) و یا در حالت Windows Application
- فیلتر و مرتب سازی پیامهای syslog برای نمایش داده شدن
- همه این قابلیتها در این نرم افزار تحت دات نت فریم ورک 4 پیاده سازی شده است
File size: 5.14MB
Operating system: Windows XP/Vista/7/Server2008
Additional Requirements: Microsoft .NET Framework 4.0
Operating system: Windows XP/Vista/7/Server2008
Additional Requirements: Microsoft .NET Framework 4.0
فرض کنید در حال توسعهی یک برنامهی Blazor WASM هاست شده هستید و میخواهید که نیازی نباشد تا به ازای هر صفحهای که به برنامه اضافه میکنید، یکبار منوی آنرا به روز رسانی کنید و نمایش منو به صورت خودکار توسط برنامه صورت گیرد. همچنین در این حالت نیاز است در قسمت مدیریتی برنامه، بتوان به صورت پویا، به ازای هر کاربری، مشخص کرد که به کدامیک از صفحات برنامه دسترسی دارد و یا خیر و به علاوه اگر به صفحاتی دسترسی ندارد، مشخصات این صفحه، در منوی پویا برنامه ظاهر نشود و همچنین با تایپ آدرس آن در نوار آدرس مرورگر نیز قابل دسترسی نباشد. امن سازی پویای سمت کلاینت، یک قسمت از پروژهاست؛ قسمت دیگر چنین پروژهای، لیست کردن اکشن متدهای API سمت سرور پروژه و انتساب دسترسیهای پویایی به این اکشن متدها، به کاربران مختلف برنامهاست.
دریافت کدهای کامل این پروژه
کدهای کامل پروژهای که نیازمندیهای فوق را پیاده سازی میکند، در اینجا میتوانید مشاهده و دریافت کنید. در این مطلب از قرار دادن مستقیم این کدها صرفنظر شده و سعی خواهد شد بجای آن، نقشهی ذهنی درک کدهای آن توضیح داده شود.
پیشنیازها
در پروژهی فوق برای شروع به کار، از اطلاعات مطرح شدهی در سلسله مطالب زیر استفاده شدهاست:
- «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- «مدیریت مرکزی شماره نگارشهای بستههای 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 میتوانید مشاهده کنید که خلاصهی آن به صورت زیر است:
ویژگی 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 ساخته شده و مقدار دهی اولیه میشود. لیست کاربران پیشفرض آنرا در اینجا میتوانید مشاهده کنید. ابتدا با کاربر ادمین وارد شده و سطوح دسترسی سایر کاربران را تغییر دهید. سپس بجای آنها وارد سیستم شده و تغییرات منوها و سطوح دسترسی پویا را بررسی کنید.
دریافت کدهای کامل این پروژه
کدهای کامل پروژهای که نیازمندیهای فوق را پیاده سازی میکند، در اینجا میتوانید مشاهده و دریافت کنید. در این مطلب از قرار دادن مستقیم این کدها صرفنظر شده و سعی خواهد شد بجای آن، نقشهی ذهنی درک کدهای آن توضیح داده شود.
پیشنیازها
در پروژهی فوق برای شروع به کار، از اطلاعات مطرح شدهی در سلسله مطالب زیر استفاده شدهاست:
- «اعتبارسنجی مبتنی بر 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 ساخته شده و مقدار دهی اولیه میشود. لیست کاربران پیشفرض آنرا در اینجا میتوانید مشاهده کنید. ابتدا با کاربر ادمین وارد شده و سطوح دسترسی سایر کاربران را تغییر دهید. سپس بجای آنها وارد سیستم شده و تغییرات منوها و سطوح دسترسی پویا را بررسی کنید.
با توجه به ماهیت چندسکویی NET 5.، در اکثر سیستمهای ویندوزی، سرویس بومی سازی، بر اساس استاندارد NLS کار میکند، اما در سیستمهای لینوکسی و مبتنی بر یونیکس، این استاندارد از نوع ICU است (و وجود و تنظیم آنها خارج از NET. و توسط سیستم عامل مدیریت میشود). جهت یکدست سازی این دو نوع سیستم بومی سازی در دات نت، از نگارش 5 آن به بعد، استاندارد ICU که به صورت گستردهتری مورد پذیرش قرار گرفتهاست، استاندارد بومی سازی پیشفرض دات نت درنظر گرفته میشود؛ مگر اینکه سیستم عاملی آنرا پشتیبانی نکند.
کدام نگارش از ویندوز، از ICU پشتیبانی میکند؟
تمام ویندوزهای پس از Windows 10 May 2019 Update، به همراه icu.dll، به عنوان جزء استاندارد سیستم عامل هستند. بنابراین دات نت 5 و نگارشهای پس از آن، در این سیستم عاملها، از سرویس بومی سازی ICU استفاده خواهند کرد؛ اما اگر از نگارشهای پیشین ویندوز استفاده میکنید، به اجبار به سیستم NLS سوئیچ خواهد شد.
تاثیر ICU بر برنامههای دات نت 5 به بعد
قطعه کد زیر را درنظر بگیرید:
در نگارشهای پیش از 5 دات نت، خروجی کدهای فوق، عدد 6 است؛ اما ... اما ... (!) از زمان دات نت 5 به بعد، خروجی آن «منهای یک» است! البته به شرطی که آخرین به روز رسانی ویندوز 10 را نصب کرده باشید؛ یعنی حداقل Windows 10 May 2019 Update را داشته باشید.
حالت «پیشفرض» جستجو و مقایسهی رشتهها در دات نت 5 به بعد، یک مقایسهی مبتنی بر «دستورات زبانی» بر اساس فرهنگ تنظیم شدهی در Thread جاری برنامهاست (یا همان System.Threading.Thread.CurrentThread.CurrentCulture).
چرا متدهای کار بر روی رشتهها در دات نت 5 به بعد، نسبت به نگارشهای قبلی متفاوت عمل میکنند؟
زمانیکه متدی مانند IndexOf فراخوانی میشود، هدف عمدهی برنامهنویسها، یک جستجوی Ordinal است (یعنی مقایسهی کاراکتر به کاراکتر؛ بدون درنظر گرفتن نکات زبانی و بومی)؛ اما فراموش میکنند که این متدها دارای پارامتر دومی هم هستند که از نوع StringComparison است و سالها است که توصیه میشود این پارامتر را هم به صورت صریحی مقدار دهی کنید تا هدف خود را از نوع جستجو دقیقا مشخص نمائید. از زمان دات نت 5 به بعد، اگر این پارامتر را مشخص نکنید، جستجوی صورت گرفته یک رفتار culture-specific را خواهد داشت و نه Ordinal. از این لحاظ مقایسهی رشتهها توسط استانداردهای ICU و NLS، بر اساس پیاده سازیهای مختلف زبانشناسی، خروجیهای یکسانی را ارائه نمیدهند و به همین جهت است که اینبار خروجی منهای یک را دریافت میکنیم.
یک نکته: خروجی قطعه کد فوق در سیستمهای لینوکسی که از .NET Core 2x - 3x. هم استفاده میکنند، دقیقا منهای یک است؛ چون پیشفرض بومی سازی آنها نیز ICU است.
چگونه میتوان به همان حالت پیشین مقایسهی رشتهها در NET. بازگشت؟
مایکروسافت بستهی نیوگت Microsoft.CodeAnalysis.FxCopAnalyzers را جهت گوشزد کردن نکتهی ذکر صریح StringComparison، به روز رسانی کردهاست. بنابراین بهتر است تا آنرا به پروژهی خود اضافه کنید. در این حالت اخطارهای مناسبی را جهت یافتن قسمتهای مشکلدار برنامهی خود دریافت میکنید. برای مثال برای اینکه در قطعه کد فوق به همان پاسخ متداول 6 برسیم، تنها کافیاست پارامتر دوم StringComparison را ذکر کنیم:
و یا حتی میتوانید فایل csproj پروژهی خود را ویرایش کرده و یک سطر زیر را به آن اضافه کنید:
در این حالت کل برنامهی شما بدون هیچ تغییری مانند قبل کار کرده و از سیستم NLS استفاده میشود.
کدام متدهای کار با رشتهها در دات نت 5، تحت تاثیر این تغییرات قرار گرفتهاند؟
اگر از متدهای زیر در برنامههای خود استفاده میکنید، نکتهی ذکر پارامتر StringComparison.Ordinal را فراموش نکنید:
سؤال: اگر متدی پارامتر دوم StringComparison را نداشت چطور؟
اگر به ماخذ «Behavior changes when comparing strings on .NET 5» مراجعه کنید، در انتهای آن جدولی را ارائه داده که دو سطر اول آن، به صورت زیر است:
در این جدول، هر متدی که رفتار پیشفرض آن از نوع CurrentCulture است، تحت تاثیر قرار گرفتهاست و متدی مانند string.Contains که رفتار پیشفرض آن Ordinal است، از این تغییرات مصون است و نیازی به تغییری ندارد.
برای مطالعهی بیشتر:
Behavior changes when comparing strings on .NET 5+
.NET globalization and ICU.
Globalization breaking changes
بحث و گفتگویی در این مورد
کدام نگارش از ویندوز، از ICU پشتیبانی میکند؟
تمام ویندوزهای پس از Windows 10 May 2019 Update، به همراه icu.dll، به عنوان جزء استاندارد سیستم عامل هستند. بنابراین دات نت 5 و نگارشهای پس از آن، در این سیستم عاملها، از سرویس بومی سازی ICU استفاده خواهند کرد؛ اما اگر از نگارشهای پیشین ویندوز استفاده میکنید، به اجبار به سیستم NLS سوئیچ خواهد شد.
تاثیر ICU بر برنامههای دات نت 5 به بعد
قطعه کد زیر را درنظر بگیرید:
string s = "Hello\r\nworld!"; int idx = s.IndexOf("\n"); Console.WriteLine(idx);
حالت «پیشفرض» جستجو و مقایسهی رشتهها در دات نت 5 به بعد، یک مقایسهی مبتنی بر «دستورات زبانی» بر اساس فرهنگ تنظیم شدهی در Thread جاری برنامهاست (یا همان System.Threading.Thread.CurrentThread.CurrentCulture).
چرا متدهای کار بر روی رشتهها در دات نت 5 به بعد، نسبت به نگارشهای قبلی متفاوت عمل میکنند؟
زمانیکه متدی مانند IndexOf فراخوانی میشود، هدف عمدهی برنامهنویسها، یک جستجوی Ordinal است (یعنی مقایسهی کاراکتر به کاراکتر؛ بدون درنظر گرفتن نکات زبانی و بومی)؛ اما فراموش میکنند که این متدها دارای پارامتر دومی هم هستند که از نوع StringComparison است و سالها است که توصیه میشود این پارامتر را هم به صورت صریحی مقدار دهی کنید تا هدف خود را از نوع جستجو دقیقا مشخص نمائید. از زمان دات نت 5 به بعد، اگر این پارامتر را مشخص نکنید، جستجوی صورت گرفته یک رفتار culture-specific را خواهد داشت و نه Ordinal. از این لحاظ مقایسهی رشتهها توسط استانداردهای ICU و NLS، بر اساس پیاده سازیهای مختلف زبانشناسی، خروجیهای یکسانی را ارائه نمیدهند و به همین جهت است که اینبار خروجی منهای یک را دریافت میکنیم.
یک نکته: خروجی قطعه کد فوق در سیستمهای لینوکسی که از .NET Core 2x - 3x. هم استفاده میکنند، دقیقا منهای یک است؛ چون پیشفرض بومی سازی آنها نیز ICU است.
چگونه میتوان به همان حالت پیشین مقایسهی رشتهها در NET. بازگشت؟
مایکروسافت بستهی نیوگت Microsoft.CodeAnalysis.FxCopAnalyzers را جهت گوشزد کردن نکتهی ذکر صریح StringComparison، به روز رسانی کردهاست. بنابراین بهتر است تا آنرا به پروژهی خود اضافه کنید. در این حالت اخطارهای مناسبی را جهت یافتن قسمتهای مشکلدار برنامهی خود دریافت میکنید. برای مثال برای اینکه در قطعه کد فوق به همان پاسخ متداول 6 برسیم، تنها کافیاست پارامتر دوم StringComparison را ذکر کنیم:
int idx = s.IndexOf("\n", StringComparison.Ordinal);
و یا حتی میتوانید فایل csproj پروژهی خود را ویرایش کرده و یک سطر زیر را به آن اضافه کنید:
<ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" /> </ItemGroup>
کدام متدهای کار با رشتهها در دات نت 5، تحت تاثیر این تغییرات قرار گرفتهاند؟
اگر از متدهای زیر در برنامههای خود استفاده میکنید، نکتهی ذکر پارامتر StringComparison.Ordinal را فراموش نکنید:
System.String.Compare System.String.EndsWith System.String.IndexOf System.String.StartsWith System.String.ToLower System.String.ToLowerInvariant System.String.ToUpper System.String.ToUpperInvariant System.Globalization.TextInfo (most members) System.Globalization.CompareInfo (most members) System.Array.Sort (when sorting arrays of strings) System.Collections.Generic.List<T>.Sort() (when the list elements are strings) System.Collections.Generic.SortedDictionary<TKey,TValue> (when the keys are strings) System.Collections.Generic.SortedList<TKey,TValue> (when the keys are strings) System.Collections.Generic.SortedSet<T> (when the set contains strings)
سؤال: اگر متدی پارامتر دوم StringComparison را نداشت چطور؟
اگر به ماخذ «Behavior changes when comparing strings on .NET 5» مراجعه کنید، در انتهای آن جدولی را ارائه داده که دو سطر اول آن، به صورت زیر است:
API Default behavior Remarks string.Compare CurrentCulture
برای مطالعهی بیشتر:
Behavior changes when comparing strings on .NET 5+
.NET globalization and ICU.
Globalization breaking changes
بحث و گفتگویی در این مورد
مطالب
SFDown
چند روز قبل جهت دریافت فایلهای تنظیم سطح دوم کش NHibernate به سایت سورس فورج مراجعه کردم و ... آه از نهادم برخاست! نه از این جهت که این سایت مدت مدیدی است ما رو تحریم کرده، به این دلیل که سورس فورج حتی با IP غیر ایرانی تونسته بود موقعیت من رو شناسایی کنه. شبیه به همین مورد مدتی است توسط گوگل نیز بکارگرفته میشه. به نظر میرسه این وسط جایی نشتی وجود داره. برای مثال در فایرفاکس امکان گزارش Geo Location به صورت پیش فرض فعال است. هر چند در مستندات آن صراحتا عنوان شده که ... خیر ... ما این اطلاعات را بدون تائید شما بروز نمیدهیم؛ ولی سؤال اینجا است که چطور تونستند از روی IP غیرایرانی، موقعیت من رو تشخیص دهند؟!
غیر فعال کردن این مورد هم ساده است. مطابق تصویر زیر عمل کنید:
به عبارتی در نوار آدرس فایرفاکس عبارت about:config را تایپ کرده، سپس عبارت geo.enabled را یافته و غیرفعال کنید. اکنون نیاز است یکبار مرورگر را بسته و باز کنید.
و ... IP ایی که سوخت ... تا یکی دو هفته عمل نخواهند کرد و بعد از لیست سیاه پاک خواهد شد.
برای رفع این نقیصه و سادهتر کردن دریافت فایلها از سورج فورج، برنامهی کوچکی را تهیه کردهام که با گرفتن آدرس یک پروژه سورس فورج، لینک مستقیم قابل دریافت فایلهای آنرا در اختیار شما قرار میدهد. این برنامه بر اساس Mirror های سورس فورج عمل میکند و به صورت خودکار تمام آنها را بررسی کرده و مورد قابل استفاده را گزارش خواهد داد. یا میتوانید توسط خود برنامه فایل نهایی را دریافت کنید یا امکان کپی کردن یا ذخیره کردن لینکهای مستقیم نهایی یافت شده، در برنامه پیش بینی شده است (جهت دریافت توسط برنامه download manager مورد علاقه شما).
The Mobile Application Security Verification Standard (MASVS) is a standard for mobile app security
استاندارد امنیتی MASVS شامل موضوعات زیر میباشد :
- Architecture, Design & Threat Modeling Requirements
- Data Storage & Privacy Requirements
- Cryptography Requirements
- Authentication & Session Management Requirements
- Network Communication Requirements
- Environmental Interaction Requirements
- Code Quality & Build Setting Requirements
- Resiliency Against Reverse Engineering Requirements