Asp.Net Identity #2
در مرحلهی بعد سه پکیج زیر را باید نصب کنیم :
- Microsoft.AspNet.Identity.EntityFramework
- Microsoft.AspNet.Identity.OWIN
- Microsoft.Owin.Host.SystemWeb
<connectionStrings> <add name="IdentityDb" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/> </connectionStrings>
<appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> <add key="owin:AppStartup" value="Users.IdentityConfig" /> </appSettings>
ساخت کلاس User
using System; using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models { public class AppUser : IdentityUser { // پروپرتیهای اضافی در اینجا } }
ساخت کلاس Database Context برنامه
using System.Data.Entity; using Microsoft.AspNet.Identity.EntityFramework; using Users.Models; namespace Users.Infrastructure { public class AppIdentityDbContext : IdentityDbContext<AppUser> { public AppIdentityDbContext() : base("IdentityDb") { } static AppIdentityDbContext() { Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit()); } public static AppIdentityDbContext Create() { return new AppIdentityDbContext(); } } public class IdentityDbInit : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> { protected override void Seed(AppIdentityDbContext context) { PerformInitialSetup(context); base.Seed(context); } public void PerformInitialSetup(AppIdentityDbContext context) { // initial configuration will go here } } }
ساخت کلاس User Manager
کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، میبینید که تمامی متدهای بالا به کلمهی Async ختم میشوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیتها را بلوکه نکنند.
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Users.Models; namespace Users.Infrastructure { public class AppUserManager : UserManager<AppUser> { public AppUserManager(IUserStore<AppUser> store) : base(store) { } public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) { AppIdentityDbContext db = context.Get<AppIdentityDbContext>(); AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db)); return manager; } } }
زمانی که Identity نیاز به وهلهای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق میافتد که عملیاتی بر روی دادههای کاربر انجام گیرد. برای ساخت وهلهای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T توسط EF میباشد که وظیفهی آن فراهم کنندهی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) میباشد. برای ساخت <UserStore<T نیاز به وهلهای از کلاس AppIdentityDbContext میباشد که از طریق Owin به صورت زیر قابل دریافت است:
AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده میشود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شدهی در کلاس شروع Owin مینماید.
ساخت کلاس شروع Owin
اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:
<add key="owin:AppStartup" value="Users.IdentityConfig" />
قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامهی شماست و IdentityConfig بیانگر کلاس شروع میباشد. (البته شما میتوانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف میکنید یکی باشد)
Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، میباشد. به طور پیش فرض این کلاس Start نام دارد و در پوشهی App_Start تعریف میشود. این کلاس حاوی یک متد به نام Configuration میباشد که بوسیله زیرساخت Owin فراخوانی میشود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده میشود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.
using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Owin; using Users.Infrastructure; namespace Users { public class IdentityConfig { public void Configuration(IAppBuilder app) { app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create); app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), }); } } }
رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شدهاند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهلهای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین میکند که هر درخواست، به یک دادهی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity میگوید که چگونه از کوکیها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص میشود. مهمترین قسمت در اینجا پروپرتی LoginPath میباشد و مشخص میکند که کلاینتهای تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده میشود.
خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفادهی از این سیستم خواهیم پرداخت.
سیستم ASP.NET Membership بهمراه ASP.NET 2.0 در سال 2005 معرفی شد، و از آن زمان تا بحال تغییرات زیادی در چگونگی مدیریت احزار هویت و اختیارات کاربران توسط اپلیکیشنهای وب بوجود آمده است. ASP.NET Identity نگاهی تازه است به آنچه که سیستم Membership هنگام تولید اپلیکیشنهای مدرن برای وب، موبایل و تبلت باید باشد.
پیش زمینه: سیستم عضویت در ASP.NET
- الگوی پایگاه داده آن برای SQL Server طراحی شده است، و قادر به تغییرش هم نیستید. میتوانید اطلاعات پروفایل را اضافه کنید، اما تمام دادهها در یک جدول دیگر ذخیره میشوند، که دسترسی به آنها نیز مشکلتر است، تنها راه دسترسی Profile Provider API خواهد بود.
- سیستم تامین کننده (Provider System) امکان تغییر منبع دادهها را به شما میدهد، مثلا میتوانید از بانکهای اطلاعاتی MySQL یا Oracle استفاده کنید. اما تمام سیستم بر اساس پیش فرض هایی طراحی شده است که تنها برای بانکهای اطلاعاتی relational درست هستند. میتوانید تامین کننده (Provider) ای بنویسید که دادههای سیستم عضویت را در منبعی به غیر از دیتابیسهای relational ذخیره میکند؛ مثلا Windows Azure Storage Tables. اما در این صورت باید مقادیر زیادی کد بنویسید. مقادیر زیادی هم System.NotImplementedException باید بنویسید، برای متد هایی که به دیتابیسهای NoSQL مربوط نیستند.
- از آنجایی که سیستم ورود/خروج سایت بر اساس مدل Forms Authentication کار میکند، سیستم عضویت نمیتواند از OWIN استفاده کند. OWIN شامل کامپوننت هایی برای احراز هویت است که شامل سرویسهای خارجی هم میشود (مانند Microsoft Accounts, Facebook, Google, Twitter). همچنین امکان ورود به سیستم توسط حسابهای کاربری سازمانی (Organizational Accounts) نیز وجود دارد مانند Active Directory و Windows Azure Active Directory. این کتابخانه از OAuth 2.0، JWT و CORS نیز پشتیبانی میکند.
- ذخیره دادههای سیستم عضویت در بانکهای اطلاعاتی non-relational مشکل است.
- نمی توانید از آن در کنار OWIN استفاده کنید.
- با فراهم کنندههای موجود ASP.NET Membership بخوبی کار نمیکند. توسعه پذیر هم نیست.
ASP.NET Universal Providers
ASP.NET Universal Providers برای ذخیره سازی اطلاعات سیستم عضویت در Windows Azure SQL Database توسعه پیدا کردند. با SQL Server Compact هم بخوبی کار میکنند. این تامین کنندهها بر اساس Entity Framework Code First ساخته شده بودند و بدین معنا بود که دادههای سیستم عضویت را میتوان در هر منبع داده ای که توسط EF پشتیبانی میشود ذخیره کرد. با انتشار این تامین کنندهها الگوی دیتابیس سیستم عضویت نیز بسیار سبکتر و بهتر شد. اما این سیستم بر پایه زیر ساخت ASP.NET Membership نوشته شده است، بنابراین محدودیتهای پیشین مانند محدودیتهای SqlMembershipProvider هنوز وجود دارند. به بیان دیگر، این سیستمها همچنان برای بانکهای اطلاعاتی relational طراحی شده اند، پس سفارشی سازی اطلاعات کاربران و پروفایلها هنوز مشکل است. در آخر آنکه این تامین کنندهها هنوز از مدل احراز هویت فرم استفاده میکنند.
ASP.NET Identity
- یک سیستم هویت واحد (One ASP.NET Identity system)
- سیستم ASP.NET Identity میتواند در تمام فریم ورکهای مشتق از ASP.NET استفاده شود. مانند ASP.NET MVC, Web Forms, Web Pages, Web API و SignalR
- از این سیستم میتوانید در تولید اپلیکیشنهای وب، موبایل، استور (Store) و یا اپلیکیشنهای ترکیبی استفاده کنید.
- سادگی تزریق دادههای پروفایل درباره کاربران
- روی الگوی دیتابیس برای اطلاعات کاربران و پروفایلها کنترل کامل دارید. مثلا میتوانید به سادگی یک فیلد، برای تاریخ تولد در نظر بگیرید که کاربران هنگام ثبت نام در سایت باید آن را وارد کنند.
- کنترل ذخیره سازی/واکشی اطلاعات
- بصورت پیش فرض ASP.NET Identity تمام اطلاعات کاربران را در یک دیتابیس ذخیره میکند. تمام مکانیزمهای دسترسی به دادهها توسط EF Code First کار میکنند.
- از آنجا که روی الگوی دیتابیس، کنترل کامل دارید، تغییر نام جداول و یا نوع داده فیلدهای کلیدی و غیره ساده است.
- استفاده از مکانیزمهای دیگر برای مدیریت دادههای آن ساده است، مانند SharePoint, Windows Azure Storage Table و دیتابیسهای NoSQL.
- تست پذیری
- ASP.NET Identity تست پذیری اپلیکیشن وب شما را بیشتر میکند. میتوانید برای تمام قسمت هایی که از ASP.NET Identity استفاده میکنند تست بنویسید.
- تامین کننده نقش (Role Provider)
- تامین کننده ای وجود دارد که به شما امکان محدود کردن سطوح دسترسی بر اساس نقوش را میدهد. بسادگی میتوانید نقشهای جدید مانند "Admin" بسازید و بخشهای مختلف اپلیکیشن خود را محدود کنید.
- Claims Based
- ASP.NET Identity از امکان احراز هویت بر اساس Claims نیز پشتیبانی میکند. در این مدل، هویت کاربر بر اساس دسته ای از اختیارات او شناسایی میشود. با استفاده از این روش توسعه دهندگان برای تعریف هویت کاربران، آزادی عمل بیشتری نسبت به مدل Roles دارند. مدل نقشها تنها یک مقدار منطقی (bool) است؛ یا عضو یک نقش هستید یا خیر، در حالیکه با استفاده از روش Claims میتوانید اطلاعات بسیار ریز و دقیقی از هویت کاربر در دست داشته باشید.
- تامین کنندگان اجتماعی
- به راحتی میتوانید از تامین کنندگان دیگری مانند Microsoft, Facebook, Twitter, Google و غیره استفاده کنید و اطلاعات مربوط به کاربران را در اپلیکیشن خود ذخیره کنید.
- Windows Azure Active Directory
- برای اطلاعات بیشتر به این لینک مراجعه کنید.
- یکپارچگی با OWIN
- ASP.NET Identity بر اساس OWIN توسعه پیدا کرده است، بنابراین از هر میزبانی که از OWIN پشتیبانی میکند میتوانید استفاده کنید. همچنین هیچ وابستگی ای به System.Web وجود ندارد. ASP.NET Identity یک فریم ورک کامل و مستقل برای OWIN است و میتواند در هر اپلیکیشنی که روی OWIN میزبانی شده استفاده شود.
- ASP.NET Identity از OWIN برای ورود/خروج کاربران در سایت استفاده میکند. این بدین معنا است که بجای استفاده از Forms Authentication برای تولید یک کوکی، از OWIN CookieAuthentication استفاده میشود.
- پکیج NuGet
- ASP.NET Identity در قالب یک بسته NuGet توزیع میشود. این بسته در قالب پروژههای ASP.NET MVC, Web Forms و Web API که با Visual Studio 2013 منتشر شدند گنجانده شده است.
- توزیع این فریم ورک در قالب یک بسته NuGet این امکان را به تیم ASP.NET میدهد تا امکانات جدیدی توسعه دهند، باگها را برطرف کنند و نتیجه را بصورت چابک به توسعه دهندگان عرضه کنند.
ASP.NET Identity در قالب پروژههای ASP.NET MVC, Web Forms, Web API و SPA که بهمراه Visual Studio 2013 منتشر شده اند استفاده میشود. در ادامه به اختصار خواهیم دید که چگونه ASP.NET Identity کار میکند.
- یک پروژه جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
-
پروژه ایجاد شده شامل سه بسته میشود که مربوط به ASP.NET Identity هستند:
- Microsoft.AspNet.Identity.EntityFramework این بسته شامل پیاده سازی ASP.NET Identity با Entity Framework میشود، که تمام دادههای مربوطه را در یک دیتابیس SQL Server ذخیره میکند.
- Microsoft.AspNet.Identity.Core این بسته محتوی تمام interfaceهای ASP.NET Identity است. با استفاده از این بسته میتوانید پیاده سازی دیگری از ASP.NET Identity بسازید که منبع داده متفاوتی را هدف قرار میدهد. مثلا Windows Azure Storage Table و دیتابیسهای NoSQL.
- Microsoft.AspNet.Identity.OWIN این بسته امکان استفاده از احراز هویت OWIN را در اپلیکیشنهای ASP.NET فراهم میکند. هنگام تولید کوکیها از OWIN Cookie Authentication استفاده خواهد شد.
هنگامیکه بر روی دکمهی Register کلیک شود، کنترلر Account، اکشن متد Register را فراخوانی میکند تا حساب کاربری جدیدی با استفاده از ASP.NET Identity API ساخته شود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
اگر حساب کاربری با موفقیت ایجاد شود، کاربر توسط فراخوانی متد SignInAsync به سایت وارد میشود.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser() { UserName = model.UserName }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInAsync(user, isPersistent: false); return RedirectToAction("Index", "Home"); } else { AddErrors(result); } } // If we got this far, something failed, redisplay form return View(model); }
private async Task SignInAsync(ApplicationUser user, bool isPersistent) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentityAsync( user, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn( new AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو Claims-based هستند، فریم ورک، انتظار آبجکتی از نوع ClaimsIdentity را خواهد داشت. این آبجکت تمامی اطلاعات لازم برای تشخیص هویت کاربر را در بر دارد. مثلا اینکه کاربر مورد نظر به چه نقش هایی تعلق دارد؟ و اطلاعاتی از این قبیل. در این مرحله میتوانید Claimهای بیشتری را به کاربر بیافزایید.
کلیک کردن روی لینک Log off در سایت، اکشن متد LogOff در کنترلر Account را اجرا میکند.
// POST: /Account/LogOff [HttpPost] [ValidateAntiForgeryToken] public ActionResult LogOff() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); }
همانطور که مشاهده میکنید برای ورود/خروج کاربران از AuthenticationManager استفاده میشود که متعلق به OWIN است. متد SignOut همتای متد FormsAuthentication.SignOut است.
کامپوننتهای ASP.NET Identity
تصویر زیر اجزای تشکیل دهنده ASP.NET Identity را نمایش میدهد. بسته هایی که با رنگ سبز نشان داده شده اند سیستم کلی ASP.NET Identity را میسازند. مابقی بستهها وابستگی هایی هستند که برای استفاده از ASP.NET Identity در اپلیکیشنهای ASP.NET لازم اند.
دو پکیج دیگر نیز وجود دارند که به آنها اشاره نشد:
- Microsoft.Security.Owin.Cookies این بسته امکان استفاده از مدل احراز هویت مبتنی بر کوکی (Cookie-based Authentication) را فراهم میکند. مدلی مانند سیستم ASP.NET Forms Authentication.
- EntityFramework که نیازی به معرفی ندارد.
مهاجرت از Membership به ASP.NET Identity
قدمهای بعدی
فریم ورک Identity در سال 2013 معرفی شد، که دنباله سیستم ASP.NET Membership بود. سیستم قبلی گرچه طی سالیان استفاده میشد اما مشکلات زیادی هم بهمراه داشت. بعلاوه با توسعه دنیای وب و نرم افزار، قابلیتهای مدرنی مورد نیاز بودند که باید پشتیبانی میشدند. فریم ورک Identity در ابتدا سیستم ساده و کارآمدی برای مدیریت کاربران بوجود آورد و مشکلات پیشین را تا حد زیادی برطرف نمود. بعنوان مثال فریم ورک جدید مبتنی بر EF Code-first است، که سفارشی کردن سیستم عضویت را بسیار آسان میکند و به شما کنترل کامل میدهد. یا مثلا احراز هویت مبتنی بر پروتوکل OAuth پشتیبانی میشود که به شما اجازه استفاده از فراهم کنندگان خارجی مانند گوگل، فیسبوک و غیره را میدهد.
نسخه جدید این فریم ورک ویژگیهای زیر را معرفی میکند (بعلاوه مواردی دیگر):
- مدل حسابهای کاربری توسعه داده شده. مثلا آدرس ایمیل و اطلاعات تماس را هم در بر میگیرد
- احراز هویت دو مرحله ای (Two-Factor Authentication) توسط اطلاع رسانی ایمیلی یا پیامکی. مشابه سیستمی که گوگل، مایکروسافت و دیگران استفاده میکنند
- تایید حسابهای کاربری توسط ایمیل (Account Confirmation)
- مدیریت کاربران و نقشها (Administration of Users & Roles)
- قفل کردن حسابهای کاربری در پاسخ به Invalid log-in attempts
- تامین کننده شناسه امنیتی (Security Token Provider) برای بازتولید شناسهها در پاسخ به تغییرات تنظیمات امنیتی (مثلا هنگام تغییر کلمه عبور)
- بهبود پشتیبانی از Social log-ins
- یکپارچه سازی ساده با Claims-based Authorization
Identity 2.0 تغییرات چشم گیری نسبت به نسخه قبلی بهوجود آورده است. به نسبت ویژگیهای جدید، پیچیدگیهایی نیز معرفی شدهاند. اگر به تازگی (مانند خودم) با نسخه 1 این فریم ورک آشنا شده و کار کرده اید، آماده شوید! گرچه لازم نیست از صفر شروع کنید، اما چیزهای بسیاری برای آموختن وجود دارد.
در این مقاله نگاهی اجمالی به نسخهی جدید این فریم ورک خواهیم داشت. کامپوننتهای جدید و اصلی را خواهیم شناخت و خواهیم دید هر کدام چگونه در این فریم ورک کار میکنند. بررسی عمیق و جزئی این فریم ورک از حوصله این مقاله خارج است، بنابراین به این مقاله تنها بعنوان یک نقطه شروع برای آشنایی با این فریم ورک نگاه کنید.
اگر به دنبال اطلاعات بیشتر و بررسیهای عمیقتر هستید، لینک هایی در انتهای این مقاله نگاشت شده اند. همچنین طی هفتههای آینده چند مقاله تخصصیتر خواهم نوشت تا از دید پیاده سازی بیشتر با این فریم ورک آشنا شوید.
در این مقاله با مقدار قابل توجهی کد مواجه خواهید شد. لازم نیست تمام جزئیات آنها را بررسی کنید، تنها با ساختار کلی این فریم ورک آشنا شوید. کامپوننتها را بشناسید و بدانید که هر کدام در کجا قرار گرفته اند، چطور کار میکنند و اجزای کلی سیستم چگونه پیکربندی میشوند. گرچه، اگر به برنامه نویسی دات نت (#ASP.NET, C) تسلط دارید و با نسخه قبلی Identity هم کار کرده اید، درک کدهای جدید کار ساده ای خواهد بود.
Identity 2.0 با نسخه قبلی سازگار نیست
اپلیکیشن هایی که با نسخه 1.0 این فریم ورک ساخته شده اند نمیتوانند بسادگی به نسخه جدید مهاجرت کنند. قابلیت هایی جدیدی که پیاده سازی شده اند تغییرات چشمگیری در معماری این فریم ورک بوجود آورده اند، همچنین API مورد استفاده در اپلیکیشنها نیز دستخوش تغییراتی شده است. مهاجرت از نسخه 1.0 به 2.0 نیاز به نوشتن کدهای جدید و اعمال تغییرات متعددی دارد که از حوصله این مقاله خارج است. فعلا همین قدر بدانید که این مهاجرت نمیتواند بسادگی در قالب Plug-in and play صورت پذیرد!
شروع به کار : پروژه مثالها را از NuGet دریافت کنید
در حال حاظر (هنگام نوشتن این مقاله) قالب پروژه استانداردی برای اپلیکیشنهای ASP.NET MVC که ا ز Identity 2.0 استفاده کنند وجود ندارد. برای اینکه بتوانید از نسخه جدید این فریم ورک استفاده کنید، باید پروژه مثال را توسط NuGet دریافت کنید. ابتدا پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب Empty را در دیالوگ تنظیمات انتخاب کنید.
کنسول Package Manager را باز کنید و با اجرای فرمان زیر پروژه مثالها را دانلود کنید.
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
پیکربندی Identity : دیگر به سادگی نسخه قبلی نیست
به نظر من یکی از مهمترین نقاط قوت فریم ورک Identity یکی از مهمترین نقاط ضعفش نیز بود. سادگی نسخه 1.0 این فریم ورک کار کردن با آن را بسیار آسان میکرد و به سادگی میتوانستید ساختار کلی و روند کارکردن کامپوننتهای آن را درک کنید. اما همین سادگی به معنای محدود بودن امکانات آن نیز بود. بعنوان مثال میتوان به تایید حسابهای کاربری یا پشتیبانی از احراز هویتهای دو مرحله ای اشاره کرد.
برای شروع نگاهی اجمالی به پیکربندی این فریم ورک و اجرای اولیه اپلیکیشن خواهیم داشت. سپس تغییرات را با نسخه 1.0 مقایسه میکنیم.
در هر دو نسخه، فایلی بنام Startup.cs در مسیر ریشه پروژه خواهید یافت. در این فایل کلاس واحدی بنام Startup تعریف شده است که متد ()ConfigureAuth را فراخوانی میکند. چیزی که در این فایل مشاهده نمیکنیم، خود متد ConfigureAuth است. این بدین دلیل است که مابقی کد کلاس Startup در یک کلاس پاره ای (Partial) تعریف شده که در پوشه App_Start قرار دارد. نام فایل مورد نظر Startup.Auth.cs است که اگر آن را باز کنید تعاریف یک کلاس پاره ای بهمراه متد ()ConfigureAuth را خواهید یافت. در یک پروژه که از نسخه Identity 1.0 استفاده میکند، کد متد ()ConfigureAuth مطابق لیست زیر است.
public partial class Startup { public void ConfigureAuth(IAppBuilder app) { // Enable the application to use a cookie to // store information for the signed in user app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login") }); // Use a cookie to temporarily store information about a // user logging in with a third party login provider app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Uncomment the following lines to enable logging // in with third party login providers //app.UseMicrosoftAccountAuthentication( // clientId: "", // clientSecret: ""); //app.UseTwitterAuthentication( // consumerKey: "", // consumerSecret: ""); //app.UseFacebookAuthentication( // appId: "", // appSecret: ""); //app.UseGoogleAuthentication(); } }
public partial class Startup { public void ConfigureAuth(IAppBuilder app) { // Configure the db context, user manager and role // manager to use a single instance per request app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create); // Enable the application to use a cookie to store information for the // signed in user and to use a cookie to temporarily store information // about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user // logs in. This is a security feature which is used when you // change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator .OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) } }); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); // Enables the application to temporarily store user information when // they are verifying the second factor in the two-factor authentication process. app.UseTwoFactorSignInCookie( DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); // Enables the application to remember the second login verification factor such // as phone or email. Once you check this option, your second step of // verification during the login process will be remembered on the device where // you logged in from. This is similar to the RememberMe option when you log in. app.UseTwoFactorRememberBrowserCookie( DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); // Uncomment the following lines to enable logging in // with third party login providers //app.UseMicrosoftAccountAuthentication( // clientId: "", // clientSecret: ""); //app.UseTwitterAuthentication( // consumerKey: "", // consumerSecret: ""); //app.UseFacebookAuthentication( // appId: "", // appSecret: ""); //app.UseGoogleAuthentication(); } }
مورد بعدی ای که جلب توجه میکند فراخوانیهای دیگری برای پیکربندی احراز هویت دو مرحلهای است. همچنین پیکربندیهای جدیدی برای کوکیها تعریف شده است که در نسخه قبلی وجود نداشتند.
تا اینجا پیکربندیهای اساسی برای اپلیکیشن شما انجام شده است و میتوانید از اپلیکیشن خود استفاده کنید. بکارگیری فراهم کنندگان خارجی در حال حاضر غیرفعال است و بررسی آنها نیز از حوصله این مقاله خارج است. این کلاس پیکربندیهای اساسی Identity را انجام میدهد. کامپوننتهای پیکربندی و کدهای کمکی دیگری نیز وجود دارند که در کلاس IdentityConfig.cs تعریف شده اند.
پیش از آنکه فایل IdentityConfig.cs را بررسی کنیم، بهتر است نگاهی به کلاس ApplicationUser بیاندازیم که در پوشه Models قرار گرفته است.
کلاس جدید ApplicationUser در Identity 2.0
اگر با نسخه 1.0 این فریم ورک اپلیکیشنی ساخته باشید، ممکن است متوجه شده باشید که کلاس پایه IdentityUser محدود و شاید ناکافی باشد. در نسخه قبلی، این فریم ورک پیاده سازی IdentityUser را تا حد امکان ساده نگاه داشته بود تا اطلاعات پروفایل کاربران را معرفی کند.
public class IdentityUser : IUser { public IdentityUser(); public IdentityUser(string userName); public virtual string Id { get; set; } public virtual string UserName { get; set; } public virtual ICollection<IdentityUserRole> Roles { get; } public virtual ICollection<IdentityUserClaim> Claims { get; } public virtual ICollection<IdentityUserLogin> Logins { get; } public virtual string PasswordHash { get; set; } public virtual string SecurityStamp { get; set; } }
اگر از نسخه Identity 1.0 استفاده کرده باشید و مطالعاتی هم در این زمینه داشته باشید، میدانید که توسعه کلاس کاربران بسیار ساده است. مثلا برای افزودن فیلد آدرس ایمیل و اطلاعات دیگر کافی بود کلاس ApplicationUser را ویرایش کنیم و از آنجا که این فریم ورک مبتنی بر EF Code-first است بروز رسانی دیتابیس و مابقی اپلیکیشن کار چندان مشکلی نخواهد بود.
با ظهور نسخه Identity 2.0 نیاز به برخی از این سفارشی سازیها از بین رفته است. گرچه هنوز هم میتوانید بسادگی مانند گذشته کلاس ApplicationUser را توسعه و گسترش دهید، تیم ASP.NET تغییراتی بوجود آورده اند تا نیازهای رایج توسعه دهندگان را پاسخگو باشد.
اگر به کد کلاسهای مربوطه دقت کنید خواهید دید که کلاس ApplicationUser همچنان از کلاس پایه IdentityUser ارث بری میکند، اما این کلاس پایه پیچیدهتر شده است. کلاس ApplicationUser در پوشه Models و در فایلی بنام IdentityModels.cs تعریف شده است. همانطور که میبینید تعاریف خود این کلاس بسیار ساده است.
public class ApplicationUser : IdentityUser { public async Task<ClaimsIdentity> GenerateUserIdentityAsync( UserManager<ApplicationUser> manager) { // Note the authenticationType must match the one // defined in CookieAuthenticationOptions.AuthenticationType var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); // Add custom user claims here return userIdentity; } }
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey> where TLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey> where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey> where TClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey> { public IdentityUser(); // Used to record failures for the purposes of lockout public virtual int AccessFailedCount { get; set; } // Navigation property for user claims public virtual ICollection<TClaim> Claims { get; } // Email public virtual string Email { get; set; } // True if the email is confirmed, default is false public virtual bool EmailConfirmed { get; set; } // User ID (Primary Key) public virtual TKey Id { get; set; } // Is lockout enabled for this user public virtual bool LockoutEnabled { get; set; } // DateTime in UTC when lockout ends, any // time in the past is considered not locked out. public virtual DateTime? LockoutEndDateUtc { get; set; } // Navigation property for user logins public virtual ICollection<TLogin> Logins { get; } // The salted/hashed form of the user password public virtual string PasswordHash { get; set; } // PhoneNumber for the user public virtual string PhoneNumber { get; set; } // True if the phone number is confirmed, default is false public virtual bool PhoneNumberConfirmed { get; set; } // Navigation property for user roles public virtual ICollection<TRole> Roles { get; } // A random value that should change whenever a users // credentials have changed (password changed, login removed) public virtual string SecurityStamp { get; set; } // Is two factor enabled for the user public virtual bool TwoFactorEnabled { get; set; } // User name public virtual string UserName { get; set; } }
اما از همه چیز مهمتر امضا (Signature)ی خود کلاس است. این آرگومانهای جنریک چه هستند؟ به امضای این کلاس دقت کنید.
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey> where TLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey> where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey> where TClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey>
public virtual TKey Id { get; set; }
public virtual ICollection<TRole> Roles { get; }
در نسخه Identity 1.0 کلاس IdentityUserRole بصورت زیر تعریف شده بود.
public class IdentityUserRole { public IdentityUserRole(); public virtual IdentityRole Role { get; set; } public virtual string RoleId { get; set; } public virtual IdentityUser User { get; set; } public virtual string UserId { get; set; } }
public class IdentityUserRole<TKey> { public IdentityUserRole(); public virtual TKey RoleId { get; set; } public virtual TKey UserId { get; set; } }
بررسی پیاده سازی جدید IdentityUser از حوصله این مقاله خارج است. فعلا همین قدر بدانید که گرچه تعاریف پایه کلاس کاربران پیچیدهتر شده است، اما انعطاف پذیری بسیار خوبی بدست آمده که شایان اهمیت فراوانی است.
از آنجا که کلاس ApplicationUser از IdentityUser ارث بری میکند، تمام خواص و تعاریف این کلاس پایه در ApplicationUser قابل دسترسی هستند.
کامپوننتهای پیکربندی Identity 2.0 و کدهای کمکی
گرچه متد ()ConfigAuth در کلاس Startup، محلی است که پیکربندی Identity در زمان اجرا صورت میپذیرد، اما در واقع کامپوننتهای موجود در فایل IdentityConfig.cs هستند که اکثر قابلیتهای Identity 2.0 را پیکربندی کرده و نحوه رفتار آنها در اپلیکیشن ما را کنترل میکنند.
اگر محتوای فایل IdentityConfig.cs را بررسی کنید خواهید دید که کلاسهای متعددی در این فایل تعریف شده اند. میتوان تک تک این کلاسها را به فایلهای مجزایی منتقل کرد، اما برای مثال جاری کدها را بهمین صورت رها کرده و نگاهی اجمالی به آنها خواهیم داشت. بهرحال در حال حاظر تمام این کلاسها در فضای نام ApplicationName.Models قرار دارند.
Application User Manager و Application Role Manager
اولین چیزی که در این فایل به آنها بر میخوریم دو کلاس ApplicationUserManager و ApplicationRoleManager هستند. آماده باشید، مقدار زیادی کد با انواع داده جنریک در پیش روست!
public class ApplicationUserManager : UserManager<ApplicationUser> { public ApplicationUserManager(IUserStore<ApplicationUser> store) : base(store) { } public static ApplicationUserManager Create( IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager( new UserStore<ApplicationUser>( context.Get<ApplicationDbContext>())); // Configure validation logic for usernames manager.UserValidator = new UserValidator<ApplicationUser>(manager) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Configure user lockout defaults manager.UserLockoutEnabledByDefault = true; manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); manager.MaxFailedAccessAttemptsBeforeLockout = 5; // Register two factor authentication providers. This application uses // Phone and Emails as a step of receiving a code for verifying // the user You can write your own provider and plug in here. manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser> { MessageFormat = "Your security code is: {0}" }); manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); manager.EmailService = new EmailService(); manager.SmsService = new SmsService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>( dataProtectionProvider.Create("ASP.NET Identity")); } return manager; } public virtual async Task<IdentityResult> AddUserToRolesAsync( string userId, IList<string> roles) { var userRoleStore = (IUserRoleStore<ApplicationUser, string>)Store; var user = await FindByIdAsync(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException("Invalid user Id"); } var userRoles = await userRoleStore .GetRolesAsync(user) .ConfigureAwait(false); // Add user to each role using UserRoleStore foreach (var role in roles.Where(role => !userRoles.Contains(role))) { await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false); } // Call update once when all roles are added return await UpdateAsync(user).ConfigureAwait(false); } public virtual async Task<IdentityResult> RemoveUserFromRolesAsync( string userId, IList<string> roles) { var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store; var user = await FindByIdAsync(userId).ConfigureAwait(false); if (user == null) { throw new InvalidOperationException("Invalid user Id"); } var userRoles = await userRoleStore .GetRolesAsync(user) .ConfigureAwait(false); // Remove user to each role using UserRoleStore foreach (var role in roles.Where(userRoles.Contains)) { await userRoleStore .RemoveFromRoleAsync(user, role) .ConfigureAwait(false); } // Call update once when all roles are removed return await UpdateAsync(user).ConfigureAwait(false); } }
مورد حائز اهمیت بعدی در متد ()Create فراخوانی ()<context.Get<ApplicationDBContext است. بیاد بیاورید که پیشتر نگاهی به متد ()ConfigAuth داشتیم که چند فراخوانی CreatePerOwinContext داشت که توسط آنها Callback هایی را رجیستر میکردیم. فراخوانی متد ()<context.Get<ApplicationDBContext این Callbackها را صدا میزند، که در اینجا فراخوانی متد استاتیک ()ApplicationDbContext.Create خواهد بود. در ادامه بیشتر درباره این قسمت خواهید خواهند.
اگر دقت کنید میبینید که احراز هویت، تعیین سطوح دسترسی و تنظیمات مدیریتی و مقادیر پیش فرض آنها در متد ()Create انجام میشوند و سپس وهله ای از نوع خود کلاس ApplicationUserManager بازگشت داده میشود. همچنین سرویسهای احراز هویت دو مرحله ای نیز در همین مرحله پیکربندی میشوند. اکثر پیکربندیها و تنظیمات نیازی به توضیح ندارند و قابل درک هستند. اما احراز هویت دو مرحله ای نیاز به بررسی عمیقتری دارد. در ادامه به این قسمت خواهیم پرداخت. اما پیش از آن نگاهی به کلاس ApplicationRoleManager بیاندازیم.
public class ApplicationRoleManager : RoleManager<IdentityRole> { public ApplicationRoleManager(IRoleStore<IdentityRole,string> roleStore) : base(roleStore) { } public static ApplicationRoleManager Create( IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context) { var manager = new ApplicationRoleManager( new RoleStore<IdentityRole>( context.Get<ApplicationDbContext>())); return manager; } }
سرویسهای ایمیل و پیامک برای احراز هویت دو مرحله ای و تایید حسابهای کاربری
دو کلاس دیگری که در فایل IdentityConfig.cs وجود دارند کلاسهای EmailService و SmsService هستند. بصورت پیش فرض این کلاسها تنها یک wrapper هستند که میتوانید با توسعه آنها سرویسهای مورد نیاز برای احراز هویت دو مرحله ای و تایید حسابهای کاربری را بسازید.
public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Plug in your email service here to send an email. return Task.FromResult(0); } }
public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Plug in your sms service here to send a text message. return Task.FromResult(0); } }
// Register two factor authentication providers. This application uses // Phone and Emails as a step of receiving a code for verifying // the user You can write your own provider and plug in here. manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser> { MessageFormat = "Your security code is: {0}" }); manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); manager.EmailService = new EmailService(); manager.SmsService = new SmsService();
کلاس کمکی SignIn
هنگام توسعه پروژه مثال Identity، تیم توسعه دهندگان کلاسی کمکی برای ما ساختهاند که فرامین عمومی احراز هویت کاربران و ورود آنها به اپلیکیشن را توسط یک API ساده فراهم میسازد. برای آشنایی با نحوه استفاده از این متدها میتوانیم به کنترلر AccountController در پوشه Controllers مراجعه کنیم. اما پیش از آن بگذارید نگاهی به خود کلاس SignInHelper داشته باشیم.
public class SignInHelper { public SignInHelper( ApplicationUserManager userManager, IAuthenticationManager authManager) { UserManager = userManager; AuthenticationManager = authManager; } public ApplicationUserManager UserManager { get; private set; } public IAuthenticationManager AuthenticationManager { get; private set; } public async Task SignInAsync( ApplicationUser user, bool isPersistent, bool rememberBrowser) { // Clear any partial cookies from external or two factor partial sign ins AuthenticationManager.SignOut( DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie); var userIdentity = await user.GenerateUserIdentityAsync(UserManager); if (rememberBrowser) { var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id); AuthenticationManager.SignIn( new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity); } else { AuthenticationManager.SignIn( new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity); } } public async Task<bool> SendTwoFactorCode(string provider) { var userId = await GetVerifiedUserIdAsync(); if (userId == null) { return false; } var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider); // See IdentityConfig.cs to plug in Email/SMS services to actually send the code await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token); return true; } public async Task<string> GetVerifiedUserIdAsync() { var result = await AuthenticationManager.AuthenticateAsync( DefaultAuthenticationTypes.TwoFactorCookie); if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserId())) { return result.Identity.GetUserId(); } return null; } public async Task<bool> HasBeenVerified() { return await GetVerifiedUserIdAsync() != null; } public async Task<SignInStatus> TwoFactorSignIn( string provider, string code, bool isPersistent, bool rememberBrowser) { var userId = await GetVerifiedUserIdAsync(); if (userId == null) { return SignInStatus.Failure; } var user = await UserManager.FindByIdAsync(userId); if (user == null) { return SignInStatus.Failure; } if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code)) { // When token is verified correctly, clear the access failed // count used for lockout await UserManager.ResetAccessFailedCountAsync(user.Id); await SignInAsync(user, isPersistent, rememberBrowser); return SignInStatus.Success; } // If the token is incorrect, record the failure which // also may cause the user to be locked out await UserManager.AccessFailedAsync(user.Id); return SignInStatus.Failure; } public async Task<SignInStatus> ExternalSignIn( ExternalLoginInfo loginInfo, bool isPersistent) { var user = await UserManager.FindAsync(loginInfo.Login); if (user == null) { return SignInStatus.Failure; } if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } return await SignInOrTwoFactor(user, isPersistent); } private async Task<SignInStatus> SignInOrTwoFactor( ApplicationUser user, bool isPersistent) { if (await UserManager.GetTwoFactorEnabledAsync(user.Id) && !await AuthenticationManager.TwoFactorBrowserRememberedAsync(user.Id)) { var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id)); AuthenticationManager.SignIn(identity); return SignInStatus.RequiresTwoFactorAuthentication; } await SignInAsync(user, isPersistent, false); return SignInStatus.Success; } public async Task<SignInStatus> PasswordSignIn( string userName, string password, bool isPersistent, bool shouldLockout) { var user = await UserManager.FindByNameAsync(userName); if (user == null) { return SignInStatus.Failure; } if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } if (await UserManager.CheckPasswordAsync(user, password)) { return await SignInOrTwoFactor(user, isPersistent); } if (shouldLockout) { // If lockout is requested, increment access failed // count which might lock out the user await UserManager.AccessFailedAsync(user.Id); if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } } return SignInStatus.Failure; } }
این متدها ویژگیهای جدیدی که در Identity 2.0 عرضه شده اند را در بر میگیرند. متد آشنایی بنام ()SignInAsync را میبینیم، و متدهای دیگری که مربوط به احراز هویت دو مرحله ای و external log-ins میشوند. اگر به متدها دقت کنید خواهید دید که برای ورود کاربران به اپلیکیشن کارهای بیشتری نسبت به نسخه پیشین انجام میشود.
بعنوان مثال متد Login در کنترلر AccountController را باز کنید تا نحوه مدیریت احراز هویت در Identity 2.0 را ببینید.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } // This doen't count login failures towards lockout only two factor authentication // To enable password failures to trigger lockout, change to shouldLockout: true var result = await SignInHelper.PasswordSignIn( model.Email, model.Password, model.RememberMe, shouldLockout: false); switch (result) { case SignInStatus.Success: return RedirectToLocal(returnUrl); case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresTwoFactorAuthentication: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl }); case SignInStatus.Failure: default: ModelState.AddModelError("", "Invalid login attempt."); return View(model); } }
مقایسه Sign-in با نسخه Identity 1.0
در نسخه 1.0 این فریم ورک، ورود کاربران به اپلیکیشن مانند لیست زیر انجام میشد. اگر متد Login در کنترلر AccountController را باز کنید چنین قطعه کدی را میبینید.
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (ModelState.IsValid) { var user = await UserManager.FindAsync(model.UserName, model.Password); if (user != null) { await SignInAsync(user, model.RememberMe); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError("", "Invalid username or password."); } } // If we got this far, something failed, redisplay form return View(model); }
private async Task SignInAsync(ApplicationUser user, bool isPersistent) { AuthenticationManager.SignOut( DefaultAuthenticationTypes.ExternalCookie); var identity = await UserManager.CreateIdentityAsync( user, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn( new AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
ApplicationDbContext
اگر از نسخه پیشین Identity در اپلیکیشنهای ASP.NET MVC استفاده کرده باشید با کلاس ApplicationDbContext آشنا هستید. این کلاس پیاده سازی پیش فرض EF فریم ورک است، که اپلیکیشن شما توسط آن دادههای مربوط به Identity را ذخیره و بازیابی میکند.
در پروژه مثال ها، تیم Identity این کلاس را بطور متفاوتی نسبت به نسخه 1.0 پیکربندی کرده اند. اگر فایل IdentityModels.cs را باز کنید تعاریف کلاس ApplicationDbContext را مانند لیست زیر خواهید یافت.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } static ApplicationDbContext() { // Set the database intializer which is run once during application start // This seeds the database with admin user credentials and admin role Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer()); } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> { protected override void Seed(ApplicationDbContext context) { InitializeIdentityForEF(context); base.Seed(context); } public static void InitializeIdentityForEF(ApplicationDbContext db) { var userManager = HttpContext .Current.GetOwinContext() .GetUserManager<ApplicationUserManager>(); var roleManager = HttpContext.Current .GetOwinContext() .Get<ApplicationRoleManager>(); const string name = "admin@admin.com"; const string password = "Admin@123456"; const string roleName = "Admin"; //Create Role Admin if it does not exist var role = roleManager.FindByName(roleName); if (role == null) { role = new IdentityRole(roleName); var roleresult = roleManager.Create(role); } var user = userManager.FindByName(name); if (user == null) { user = new ApplicationUser { UserName = name, Email = name }; var result = userManager.Create(user, password); result = userManager.SetLockoutEnabled(user.Id, false); } // Add user admin to Role Admin if not already added var rolesForUser = userManager.GetRoles(user.Id); if (!rolesForUser.Contains(role.Name)) { var result = userManager.AddToRole(user.Id, role.Name); } } }
نکته حائز اهمیت دیگر متد ()InitializeIdentityForEF است. این متد کاری مشابه متد ()Seed انجام میدهد که هنگام استفاده از مهاجرتها (Migrations) از آن استفاده میکنیم. در این متد میتوانید رکوردهای اولیه ای را در دیتابیس ثبت کنید. همانطور که مشاهده میکنید در قطعه کد بالا نقشی مدیریتی بنام Admin ایجاد شده و کاربر جدیدی با اطلاعاتی پیش فرض ساخته میشود که در آخر به این نقش منتسب میگردد. با انجام این مراحل، پس از اجرای اولیه اپلیکیشن کاربری با سطح دسترسی مدیر در اختیار خواهیم داشت که برای تست اپلیکیشن بسیار مفید خواهد بود.
در این مقاله نگاهی اجمالی به Identity 2.0 در پروژههای ASP.NET MVC داشتیم. کامپوننتهای مختلف فریم ورک و نحوه پیکربندی آنها را بررسی کردیم و با تغییرات و قابلیتهای جدید به اختصار آشنا شدیم. در مقالات بعدی بررسی هایی عمیقتر خواهیم داشت و با نحوه استفاده و پیاده سازی قسمتهای مختلف این فریم ورک آشنا خواهیم شد.
مطالعه بیشتر
USE tempdb GO CREATE TABLE Customers1 ( ID INT IDENTITY,-- ID INT IDENTITY(1,1) Name NVARCHAR(100), [Address] NVARCHAR(200) ) GO
INSERT INTO Customers1 (Name,[Address]) VALUES (N'مسعود',N'میانه'), (N'فرید',N'میانه'), (N'احمد',N'میانه') GO SELECT * FROM Customers1
USE tempdb GO CREATE TABLE Customers2 ( ID INT IDENTITY(100,2), Name NVARCHAR(100), [Address] NVARCHAR(200) ) GO
INSERT INTO Customers2 (Name,[Address]) VALUES (N'مسعود',N'میانه'), (N'فرید',N'میانه'), (N'احمد',N'میانه') GO SELECT * FROM Customers2
- تنظیمات اولیه ASP.NET Identity 2.1 با Web API
- ایجاد Account Confirmation به وسیله Identity به همراه تنظیمات policy برای user name و password
- توسعه OAuth Json Web Token Authentication به کمک Web API و Identity
- ایجاد یک سیستم Role Based و تایید صلاحیتهای مربوط به آن
- توسعه Web API Claims Authorization در Identity 2.1
- توسعه بخش front-end با AngularJs
تنظیمات اولیه ASP.NET Identity 2.1 با Web API
1. تنظیمات ASP.Net Identity 2.1
1-1. ساخت یک پروژه Web API
در ابتدا ما یک empty solution را با نام "AspNetIdentity" همانند شکل مقابل میسازیم.
یک ASP.NET Web Application با نام "AspNetIdentity.WebApi" را به این solution اضافه مینماییم. در بخش select template ما empty template را انتخاب میکنیم. همچنین در قسمت add folders and core references for: نیز هیچیک از گزینهها را انتخاب نمیکنیم.
1-2. نصب packageهای مورد نیاز از Nuget
در زیر packageهای مورد نیاز برای ASP.NET Web API و Owin را مشاهده میکنید. همچنین packageهای مربوط به ASP.Net Identity 2.1 نیز در زیر قرار داده شدهاند.
Install-Package Microsoft.AspNet.Identity.Owin -Version 2.1.0 Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.1.0 Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0 Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.2 Install-Package Microsoft.Owin.Security.OAuth -Version 3.0.0 Install-Package Microsoft.Owin.Cors -Version 3.0.0
1-3. اضافه کردن user class و database context
حال که تمامی پکیجهای مورد نیاز را به پروژه خود اضافه نمودیم، قصد داریم تا اولین کلاس EF را با نام "ApplicationUser" به پروژه اضافه کنیم. این کلاس، کاربری را که قصد ثبت نام در membership system، دارد را نمایش میدهد. برای این کار ما یک کلاس جدید را به نام "ApplicationUser" میسازیم و کلاس "Microsoft.AspNet.Identity.EntityFramework.IdentityUser" را در آن به ارث میبریم.
برای این کار ما یک پوشهی جدید را در برنامه با نام "Infrastructure" میسازیم و درون آن کلاس ذکر شده را اضافه میکنیم:
public class ApplicationUser : IdentityUser { [Required] [MaxLength(100)] public string FirstName { get; set; } [Required] [MaxLength(100)] public string LastName { get; set; } [Required] public byte Level { get; set; } [Required] public DateTime JoinDate { get; set; } }
پس از افزودن کلاس User، نوبت به اضافه نمودن Db Context است. این کلاس وظیفهی ارتباط با پایگاه داده را بر عهده دارد. ما یک کلاس جدید را با نام ApplicationDbContext، به پوشهی Infrastructure اضافه مینماییم. کد مربوط به این کلاس به صورت زیر است:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { Configuration.ProxyCreationEnabled = false; Configuration.LazyLoadingEnabled = false; } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }
همانطور که ملاحظه میکنید این کلاس از IdentityDbContext ارث بری نموده است. این کلاس یک نسخهی جدیدتر از DbContext است که تمامی نگاشتهای Entity Framework Code First را انجام میدهد. در ادامه ما یک Connection String را با نام DefaultConnection در فایل web.config اضافه مینماییم.
همچنین متد static ایی را با نام Create که در تکه کد فوق از سوی Owin Startup class فراخوانی میگردد که در ادامه به شرح آن نیز خواهیم پرداخت.
ConnectionString قرار داده شده در فایل web.config در قسمت زیر قرار داده شده است:
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=AspNetIdentity;Integrated Security=SSPI;" providerName="System.Data.SqlClient" /> </connectionStrings>
قدم 4: ساخت پایگاه داده و فعال سازی DB Migration
حال ما باید EF CodeFirst Migration را برای آپدیت کردن دیتابیس، بجای دوباره ساختن آن به ازای هر تغییری، فعال نماییم. برای این کار بایستی در قسمت NuGet Package Manager Console عبارت زیر را وارد نماییم:
enable-migrations add-migration InitialCreate
اگر تا به اینجای کار تمامی مراحل به درستی صورت گرفته باشند، پوشهی Migration با موفقیت ساخته میشود. همچنین پایگاه داده متشکل از جداول مورد نیاز برای سیستم Identity نیز ایجاد میشود. برای اطلاعات بیشتر میتوانید مقالات مرتبط با Code First را مطالعه نمایید. ساختار جداول ما باید به صورت زیر باشد:
در بخش بعدی، کلاسهای مربوط به UserManager را ایجاد خواهیم کرد و پس از آن فایل Startup Owin را برای مدیریت کاربران، تشریح میکنیم.
اگه توی پروژه ASP.NET Core ایی تون از MongoDb استفاده میکنین و میخواین از سیستم احراز هویت Identity روش پیاده کنین، این کتابخونه کار یکپارچه سازیش رو براتون انجام میده
کتابخانههای زیادی برای پشتیبانی از MongoDb در Identity وجود دارند که من همشون رو بررسی کردم و این بهترینشون و کاملترینشون بود (بعدشم این یکی)
A MongoDb UserStore and RoleStore adapter for Microsoft.AspNetCore.Identity 2.2. Allows you to use MongoDb instead of SQL server with Microsoft.AspNetCore.Identity 2.2