اشتراکها
تشخیص هویت claims-based چیست؟
در نگارشهای اولیهی ASP.NET Core، پشتیبانی از authorization، صرفا توسط ویژگی [Authorize]، قابل اعمال به اکشن متد خاصی بود. برای بهبود تنظیم این قابلیت، میانافزار جدید Authorization به ASP.NET Core 3.0 اضافه شدهاست و تنظیم آن جهت کار با امکانات امنیتی برنامه، الزامی است؛ در غیر اینصورت در حین مرور این صفحات و قسمتهای محافظت شده، برنامه با خطای زیر متوقف خواهد شد:
محل صحیح تعریف میانافزار Authorization در ASP.NET Core 3.0
توصیه شدهاست میانافزار جدید Authorization، با فراخوانی متد UseAuthorization، بلافاصله پس از فراخوانی متد UseAuthentication معرفی شود. در این حالت این میانافزار، با یک Policy پیشفرض تنظیم میشود که قابل تغییر و بازنویسی است:
در مورد مفهوم متد MapDefaultControllerRoute، میتوانید به نظرات تکمیلی مطلب Endpoint routing مراجعه کنید.
در ASP.NET Core 3.0، پس از تنظیمات فوق است که قطعه کد زیر و اعمال فیلتر Authorize، مجددا کار میکند:
روش تعریف فیلتر Authorize به صورت سراسری در ASP.NET Core 3.0
میتوان AuthorizeFilter را به صورت یک فیلتر سراسری (global filter in MVC) تعریف کرد تا به تمام کنترلرها و اکشن متدها اعمال شود. هرچند این روش هنوز هم در ASP.NET Core 3.0 کار میکند، اما روش توصیه شدهی جدید آن به صورت زیر است:
در این حالت با اعمال RequireAuthorization به MapDefaultControllerRoute، سبب خواهیم شد تا اعتبارسنجی کاربر برای استفادهی از تمام قسمتهای برنامه، اجباری شود. در این بین اگر کنترلری و یا اکشن متدی نباید محافظت شود و دسترسی آزادانهی به آنها مدنظر است، میتوان از فیلتر AllowAnonymous استفاده کرد:
سفارشی سازی سیاستهای Authorization در ASP.NET Core 3.0
اگر بخواهیم DefaultPolicy میانافزار Authorization را بازنویسی کنیم، میتوان از متد services.AddAuthorization به صورت زیر استفاده کرد که در آن authentication و داشتن یک Scope خاص را اجباری میکند:
تنظیم سیاستهای دسترسی، زمانیکه هیچ نوع تنظیمی از پیش تعریف نشدهاست
با تنظیم FallbackPolicy، میتوان تمام endpointهایی را که توسط فیلتر [Authorize] و یا ()RequireAuthorization محافظت نشدهاند، محافظت کرد.
DefaultPolicy توسط فیلتر [Authorize] و یا ()RequireAuthorization مورد استفاده قرار میگیرد؛ اما FallbackPolicy زمانی بکار خواهد رفت که هیچ نوع سیاست دسترسی تنظیم نشدهباشد. حالت پیشفرض FallbackPolicy، پذیرش تمام درخواستها بدون اعتبارسنجی است.
کارکرد مثال زیر با مثالهای قبلی که از DefaultPolicy استفاده میکردند، یکی است و هدف آن، اجبار به اعتبارسنجی کاربران در همهجا (تمام endpoints)، منهای مواردی است که از فیلتر AllowAnonymous استفاده میشود:
امکان اعمال سفارشی میانافزار Authorization به Endpointهای مجزا
برای مثال در مبحث «بررسی سلامت برنامه»، فریمورک، اعتبارسنجی درخواستهای آنرا پیش بینی نکردهاست؛ اما اینبار میتوان اعتبارسنجی را به endpoint آن اعمال کرد:
همانطور که مشاهده میکنید، در اینجا RequireAuthorization، به صورت سفارشی به یک endpoint خاص اعمال شدهاست و تنظیمات آن، با تنظیمات سایر قسمتهای برنامه یکی نیست.
Endpoint xyz contains authorization metadata, but a middleware was not found that supports authorization. Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code.
محل صحیح تعریف میانافزار Authorization در ASP.NET Core 3.0
توصیه شدهاست میانافزار جدید Authorization، با فراخوانی متد UseAuthorization، بلافاصله پس از فراخوانی متد UseAuthentication معرفی شود. در این حالت این میانافزار، با یک Policy پیشفرض تنظیم میشود که قابل تغییر و بازنویسی است:
public void Configure(IApplicationBuilder app) { ... app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); }
در ASP.NET Core 3.0، پس از تنظیمات فوق است که قطعه کد زیر و اعمال فیلتر Authorize، مجددا کار میکند:
public class HomeController : ControllerBase { [Authorize] public IActionResult BuyWidgets() { ... } }
میتوان AuthorizeFilter را به صورت یک فیلتر سراسری (global filter in MVC) تعریف کرد تا به تمام کنترلرها و اکشن متدها اعمال شود. هرچند این روش هنوز هم در ASP.NET Core 3.0 کار میکند، اما روش توصیه شدهی جدید آن به صورت زیر است:
public void Configure(IApplicationBuilder app) { ... app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute().RequireAuthorization(); }); }
[AllowAnonymous] public class HomeController : ControllerBase { ... }
سفارشی سازی سیاستهای Authorization در ASP.NET Core 3.0
اگر بخواهیم DefaultPolicy میانافزار Authorization را بازنویسی کنیم، میتوان از متد services.AddAuthorization به صورت زیر استفاده کرد که در آن authentication و داشتن یک Scope خاص را اجباری میکند:
public void ConfigureServices(IServiceCollection services) { ... services.AddAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireScope("MyScope") .Build(); }); }
با تنظیم FallbackPolicy، میتوان تمام endpointهایی را که توسط فیلتر [Authorize] و یا ()RequireAuthorization محافظت نشدهاند، محافظت کرد.
DefaultPolicy توسط فیلتر [Authorize] و یا ()RequireAuthorization مورد استفاده قرار میگیرد؛ اما FallbackPolicy زمانی بکار خواهد رفت که هیچ نوع سیاست دسترسی تنظیم نشدهباشد. حالت پیشفرض FallbackPolicy، پذیرش تمام درخواستها بدون اعتبارسنجی است.
کارکرد مثال زیر با مثالهای قبلی که از DefaultPolicy استفاده میکردند، یکی است و هدف آن، اجبار به اعتبارسنجی کاربران در همهجا (تمام endpoints)، منهای مواردی است که از فیلتر AllowAnonymous استفاده میشود:
public void ConfigureServices(IServiceCollection services) { services.AddAuthorization(options => { options.FallbackPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .RequireScope("MyScope") .Build(); }); } public void Configure(IApplicationBuilder app) { app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute(); }); } [AllowAnonymous] public class HomeController : ControllerBase { }
امکان اعمال سفارشی میانافزار Authorization به Endpointهای مجزا
برای مثال در مبحث «بررسی سلامت برنامه»، فریمورک، اعتبارسنجی درخواستهای آنرا پیش بینی نکردهاست؛ اما اینبار میتوان اعتبارسنجی را به endpoint آن اعمال کرد:
public void Configure(IApplicationBuilder app) { ... app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints .MapHealthChecks("/healthz") .RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin", }); }); }
سیستم دسترسی در یک سیستم، همیشه برای من چالش برانگیز بوده است. با دیدن کدهای مختلف از افراد مختلف، شیوههای گوناگونی از کدنویسی را دیدهام؛ ولی یکی از نکاتی که در بین آنها بررسی نشده بود و یا از آن غافل مانده بودند، بررسی بعضی از عناصر موجود در ویو بود که باید با توجه به نقش کاربر سیستم، وضعیت آن بررسی میشد.
برای مثال تصور کنید که شما دو کاربر دارید که هر دو سطح دسترسی به پروفایل کاربران دیگر را دارند. ولی یکی از کاربرها این توانایی را دارد تا با کلیک بر روی دکمهای، لاگین کاربر مورد نظر را مسدود نماید، ولی دیگری نمیتواند این دکمه را ببیند، یا برای او به صورت غیرفعال نمایش داده شود. در اکثر این مواقع کاری که برنامه نویسان انجام میدهند، نوشتن توابع به شیوههای گوناگون در لابلای انبوهی از کدهای View هست تا بتوانند این مسئله را پیاده سازی کنند. ولی با اضافه شدن هر چه بیشتر این عناصر به صفحه و با توجه به انبوهی از ویووها و ویووکامپوننتها کار بسیار سختتر میشود.
به همین جهت من از فیلترها در MVC کمک گرفتم و این موضوع را با توجه به خروجی نهایی صفحه بررسی میکنم. به این معنا که وقتی صفحه بدون هیچ گونه اعتبارسنجی در سطح ویو آماده شد، با استفاده از فیلتر مورد نظر که به صورت سراسری اضافه شده است، بررسی میکنم که آیا این کاربر حق دارد بعضی از المانها را ببیند یا خیر؟ در صورتیکه برای هر المان موجود در صفحه اعتباری نداشته باشد، آن المان از صفحه حذف و یا غیرفعال میشود.
ابتدا یک صفحه را با المانهای زیر میسازیم. از آنجا که بیشتر تگهای عملیاتی از نوع لینک و دکمه هستند، از هر کدام، سه عنصر به صفحه اضافه میکنیم:
<button data-perm="true" data-controller="c" data-action="r" data-method="post" data-type="disable">Test 1</button> <button data-perm="true" data-controller="c" data-action="t" data-method="post">Test 2</button> <button data-perm="false" data-controller="c" data-action="r" data-method="post">Test 3</button> <a data-perm="true" data-controller="c" data-action="m" data-method="post">Test 4</a> <a data-perm="false" data-controller="c" data-action="m" data-method="post">Test 5</a> <a data-perm="true" data-controller="c" data-action="t" data-method="post">Test 6</a>
ویژگی | توضیحات |
perm | آیا نیاز به اعتبارسنجی دارد یا خیر؟ در صورتی که المانی مقدار Perm آن با مقدار true پر گردد، اعتبارسنجی روی آن اعمال خواهد شد. |
controller | نام کنترلری که به آن دسترسی دارد. |
action | نام اکشنی که به آن در کنترلر ذکر شده دسترسی دارد. |
method | در صورتیکه دسترسی get و post و ... هر یک متفاوت باشد. |
type | نحوه برخورد با المان غیرمجاز. در صورتیکه با disable مقداردهی شود، المان غیرفعال و در غیر اینصورت، از روی صفحه حذف میشود. |
سپس یک کلاس جدید ساخته و با ارث بری از ActionFilterAttribute، کار ساخت فیلتر را آغاز میکنیم:
public class AuthorizePage: ActionFilterAttribute { private HtmlTextWriter _htmlTextWriter; private StringWriter _stringWriter; private StringBuilder _stringBuilder; private HttpWriter _output; IAuthorization _auth; public override void OnActionExecuting(ActionExecutingContext filterContext) { _stringBuilder = new StringBuilder(); _stringWriter = new StringWriter(_stringBuilder); _htmlTextWriter = new HtmlTextWriter(_stringWriter); _output = (HttpWriter) filterContext.RequestContext.HttpContext.Response.Output; filterContext.RequestContext.HttpContext.Response.Output = _htmlTextWriter; _auth = new Auth(); } public override void OnResultExecuted(ResultExecutedContext filterContext) { var response = _stringBuilder.ToString(); response = AuthorizeTags(response); _output.Write(response); } public string AuthorizeTags(string response) { var doc = GetHtmlDocument(response); var nodes=doc.DocumentNode.SelectNodes("//*[@data-perm]"); if (nodes == null) return response; foreach(var node in nodes) { var dataPermission = node.Attributes["data-perm"]; if(!dataPermission.Value.TryBooleanParse()) { continue; } var controller = node.Attributes["data-controller"].Value; var action = node.Attributes["data-action"].Value; var method = node.Attributes["data-method"].Value; var access=_auth.Authorize(HttpContext.Current.User.Identity.Name , controller, action, method); if (access) continue; var removeElm = true; var type = node.Attributes["data-type"]?.Value; if (type!=null && type.ToLower()== "disable") { removeElm = false; } if(removeElm) { node.Remove(); continue; } node.Attributes.Add("disabled", "true"); } return doc.DocumentNode.OuterHtml; } private HtmlDocument GetHtmlDocument(string htmlContent) { var doc = new HtmlDocument { OptionOutputAsXml = true, OptionDefaultStreamEncoding = Encoding.UTF8 }; doc.LoadHtml(htmlContent); return doc; } }
public interface IAuthorization { bool Authorize(string userId, string controller, string action, string method); }
ادامه کد در متد OnResultExecuted قرار دارد و متد اصلی کار ما میباشد. این متد بعد از صدور خروجی از اکشن، صدا زده شده اجرا میشود و شامل خروجی اکشن میباشد. خروجی اکشن را به متدی به نام AuthorizeResponse داده و با استفاده از بسته htmlagilitypack که یک HTML Parser میباشد، کدهای HTML را تحلیل میکنیم. قاعده فیلترسازی المانها در این کتابخانه بر اساس قواعد تعریف شده در XPath میباشد. بر اساس این قاعده ما گفتیم هر نوع تگی که دارای ویژگی data-perm میباشد، باید به عنوان گرههای فیلتر شده برگشت داده شود. سپس مقادیر نام کنترلر و اکشن و ... از المان دریافت شده و با استفاده از اینترفیسی که ما اینجا تعریف کردهایم، بررسی میکنیم که آیا این کاربر به این موارد دسترسی دارد یا خیر. در صورتیکه پاسخ برگشتی، از عدم اعتبار کاربر بگوید، گره مورد نظر حذف و یا در صورتیکه ویژگی data-type وجود داشته و مقدارش برابر disable باشد، آن المان غیرفعال خواهد شد. در نهایت کد تولیدی سند را به رشته تبدیل کرده و جایگزین خروجی فعلی میکنیم.
جهت تعریف سراسری آن در Global.asax داریم:
protected void Application_Start() { GlobalFilters.Filters.Add(new AuthorizePage()); }
نظرات اشتراکها
زندگی پس از Google Reader؛ نگاهی به گزینههای مهیا
تنها مشکلش تا الان برای من Auto Refresh نبودنشه
نظرات مطالب
معرفی JSON Web Token
- بله. کلاس نمونه User مطلب «پیاده سازی JSON Web Token با ASP.NET Web API 2.x» را با کلاس User مربوط به پیاده سازی خاص خودتان جایگزین کنید (در قسمت «حداقلهای بانک اطلاعاتی مورد نیاز جهت ذخیره سازی وضعیت کاربران و توکنهای آنها» مطلب یاد شده).
- برای سفارشی سازی ASP.NET Identity از مطلب «اعمال تزریق وابستگیها به مثال رسمی ASP.NET Identity» ایده بگیرید.
- برای سفارشی سازی ASP.NET Identity از مطلب «اعمال تزریق وابستگیها به مثال رسمی ASP.NET Identity» ایده بگیرید.