اشتراکها
تفاوت Using Directives و Using Block
اشتراکها
راه اندازی ASP.NET 5 در Ubuntu
پروژهی ASP.NET Identity که نسل جدید سیستم Authentication و Authorization مخصوص ASP.NET است، دارای دو سری مثال رسمی است:
الف) مثالهای کدپلکس
ب) مثال نیوگت
در ادامه قصد داریم مثال نیوگت آنرا که مثال کاملی است از نحوهی استفاده از ASP.NET Identity در ASP.NET MVC، جهت اعمال الگوی واحد کار و تزریق وابستگیها، بازنویسی کنیم.
پیشنیازها
- برای درک مطلب جاری نیاز است ابتدا دورهی مرتبطی را در سایت مطالعه کنید و همچنین با نحوهی پیاده سازی الگوی واحد کار در EF Code First آشنا باشید.
- به علاوه فرض بر این است که یک پروژهی خالی ASP.NET MVC 5 را نیز آغاز کردهاید و توسط کنسول پاور شل نیوگت، فایلهای مثال Microsoft.AspNet.Identity.Samples را به آن افزودهاید:
ساختار پروژهی تکمیلی
همانند مطلب پیاده سازی الگوی واحد کار در EF Code First، این پروژهی جدید را با چهار اسمبلی class library دیگر به نامهای
AspNetIdentityDependencyInjectionSample.DataLayer
AspNetIdentityDependencyInjectionSample.DomainClasses
AspNetIdentityDependencyInjectionSample.IocConfig
AspNetIdentityDependencyInjectionSample.ServiceLayer
تکمیل میکنیم.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DomainClasses
مثال Microsoft.AspNet.Identity.Samples بر مبنای primary key از نوع string است. برای نمونه کلاس کاربران آنرا به نام ApplicationUser در فایل Models\IdentityModels.cs میتوانید مشاهده کنید. در مطلب جاری، این نوع پیش فرض، به نوع متداول int تغییر خواهد یافت. به همین جهت نیاز است کلاسهای ذیل را به پروژهی DomainClasses اضافه کرد:
در اینجا نحوهی تغییر primary key از نوع string را به نوع int، مشاهده میکنید. این تغییر نیاز به اعمال به کلاسهای کاربران و همچنین نقشهای آنها نیز دارد. به همین جهت صرفا تغییر کلاس ابتدایی ApplicationUser کافی نیست و باید کلاسهای فوق را نیز اضافه کرد و تغییر داد.
بدیهی است در اینجا کلاس پایه کاربران را میتوان سفارشی سازی کرد و خواص دیگری را نیز به آن افزود. برای مثال در اینجا یک کلاس جدید آدرس تعریف شدهاست که ارجاعی از آن در کلاس کاربران نیز قابل مشاهده است.
سایر کلاسهای مدلهای اصلی برنامه که جداول بانک اطلاعاتی را تشکیل خواهند داد نیز در آینده به همین اسمبلی DomainClasses اضافه میشوند.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DataLayer جهت اعمال الگوی واحد کار
اگر به همان فایل Models\IdentityModels.cs ابتدایی پروژه که اکنون کلاس ApplicationUser آنرا به پروژهی DomainClasses منتقل کردهایم، مجددا مراجعه کنید، کلاس DbContext مخصوص ASP.NET Identity نیز در آن تعریف شدهاست:
این کلاس را به پروژهی DataLayer منتقل میکنیم و از آن به عنوان DbContext اصلی برنامه استفاده خواهیم کرد. بنابراین دیگر نیازی نیست چندین DbContext در برنامه داشته باشیم. IdentityDbContext، در اصل از DbContext مشتق شدهاست.
اینترفیس IUnitOfWork برنامه، در پروژهی DataLayer چنین شکلی را دارد که نمونهای از آنرا در مطلب آشنایی با نحوهی پیاده سازی الگوی واحد کار در EF Code First، پیشتر ملاحظه کردهاید.
اکنون کلاس ApplicationDbContext منتقل شده به DataLayer یک چنین امضایی را خواهد یافت:
تعریف آن باید جهت اعمال کلاسهای سفارشی سازی شدهی کاربران و نقشهای آنها برای استفاده از primary key از نوع int به شکل فوق، تغییر یابد. همچنین در انتهای آن مانند قبل، IUnitOfWork نیز ذکر شدهاست. پیاده سازی کامل این کلاس را از پروژهی پیوست انتهای بحث میتوانید دریافت کنید.
کار کردن با این کلاس، هیچ تفاوتی با DbContextهای متداول EF Code First ندارد و تمام اصول آنها یکی است.
در ادامه اگر به فایل App_Start\IdentityConfig.cs مراجعه کنید، کلاس ذیل در آن قابل مشاهدهاست:
نیازی به این کلاس به این شکل نیست. آنرا حذف کنید و در پروژهی DataLayer، کلاس جدید ذیل را اضافه نمائید:
در این مثال، بحث migrations به حالت خودکار تنظیم شدهاست و تمام تغییرات در پروژهی DomainClasses را به صورت خودکار به بانک اطلاعاتی اعمال میکند. تا همینجا کار تنظیم DataLayer به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.ServiceLayer
در ادامه مابقی کلاسهای موجود در فایل App_Start\IdentityConfig.cs را به لایه سرویس برنامه منتقل خواهیم کرد. همچنین برای آنها یک سری اینترفیس جدید نیز تعریف میکنیم، تا تزریق وابستگیها به نحو صحیحی صورت گیرد. اگر به فایلهای کنترلر این مثال پیش فرض مراجعه کنید (پیش از تغییرات بحث جاری)، هرچند به نظر در کنترلرها، کلاسهای موجود در فایل App_Start\IdentityConfig.cs تزریق شدهاند، اما به دلیل عدم استفاده از اینترفیسها، وابستگی کاملی بین جزئیات پیاده سازی این کلاسها و نمونههای تزریق شده به کنترلرها وجود دارد و عملا معکوس سازی واقعی وابستگیها رخ ندادهاست. بنابراین نیاز است این مسایل را اصلاح کنیم.
الف) انتقال کلاس ApplicationUserManager به لایه سرویس برنامه
کلاس ApplicationUserManager فایل App_Start\IdentityConfig.c را به لایه سرویس منتقل میکنیم:
تغییراتی که در اینجا اعمال شدهاند، به شرح زیر میباشند:
- متد استاتیک Create این کلاس حذف و تعاریف آن به سازندهی کلاس منتقل شدهاند. به این ترتیب با هربار وهله سازی این کلاس توسط IoC Container به صورت خودکار این تنظیمات نیز به کلاس پایه UserManager اعمال میشوند.
- اگر به کلاس پایه UserManager دقت کنید، به آرگومانهای جنریک آن یک int هم اضافه شدهاست. این مورد جهت استفاده از primary key از نوع int ضروری است.
- در کلاس پایه UserManager تعدادی متد وجود دارند. تعاریف آنها را به اینترفیس IApplicationUserManager منتقل خواهیم کرد. نیازی هم به پیاده سازی این متدها در کلاس جدید ApplicationUserManager نیست؛ زیرا کلاس پایه UserManager پیشتر آنها را پیاده سازی کردهاست. به این ترتیب میتوان به یک تزریق وابستگی واقعی و بدون وابستگی به پیاده سازی خاص UserManager رسید. کنترلری که با IApplicationUserManager بجای ApplicationUserManager کار میکند، قابلیت تعویض پیاده سازی آنرا جهت آزمونهای واحد خواهد یافت.
- در کلاس اصلی ApplicationDbInitializer پیش فرض این مثال، متد Seed هم قابل مشاهدهاست. این متد را از کلاس جدید Configuration اضافه شده به DataLayer حذف کردهایم. از این جهت که در آن از متدهای کلاس ApplicationUserManager مستقیما استفاده شدهاست. متد Seed اکنون به کلاس جدید اضافه شده به لایه سرویس منتقل شده و در آغاز برنامه فراخوانی خواهد شد. DataLayer نباید وابستگی به لایه سرویس داشته باشد. لایه سرویس است که از امکانات DataLayer استفاده میکند.
- اگر به سازندهی کلاس جدید ApplicationUserManager دقت کنید، چند اینترفیس دیگر نیز به آن تزریق شدهاند. اینترفیس IApplicationRoleManager را ادامه تعریف خواهیم کرد. سایر اینترفیسهای تزریق شده مانند IUserStore، IDataProtectionProvider و IIdentityMessageService جزو تعاریف اصلی ASP.NET Identity بوده و نیازی به تعریف مجدد آنها نیست. فقط کلاسهای EmailService و SmsService فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل کردهایم. این کلاسها بر اساس تنظیمات IoC Container مورد استفاده، در اینجا به صورت خودکار ترزیق خواهند شد. حالت پیش فرض آن، وهله سازی مستقیم است که مطابق کدهای فوق به حالت تزریق وابستگیها بهبود یافتهاست.
ب) انتقال کلاس ApplicationSignInManager به لایه سرویس برنامه
کلاس ApplicationSignInManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل میکنیم.
در اینجا نیز اینترفیس جدید IApplicationSignInManager را برای مخفی سازی پیاده سازی کلاس پایه توکار SignInManager، اضافه کردهایم. این اینترفیس دقیقا حاوی تعاریف متدهای کلاس پایه SignInManager است و نیازی به پیاده سازی مجدد در کلاس ApplicationSignInManager نخواهد داشت.
ج) انتقال کلاس ApplicationRoleManager به لایه سرویس برنامه
کلاس ApplicationRoleManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل خواهیم کرد:
روش کار نیز در اینجا همانند دو کلاس قبل است. اینترفیس جدید IApplicationRoleManager را که حاوی تعاریف متدهای کلاس پایه توکار RoleManager است، به لایه سرویس اضافه میکنیم. کنترلرهای برنامه با این اینترفیس بجای استفاده مستقیم از کلاس ApplicationRoleManager کار خواهند کرد.
تا اینجا کار تنظیمات لایه سرویس برنامه به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.IocConfig
پروژهی IocConfig جایی است که تنظیمات StructureMap را به آن منتقل کردهایم:
در اینجا نحوهی اتصال اینترفیسهای برنامه را به کلاسها و یا نمونههایی که آنها را میتوانند پیاده سازی کنند، مشاهده میکنید. برای مثال IUnitOfWork به ApplicationDbContext مرتبط شدهاست و یا دوبار تعاریف متناظر با DbContext را مشاهده میکنید. از این تعاریف به صورت توکار توسط ASP.NET Identity زمانیکه قرار است UserStore و RoleStore را وهله سازی کند، استفاده میشوند و ذکر آنها الزامی است.
در تعاریف فوق یک مورد را به فایل Startup.cs موکول کردهایم. برای مشخص سازی نمونهی پیاده سازی کنندهی IDataProtectionProvider نیاز است به IAppBuilder کلاس Startup برنامه دسترسی داشت. این کلاس آغازین Owin اکنون به نحو ذیل بازنویسی شدهاست و در آن، تنظیمات IDataProtectionProvider را به همراه وهله سازی CreatePerOwinContext مشاهده میکنید:
این تعاریف از فایل پیش فرض Startup.Auth.cs پوشهی App_Start دریافت و جهت کار با IoC Container برنامه، بازنویسی شدهاند.
تنظیمات برنامهی اصلی ASP.NET MVC، جهت اعمال تزریق وابستگیها
الف) ابتدا نیاز است فایل Global.asax.cs را به نحو ذیل بازنویسی کنیم:
در اینجا در متد setDbInitializer، نحوهی استفاده و تعریف فایل Configuration لایه Data را ملاحظه میکنید؛ به همراه متد آغاز بانک اطلاعاتی و اعمال تغییرات لازم به آن در ابتدای کار برنامه. همچنین ControllerFactory برنامه نیز به StructureMapControllerFactory تنظیم شدهاست تا کار تزریق وابستگیها به کنترلرهای برنامه به صورت خودکار میسر شود. در پایان کار هر درخواست نیز منابع Disposable رها میشوند.
ب) به پوشهی Models برنامه مراجعه کنید. در اینجا در هر کلاسی که Id از نوع string وجود داشت، باید تبدیل به نوع int شوند. چون primary key برنامه را به نوع int تغییر دادهایم. برای مثال کلاسهای EditUserViewModel و RoleViewModel باید تغییر کنند.
ج) اصلاح کنترلرهای برنامه جهت اعمال تزریق وابستگیها
اکنون اصلاح کنترلرها جهت اعمال تزریق وابستگیها سادهاست. در ادامه نحوهی تغییر امضای سازندههای این کنترلرها را جهت استفاده از اینترفیسهای جدید مشاهده میکنید:
پس از این تغییرات، فقط کافی است بجای خواص برای مثال RoleManager سابق از فیلدهای تزریق شده در کلاس، مثلا roleManager_ جدید استفاده کرد. امضای متدهای یکی است و تنها به یک search و replace نیاز دارد.
البته تعدادی اکشن متد نیز در اینجا وجود دارند که از string id استفاده میکنند. اینها را باید به int? Id تغییر داد تا با نوع primary key جدید مورد استفاده تطابق پیدا کنند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
AspNetIdentityDependencyInjectionSample
معادل این پروژه جهت ASP.NET Core Identity : «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیتهای پایه و DbContext برنامه »
الف) مثالهای کدپلکس
ب) مثال نیوگت
در ادامه قصد داریم مثال نیوگت آنرا که مثال کاملی است از نحوهی استفاده از ASP.NET Identity در ASP.NET MVC، جهت اعمال الگوی واحد کار و تزریق وابستگیها، بازنویسی کنیم.
پیشنیازها
- برای درک مطلب جاری نیاز است ابتدا دورهی مرتبطی را در سایت مطالعه کنید و همچنین با نحوهی پیاده سازی الگوی واحد کار در EF Code First آشنا باشید.
- به علاوه فرض بر این است که یک پروژهی خالی ASP.NET MVC 5 را نیز آغاز کردهاید و توسط کنسول پاور شل نیوگت، فایلهای مثال Microsoft.AspNet.Identity.Samples را به آن افزودهاید:
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre
ساختار پروژهی تکمیلی
همانند مطلب پیاده سازی الگوی واحد کار در EF Code First، این پروژهی جدید را با چهار اسمبلی class library دیگر به نامهای
AspNetIdentityDependencyInjectionSample.DataLayer
AspNetIdentityDependencyInjectionSample.DomainClasses
AspNetIdentityDependencyInjectionSample.IocConfig
AspNetIdentityDependencyInjectionSample.ServiceLayer
تکمیل میکنیم.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DomainClasses
مثال Microsoft.AspNet.Identity.Samples بر مبنای primary key از نوع string است. برای نمونه کلاس کاربران آنرا به نام ApplicationUser در فایل Models\IdentityModels.cs میتوانید مشاهده کنید. در مطلب جاری، این نوع پیش فرض، به نوع متداول int تغییر خواهد یافت. به همین جهت نیاز است کلاسهای ذیل را به پروژهی DomainClasses اضافه کرد:
using System.ComponentModel.DataAnnotations.Schema; using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim> { // سایر خواص اضافی در اینجا [ForeignKey("AddressId")] public virtual Address Address { get; set; } public int? AddressId { get; set; } } } using System.Collections.Generic; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string State { get; set; } public virtual ICollection<ApplicationUser> ApplicationUsers { set; get; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomRole : IdentityRole<int, CustomUserRole> { public CustomRole() { } public CustomRole(string name) { Name = name; } } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserClaim : IdentityUserClaim<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserLogin : IdentityUserLogin<int> { } } using Microsoft.AspNet.Identity.EntityFramework; namespace AspNetIdentityDependencyInjectionSample.DomainClasses { public class CustomUserRole : IdentityUserRole<int> { } }
بدیهی است در اینجا کلاس پایه کاربران را میتوان سفارشی سازی کرد و خواص دیگری را نیز به آن افزود. برای مثال در اینجا یک کلاس جدید آدرس تعریف شدهاست که ارجاعی از آن در کلاس کاربران نیز قابل مشاهده است.
سایر کلاسهای مدلهای اصلی برنامه که جداول بانک اطلاعاتی را تشکیل خواهند داد نیز در آینده به همین اسمبلی DomainClasses اضافه میشوند.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.DataLayer جهت اعمال الگوی واحد کار
اگر به همان فایل Models\IdentityModels.cs ابتدایی پروژه که اکنون کلاس ApplicationUser آنرا به پروژهی DomainClasses منتقل کردهایم، مجددا مراجعه کنید، کلاس DbContext مخصوص ASP.NET Identity نیز در آن تعریف شدهاست:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
اینترفیس IUnitOfWork برنامه، در پروژهی DataLayer چنین شکلی را دارد که نمونهای از آنرا در مطلب آشنایی با نحوهی پیاده سازی الگوی واحد کار در EF Code First، پیشتر ملاحظه کردهاید.
using System.Collections.Generic; using System.Data.Entity; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveAllChanges(); void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class; IList<T> GetRows<T>(string sql, params object[] parameters) where T : class; IEnumerable<TEntity> AddThisRange<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; void ForceDatabaseInitialize(); } }
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; } public DbSet<Address> Addresses { set; get; }
کار کردن با این کلاس، هیچ تفاوتی با DbContextهای متداول EF Code First ندارد و تمام اصول آنها یکی است.
در ادامه اگر به فایل App_Start\IdentityConfig.cs مراجعه کنید، کلاس ذیل در آن قابل مشاهدهاست:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
using System.Data.Entity.Migrations; namespace AspNetIdentityDependencyInjectionSample.DataLayer.Context { public class Configuration : DbMigrationsConfiguration<ApplicationDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } } }
ساختار پروژهی AspNetIdentityDependencyInjectionSample.ServiceLayer
در ادامه مابقی کلاسهای موجود در فایل App_Start\IdentityConfig.cs را به لایه سرویس برنامه منتقل خواهیم کرد. همچنین برای آنها یک سری اینترفیس جدید نیز تعریف میکنیم، تا تزریق وابستگیها به نحو صحیحی صورت گیرد. اگر به فایلهای کنترلر این مثال پیش فرض مراجعه کنید (پیش از تغییرات بحث جاری)، هرچند به نظر در کنترلرها، کلاسهای موجود در فایل App_Start\IdentityConfig.cs تزریق شدهاند، اما به دلیل عدم استفاده از اینترفیسها، وابستگی کاملی بین جزئیات پیاده سازی این کلاسها و نمونههای تزریق شده به کنترلرها وجود دارد و عملا معکوس سازی واقعی وابستگیها رخ ندادهاست. بنابراین نیاز است این مسایل را اصلاح کنیم.
الف) انتقال کلاس ApplicationUserManager به لایه سرویس برنامه
کلاس ApplicationUserManager فایل App_Start\IdentityConfig.c را به لایه سرویس منتقل میکنیم:
using System; using System.Security.Claims; using System.Threading.Tasks; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationUserManager : UserManager<ApplicationUser, int>, IApplicationUserManager { private readonly IDataProtectionProvider _dataProtectionProvider; private readonly IIdentityMessageService _emailService; private readonly IApplicationRoleManager _roleManager; private readonly IIdentityMessageService _smsService; private readonly IUserStore<ApplicationUser, int> _store; public ApplicationUserManager(IUserStore<ApplicationUser, int> store, IApplicationRoleManager roleManager, IDataProtectionProvider dataProtectionProvider, IIdentityMessageService smsService, IIdentityMessageService emailService) : base(store) { _store = store; _roleManager = roleManager; _dataProtectionProvider = dataProtectionProvider; _smsService = smsService; _emailService = emailService; createApplicationUserManager(); } public void SeedDatabase() { } private void createApplicationUserManager() { // Configure validation logic for usernames this.UserValidator = new UserValidator<ApplicationUser, int>(this) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords this.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Configure user lockout defaults this.UserLockoutEnabledByDefault = true; this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); this.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. this.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser, int> { MessageFormat = "Your security code is: {0}" }); this.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser, int> { Subject = "SecurityCode", BodyFormat = "Your security code is {0}" }); this.EmailService = _emailService; this.SmsService = _smsService; if (_dataProtectionProvider != null) { var dataProtector = _dataProtectionProvider.Create("ASP.NET Identity"); this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser, int>(dataProtector); } } } }
- متد استاتیک Create این کلاس حذف و تعاریف آن به سازندهی کلاس منتقل شدهاند. به این ترتیب با هربار وهله سازی این کلاس توسط IoC Container به صورت خودکار این تنظیمات نیز به کلاس پایه UserManager اعمال میشوند.
- اگر به کلاس پایه UserManager دقت کنید، به آرگومانهای جنریک آن یک int هم اضافه شدهاست. این مورد جهت استفاده از primary key از نوع int ضروری است.
- در کلاس پایه UserManager تعدادی متد وجود دارند. تعاریف آنها را به اینترفیس IApplicationUserManager منتقل خواهیم کرد. نیازی هم به پیاده سازی این متدها در کلاس جدید ApplicationUserManager نیست؛ زیرا کلاس پایه UserManager پیشتر آنها را پیاده سازی کردهاست. به این ترتیب میتوان به یک تزریق وابستگی واقعی و بدون وابستگی به پیاده سازی خاص UserManager رسید. کنترلری که با IApplicationUserManager بجای ApplicationUserManager کار میکند، قابلیت تعویض پیاده سازی آنرا جهت آزمونهای واحد خواهد یافت.
- در کلاس اصلی ApplicationDbInitializer پیش فرض این مثال، متد Seed هم قابل مشاهدهاست. این متد را از کلاس جدید Configuration اضافه شده به DataLayer حذف کردهایم. از این جهت که در آن از متدهای کلاس ApplicationUserManager مستقیما استفاده شدهاست. متد Seed اکنون به کلاس جدید اضافه شده به لایه سرویس منتقل شده و در آغاز برنامه فراخوانی خواهد شد. DataLayer نباید وابستگی به لایه سرویس داشته باشد. لایه سرویس است که از امکانات DataLayer استفاده میکند.
- اگر به سازندهی کلاس جدید ApplicationUserManager دقت کنید، چند اینترفیس دیگر نیز به آن تزریق شدهاند. اینترفیس IApplicationRoleManager را ادامه تعریف خواهیم کرد. سایر اینترفیسهای تزریق شده مانند IUserStore، IDataProtectionProvider و IIdentityMessageService جزو تعاریف اصلی ASP.NET Identity بوده و نیازی به تعریف مجدد آنها نیست. فقط کلاسهای EmailService و SmsService فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل کردهایم. این کلاسها بر اساس تنظیمات IoC Container مورد استفاده، در اینجا به صورت خودکار ترزیق خواهند شد. حالت پیش فرض آن، وهله سازی مستقیم است که مطابق کدهای فوق به حالت تزریق وابستگیها بهبود یافتهاست.
ب) انتقال کلاس ApplicationSignInManager به لایه سرویس برنامه
کلاس ApplicationSignInManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل میکنیم.
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationSignInManager : SignInManager<ApplicationUser, int>, IApplicationSignInManager { private readonly ApplicationUserManager _userManager; private readonly IAuthenticationManager _authenticationManager; public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } } }
ج) انتقال کلاس ApplicationRoleManager به لایه سرویس برنامه
کلاس ApplicationRoleManager فایل App_Start\IdentityConfig.c را نیز به لایه سرویس منتقل خواهیم کرد:
using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; namespace AspNetIdentityDependencyInjectionSample.ServiceLayer { public class ApplicationRoleManager : RoleManager<CustomRole, int>, IApplicationRoleManager { private readonly IRoleStore<CustomRole, int> _roleStore; public ApplicationRoleManager(IRoleStore<CustomRole, int> roleStore) : base(roleStore) { _roleStore = roleStore; } public CustomRole FindRoleByName(string roleName) { return this.FindByName(roleName); // RoleManagerExtensions } public IdentityResult CreateRole(CustomRole role) { return this.Create(role); // RoleManagerExtensions } } }
تا اینجا کار تنظیمات لایه سرویس برنامه به پایان میرسد.
ساختار پروژهی AspNetIdentityDependencyInjectionSample.IocConfig
پروژهی IocConfig جایی است که تنظیمات StructureMap را به آن منتقل کردهایم:
using System; using System.Data.Entity; using System.Threading; using System.Web; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.DomainClasses; using AspNetIdentityDependencyInjectionSample.ServiceLayer; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; using Microsoft.Owin.Security; using StructureMap; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample.IocConfig { public static class SmObjectFactory { private static readonly Lazy<Container> _containerBuilder = new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication); public static IContainer Container { get { return _containerBuilder.Value; } } private static Container defaultContainer() { return new Container(ioc => { ioc.For<IUnitOfWork>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationDbContext>(); ioc.For<ApplicationDbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<DbContext>().HybridHttpOrThreadLocalScoped().Use<ApplicationDbContext>(); ioc.For<IUserStore<ApplicationUser, int>>() .HybridHttpOrThreadLocalScoped() .Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin, CustomUserRole, CustomUserClaim>>(); ioc.For<IRoleStore<CustomRole, int>>() .HybridHttpOrThreadLocalScoped() .Use<RoleStore<CustomRole, int, CustomUserRole>>(); ioc.For<IAuthenticationManager>() .Use(() => HttpContext.Current.GetOwinContext().Authentication); ioc.For<IApplicationSignInManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationSignInManager>(); ioc.For<IApplicationUserManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationUserManager>(); ioc.For<IApplicationRoleManager>() .HybridHttpOrThreadLocalScoped() .Use<ApplicationRoleManager>(); ioc.For<IIdentityMessageService>().Use<SmsService>(); ioc.For<IIdentityMessageService>().Use<EmailService>(); ioc.For<ICustomRoleStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomRoleStore>(); ioc.For<ICustomUserStore>() .HybridHttpOrThreadLocalScoped() .Use<CustomUserStore>(); //config.For<IDataProtectionProvider>().Use(()=> app.GetDataProtectionProvider()); // In Startup class ioc.For<ICategoryService>().Use<EfCategoryService>(); ioc.For<IProductService>().Use<EfProductService>(); }); } } }
در تعاریف فوق یک مورد را به فایل Startup.cs موکول کردهایم. برای مشخص سازی نمونهی پیاده سازی کنندهی IDataProtectionProvider نیاز است به IAppBuilder کلاس Startup برنامه دسترسی داشت. این کلاس آغازین Owin اکنون به نحو ذیل بازنویسی شدهاست و در آن، تنظیمات IDataProtectionProvider را به همراه وهله سازی CreatePerOwinContext مشاهده میکنید:
using System; using AspNetIdentityDependencyInjectionSample.IocConfig; using AspNetIdentityDependencyInjectionSample.ServiceLayer.Contracts; using Microsoft.AspNet.Identity; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.DataProtection; using Owin; using StructureMap.Web; namespace AspNetIdentityDependencyInjectionSample { public class Startup { public void Configuration(IAppBuilder app) { configureAuth(app); } private static void configureAuth(IAppBuilder app) { SmObjectFactory.Container.Configure(config => { config.For<IDataProtectionProvider>() .HybridHttpOrThreadLocalScoped() .Use(()=> app.GetDataProtectionProvider()); }); SmObjectFactory.Container.GetInstance<IApplicationUserManager>().SeedDatabase(); // Configure the db context, user manager and role manager to use a single instance per request app.CreatePerOwinContext(() => SmObjectFactory.Container.GetInstance<IApplicationUserManager>()); // 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 = SmObjectFactory.Container.GetInstance<IApplicationUserManager>().OnValidateIdentity() } }); 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); } } }
تنظیمات برنامهی اصلی ASP.NET MVC، جهت اعمال تزریق وابستگیها
الف) ابتدا نیاز است فایل Global.asax.cs را به نحو ذیل بازنویسی کنیم:
using System; using System.Data.Entity; using System.Web; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using AspNetIdentityDependencyInjectionSample.DataLayer.Context; using AspNetIdentityDependencyInjectionSample.IocConfig; using StructureMap.Web.Pipeline; namespace AspNetIdentityDependencyInjectionSample { public class MvcApplication : HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); setDbInitializer(); //Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); } protected void Application_EndRequest(object sender, EventArgs e) { HttpContextLifecycle.DisposeAndClearAll(); } public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.RawUrl)); return SmObjectFactory.Container.GetInstance(controllerType) as Controller; } } private static void setDbInitializer() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<ApplicationDbContext, Configuration>()); SmObjectFactory.Container.GetInstance<IUnitOfWork>().ForceDatabaseInitialize(); } } }
ب) به پوشهی Models برنامه مراجعه کنید. در اینجا در هر کلاسی که Id از نوع string وجود داشت، باید تبدیل به نوع int شوند. چون primary key برنامه را به نوع int تغییر دادهایم. برای مثال کلاسهای EditUserViewModel و RoleViewModel باید تغییر کنند.
ج) اصلاح کنترلرهای برنامه جهت اعمال تزریق وابستگیها
اکنون اصلاح کنترلرها جهت اعمال تزریق وابستگیها سادهاست. در ادامه نحوهی تغییر امضای سازندههای این کنترلرها را جهت استفاده از اینترفیسهای جدید مشاهده میکنید:
[Authorize] public class AccountController : Controller { private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationSignInManager _signInManager; private readonly IApplicationUserManager _userManager; public AccountController(IApplicationUserManager userManager, IApplicationSignInManager signInManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _signInManager = signInManager; _authenticationManager = authenticationManager; } [Authorize] public class ManageController : Controller { // Used for XSRF protection when adding external logins private const string XsrfKey = "XsrfId"; private readonly IAuthenticationManager _authenticationManager; private readonly IApplicationUserManager _userManager; public ManageController(IApplicationUserManager userManager, IAuthenticationManager authenticationManager) { _userManager = userManager; _authenticationManager = authenticationManager; } [Authorize(Roles = "Admin")] public class RolesAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public RolesAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; } [Authorize(Roles = "Admin")] public class UsersAdminController : Controller { private readonly IApplicationRoleManager _roleManager; private readonly IApplicationUserManager _userManager; public UsersAdminController(IApplicationUserManager userManager, IApplicationRoleManager roleManager) { _userManager = userManager; _roleManager = roleManager; }
البته تعدادی اکشن متد نیز در اینجا وجود دارند که از string id استفاده میکنند. اینها را باید به int? Id تغییر داد تا با نوع primary key جدید مورد استفاده تطابق پیدا کنند.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
AspNetIdentityDependencyInjectionSample
معادل این پروژه جهت ASP.NET Core Identity : «سفارشی سازی ASP.NET Core Identity - قسمت اول - موجودیتهای پایه و DbContext برنامه »
معرفی IdentityServer 4
اگر استاندارد OpenID Connect را بررسی کنیم، از مجموعهای از دستورات و رهنمودها تشکیل شدهاست. بنابراین نیاز به کامپوننتی داریم که این استاندارد را پیاده سازی کرده باشد تا بتوان بر اساس آن یک Identity Provider را تشکیل داد و پیاده سازی مباحثی که در قسمت قبل بررسی شدند مانند توکنها، Flow، انواع کلاینتها، انواع Endpoints و غیره چیزی نیستند که به سادگی قابل انجام باشند. اینجا است که IdentityServer 4، به عنوان یک فریم ورک پیاده سازی کنندهی استانداردهای OAuth 2 و OpenID Connect مخصوص ASP.NET Core ارائه شدهاست. این فریم ورک توسط OpenID Foundation تائید شده و داری مجوز رسمی از آن است. همچنین جزئی از NET Foundation. نیز میباشد. به علاوه باید دقت داشت که این فریم ورک کاملا سورس باز است.
نصب و راه اندازی IdentityServer 4
همان مثال «قسمت دوم - ایجاد ساختار اولیهی مثال این سری» را در نظر بگیرید. داخل آن پوشههای جدید src\IDP\DNT.IDP را ایجاد میکنیم.
نام دلخواه DNT.IDP، به پوشهی جدیدی اشاره میکند که قصد داریم IDP خود را در آن برپا کنیم. نام آن را نیز در ادامهی نامهای پروژههای قبلی که با ImageGallery شروع شدهاند نیز انتخاب نکردهایم؛ از این جهت که یک IDP را قرار است برای بیش از یک برنامهی کلاینت مورد استفاده قرار دهیم. برای مثال میتوانید از نام شرکت خود برای نامگذاری این IDP استفاده کنید.
اکنون از طریق خط فرمان به پوشهی src\IDP\DNT.IDP وارد شده و دستور زیر را صادر کنید:
این دستور، یک پروژهی جدیدی را از نوع «ASP.NET Core Empty»، در این پوشه، بر اساس آخرین نگارش SDK نصب شدهی بر روی سیستم شما، ایجاد میکند. از این جهت نوع پروژه خالی درنظر گرفته شدهاست که قرار است توسط اجزای IdentityServer 4 به مرور تکمیل شود.
اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل Properties\launchSettings.json آن و تغییر شماره پورتهای پیشفرض آن است تا با سایر پروژههای وبی که تاکنون ایجاد کردهایم، تداخل نکند. برای مثال در اینجا شماره پورت SSL آنرا به 6001 تغییر دادهایم.
اکنون نوبت به افزودن میانافزار IdentityServer 4 به پروژهی خالی وب است. اینکار را نیز توسط اجرای دستور زیر در پوشهی src\IDP\DNT.IDP انجام میدهیم:
در ادامه نیاز است این میانافزار جدید را معرفی و تنظیم کرد. به همین جهت فایل Startup.cs پروژهی خالی وب را گشوده و به صورت زیر تکمیل میکنیم:
- متد الحاقی AddIdentityServer، کار ثبت و معرفی سرویسهای توکار IdentityServer را به سرویس توکار تزریق وابستگیهای ASP.NET Core انجام میدهد.
- متد الحاقی AddDeveloperSigningCredential کار تنظیمات کلید امضای دیجیتال توکنها را انجام میدهد. در نگارشهای قبلی IdentityServer، اینکار با معرفی یک مجوز امضاء کردن توکنها انجام میشد. اما در این نگارش دیگر نیازی به آن نیست. در طول توسعهی برنامه میتوان از نگارش Developer این مجوز استفاده کرد. البته در حین توزیع برنامه به محیط ارائهی نهایی، باید به یک مجوز واقعی تغییر پیدا کند.
تعریف کاربران، منابع و کلاینتها
مرحلهی بعدی تنظیمات میانافزار IdentityServer4، تعریف کاربران، منابع و کلاینتهای این IDP است. به همین جهت یک کلاس جدید را به نام Config، در ریشهی پروژه ایجاد و به صورت زیر تکمیل میکنیم:
توضیحات:
- این کلاس استاتیک، اطلاعاتی درون حافظهای را برای تکمیل دموی جاری ارائه میدهد.
- ابتدا در متد GetUsers، تعدادی کاربر آزمایشی اضافه شدهاند. کلاس TestUser در فضای نام IdentityServer4.Test قرار دارد. در کلاس TestUser، خاصیت SubjectId، بیانگر Id منحصربفرد هر کاربر در کل این IDP است. سپس نام کاربری، کلمهی عبور و تعدادی Claim برای هر کاربر تعریف شدهاند که بیانگر اطلاعاتی اضافی در مورد هر کدام از آنها هستند. برای مثال نام و نام خانوادگی جزو خواص کلاس TestUser نیستند؛ اما منعی هم برای تعریف آنها وجود ندارد. اینگونه اطلاعات اضافی را میتوان توسط Claims به سیستم اضافه کرد.
- بازگشت Claims توسط یک IDP مرتبط است به مفهوم Scopes. برای این منظور متد دیگری به نام GetIdentityResources تعریف شدهاست تا لیستی از IdentityResourceها را بازگشت دهد که در فضای نام IdentityServer4.Models قرار دارد. هر IdentityResource، به یک Scope که سبب دسترسی به اطلاعات Identity کاربران میشود، نگاشت خواهد شد. در اینجا چون از پروتکل OpenID Connect استفاده میکنیم، ذکر IdentityResources.OpenId اجباری است. به این ترتیب مطمئن خواهیم شد که SubjectId به سمت برنامهی کلاینت بازگشت داده میشود. برای بازگشت Claims نیز باید IdentityResources.Profile را به عنوان یک Scope دیگری مشخص کرد که در متد GetIdentityResources مشخص شدهاست.
- در آخر نیاز است کلاینتهای این IDP را نیز مشخص کنیم (در مورد مفهوم Clients در قسمت قبل بیشتر توضیح داده شد) که اینکار در متد GetClients انجام میشود. فعلا یک لیست خالی را بازگشت میدهیم و آنرا در قسمتهای بعدی تکمیل خواهیم کرد.
افزودن کاربران، منابع و کلاینتها به سیستم
پس از تعریف و تکمیل کلاس Config، برای معرفی آن به IDP، به کلاس آغازین برنامه مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
در اینجا لیست کاربران و اطلاعات آنها توسط متد AddTestUsers، لیست منابع و Scopes توسط متد AddInMemoryIdentityResources و لیست کلاینتها توسط متد AddInMemoryClients به تنظیمات IdentityServer اضافه شدهاند.
افزودن میان افزار IdentityServer به برنامه
پس از انجام تنظیمات مقدماتی سرویسهای برنامه، اکنون نوبت به افزودن میانافزار IdentityServer است که در کلاس آغازین برنامه به صورت زیر تعریف میشود:
آزمایش IDP
اکنون برای آزمایش IDP، به پوشهی src\IDP\DNT.IDP وارد شده و دستور dotnet run را اجرا کنید:
همانطور که ملاحظه میکنید، برنامهی IDP بر روی پورت 6001 قابل دسترسی است. برای آزمایش Web API آن، آدرس discovery endpoint این IDP را به صورت زیر در مرورگر وارد کنید:
در این تصویر، مفاهیمی را که در قسمت قبل بررسی کردیم مانند authorization_endpoint ،token_endpoint و غیره، مشاهده میکنید.
افزودن UI به IdentityServer
تا اینجا میانافزار IdentityServer را نصب و راه اندازی کردیم. در نگارشهای قبلی آن، UI به صورت پیشفرض جزئی از این سیستم بود. در این نگارش آنرا میتوان به صورت جداگانه دریافت و به برنامه اضافه کرد. برای این منظور به آدرس IdentityServer4.Quickstart.UI مراجعه کرده و همانطور که در readme آن ذکر شدهاست میتوان از یکی از دستورات زیر برای افزودن آن به پروژهی IDP استفاده کرد:
الف) در ویندوز از طریق کنسول پاورشل به پوشهی src\IDP\DNT.IDP وارد شده و سپس دستور زیر را وارد کنید:
ب) و یا درmacOS و یا Linux، دستور زیر را اجرا کنید:
یک نکته: در ویندوز اگر در نوار آدرس هر پوشه، عبارت cmd را وارد و enter کنید، کنسول خط فرمان ویندوز در همان پوشه باز خواهد شد. همچنین در اینجا از ورود عبارت powershell هم پشتیبانی میشود:
بنابراین در نوار آدرس پوشهی src\IDP\DNT.IDP، عبارت powershell را وارد کرده و سپس enter کنید. پس از آن دستور الف را وارد (copy/paste) و اجرا کنید.
به این ترتیب فایلهای IdentityServer4.Quickstart.UI به پروژهی IDP جاری اضافه میشوند.
- پس از آن اگر به پوشهی Views مراجعه کنید، برای نمونه ذیل پوشهی Account آن، Viewهای ورود و خروج به سیستم قابل مشاهده هستند.
- در پوشهی Quickstart آن، کدهای کامل کنترلرهای متناظر با این Viewها قرار دارند.
بنابراین اگر نیاز به سفارشی سازی این Viewها را داشته باشید، کدهای کامل کنترلرها و Viewهای آن هم اکنون در پروژهی IDP جاری در دسترس هستند.
نکتهی مهم: این UI اضافه شده، یک برنامهی ASP.NET Core MVC است. به همین جهت در انتهای متد Configure، ذکر میان افزارهای UseStaticFiles و همچنین UseMvcWithDefaultRoute انجام شدند.
اکنون اگر برنامهی IDP را مجددا با دستور dotnet run اجرا کنیم، تصویر زیر را میتوان در ریشهی سایت، مشاهده کرد که برای مثال لینک discovery endpoint در همان سطر اول آن ذکر شدهاست:
همچنین همانطور که در قسمت قبل نیز ذکر شد، یک IDP حتما باید از طریق پروتکل HTTPS در دسترس قرار گیرد که در پروژههای ASP.NET Core 2.1 این حالت، جزو تنظیمات پیشفرض است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
اگر استاندارد OpenID Connect را بررسی کنیم، از مجموعهای از دستورات و رهنمودها تشکیل شدهاست. بنابراین نیاز به کامپوننتی داریم که این استاندارد را پیاده سازی کرده باشد تا بتوان بر اساس آن یک Identity Provider را تشکیل داد و پیاده سازی مباحثی که در قسمت قبل بررسی شدند مانند توکنها، Flow، انواع کلاینتها، انواع Endpoints و غیره چیزی نیستند که به سادگی قابل انجام باشند. اینجا است که IdentityServer 4، به عنوان یک فریم ورک پیاده سازی کنندهی استانداردهای OAuth 2 و OpenID Connect مخصوص ASP.NET Core ارائه شدهاست. این فریم ورک توسط OpenID Foundation تائید شده و داری مجوز رسمی از آن است. همچنین جزئی از NET Foundation. نیز میباشد. به علاوه باید دقت داشت که این فریم ورک کاملا سورس باز است.
نصب و راه اندازی IdentityServer 4
همان مثال «قسمت دوم - ایجاد ساختار اولیهی مثال این سری» را در نظر بگیرید. داخل آن پوشههای جدید src\IDP\DNT.IDP را ایجاد میکنیم.
نام دلخواه DNT.IDP، به پوشهی جدیدی اشاره میکند که قصد داریم IDP خود را در آن برپا کنیم. نام آن را نیز در ادامهی نامهای پروژههای قبلی که با ImageGallery شروع شدهاند نیز انتخاب نکردهایم؛ از این جهت که یک IDP را قرار است برای بیش از یک برنامهی کلاینت مورد استفاده قرار دهیم. برای مثال میتوانید از نام شرکت خود برای نامگذاری این IDP استفاده کنید.
اکنون از طریق خط فرمان به پوشهی src\IDP\DNT.IDP وارد شده و دستور زیر را صادر کنید:
dotnet new web
اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل Properties\launchSettings.json آن و تغییر شماره پورتهای پیشفرض آن است تا با سایر پروژههای وبی که تاکنون ایجاد کردهایم، تداخل نکند. برای مثال در اینجا شماره پورت SSL آنرا به 6001 تغییر دادهایم.
اکنون نوبت به افزودن میانافزار IdentityServer 4 به پروژهی خالی وب است. اینکار را نیز توسط اجرای دستور زیر در پوشهی src\IDP\DNT.IDP انجام میدهیم:
dotnet add package IdentityServer4
در ادامه نیاز است این میانافزار جدید را معرفی و تنظیم کرد. به همین جهت فایل Startup.cs پروژهی خالی وب را گشوده و به صورت زیر تکمیل میکنیم:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddIdentityServer() .AddDeveloperSigningCredential(); }
- متد الحاقی AddDeveloperSigningCredential کار تنظیمات کلید امضای دیجیتال توکنها را انجام میدهد. در نگارشهای قبلی IdentityServer، اینکار با معرفی یک مجوز امضاء کردن توکنها انجام میشد. اما در این نگارش دیگر نیازی به آن نیست. در طول توسعهی برنامه میتوان از نگارش Developer این مجوز استفاده کرد. البته در حین توزیع برنامه به محیط ارائهی نهایی، باید به یک مجوز واقعی تغییر پیدا کند.
تعریف کاربران، منابع و کلاینتها
مرحلهی بعدی تنظیمات میانافزار IdentityServer4، تعریف کاربران، منابع و کلاینتهای این IDP است. به همین جهت یک کلاس جدید را به نام Config، در ریشهی پروژه ایجاد و به صورت زیر تکمیل میکنیم:
using System.Collections.Generic; using System.Security.Claims; using IdentityServer4.Models; using IdentityServer4.Test; namespace DNT.IDP { public static class Config { // test users public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = "d860efca-22d9-47fd-8249-791ba61b07c7", Username = "User 1", Password = "password", Claims = new List<Claim> { new Claim("given_name", "Vahid"), new Claim("family_name", "N"), } }, new TestUser { SubjectId = "b7539694-97e7-4dfe-84da-b4256e1ff5c7", Username = "User 2", Password = "password", Claims = new List<Claim> { new Claim("given_name", "User 2"), new Claim("family_name", "Test"), } } }; } // identity-related resources (scopes) public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } public static IEnumerable<Client> GetClients() { return new List<Client>(); } } }
- این کلاس استاتیک، اطلاعاتی درون حافظهای را برای تکمیل دموی جاری ارائه میدهد.
- ابتدا در متد GetUsers، تعدادی کاربر آزمایشی اضافه شدهاند. کلاس TestUser در فضای نام IdentityServer4.Test قرار دارد. در کلاس TestUser، خاصیت SubjectId، بیانگر Id منحصربفرد هر کاربر در کل این IDP است. سپس نام کاربری، کلمهی عبور و تعدادی Claim برای هر کاربر تعریف شدهاند که بیانگر اطلاعاتی اضافی در مورد هر کدام از آنها هستند. برای مثال نام و نام خانوادگی جزو خواص کلاس TestUser نیستند؛ اما منعی هم برای تعریف آنها وجود ندارد. اینگونه اطلاعات اضافی را میتوان توسط Claims به سیستم اضافه کرد.
- بازگشت Claims توسط یک IDP مرتبط است به مفهوم Scopes. برای این منظور متد دیگری به نام GetIdentityResources تعریف شدهاست تا لیستی از IdentityResourceها را بازگشت دهد که در فضای نام IdentityServer4.Models قرار دارد. هر IdentityResource، به یک Scope که سبب دسترسی به اطلاعات Identity کاربران میشود، نگاشت خواهد شد. در اینجا چون از پروتکل OpenID Connect استفاده میکنیم، ذکر IdentityResources.OpenId اجباری است. به این ترتیب مطمئن خواهیم شد که SubjectId به سمت برنامهی کلاینت بازگشت داده میشود. برای بازگشت Claims نیز باید IdentityResources.Profile را به عنوان یک Scope دیگری مشخص کرد که در متد GetIdentityResources مشخص شدهاست.
- در آخر نیاز است کلاینتهای این IDP را نیز مشخص کنیم (در مورد مفهوم Clients در قسمت قبل بیشتر توضیح داده شد) که اینکار در متد GetClients انجام میشود. فعلا یک لیست خالی را بازگشت میدهیم و آنرا در قسمتهای بعدی تکمیل خواهیم کرد.
افزودن کاربران، منابع و کلاینتها به سیستم
پس از تعریف و تکمیل کلاس Config، برای معرفی آن به IDP، به کلاس آغازین برنامه مراجعه کرده و آنرا به صورت زیر تکمیل میکنیم:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(Config.GetUsers()) .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryClients(Config.GetClients()); }
افزودن میان افزار IdentityServer به برنامه
پس از انجام تنظیمات مقدماتی سرویسهای برنامه، اکنون نوبت به افزودن میانافزار IdentityServer است که در کلاس آغازین برنامه به صورت زیر تعریف میشود:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
آزمایش IDP
اکنون برای آزمایش IDP، به پوشهی src\IDP\DNT.IDP وارد شده و دستور dotnet run را اجرا کنید:
همانطور که ملاحظه میکنید، برنامهی IDP بر روی پورت 6001 قابل دسترسی است. برای آزمایش Web API آن، آدرس discovery endpoint این IDP را به صورت زیر در مرورگر وارد کنید:
https://localhost:6001/.well-known/openid-configuration
در این تصویر، مفاهیمی را که در قسمت قبل بررسی کردیم مانند authorization_endpoint ،token_endpoint و غیره، مشاهده میکنید.
افزودن UI به IdentityServer
تا اینجا میانافزار IdentityServer را نصب و راه اندازی کردیم. در نگارشهای قبلی آن، UI به صورت پیشفرض جزئی از این سیستم بود. در این نگارش آنرا میتوان به صورت جداگانه دریافت و به برنامه اضافه کرد. برای این منظور به آدرس IdentityServer4.Quickstart.UI مراجعه کرده و همانطور که در readme آن ذکر شدهاست میتوان از یکی از دستورات زیر برای افزودن آن به پروژهی IDP استفاده کرد:
الف) در ویندوز از طریق کنسول پاورشل به پوشهی src\IDP\DNT.IDP وارد شده و سپس دستور زیر را وارد کنید:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))
\curl -L https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.sh | bash
یک نکته: در ویندوز اگر در نوار آدرس هر پوشه، عبارت cmd را وارد و enter کنید، کنسول خط فرمان ویندوز در همان پوشه باز خواهد شد. همچنین در اینجا از ورود عبارت powershell هم پشتیبانی میشود:
بنابراین در نوار آدرس پوشهی src\IDP\DNT.IDP، عبارت powershell را وارد کرده و سپس enter کنید. پس از آن دستور الف را وارد (copy/paste) و اجرا کنید.
به این ترتیب فایلهای IdentityServer4.Quickstart.UI به پروژهی IDP جاری اضافه میشوند.
- پس از آن اگر به پوشهی Views مراجعه کنید، برای نمونه ذیل پوشهی Account آن، Viewهای ورود و خروج به سیستم قابل مشاهده هستند.
- در پوشهی Quickstart آن، کدهای کامل کنترلرهای متناظر با این Viewها قرار دارند.
بنابراین اگر نیاز به سفارشی سازی این Viewها را داشته باشید، کدهای کامل کنترلرها و Viewهای آن هم اکنون در پروژهی IDP جاری در دسترس هستند.
نکتهی مهم: این UI اضافه شده، یک برنامهی ASP.NET Core MVC است. به همین جهت در انتهای متد Configure، ذکر میان افزارهای UseStaticFiles و همچنین UseMvcWithDefaultRoute انجام شدند.
اکنون اگر برنامهی IDP را مجددا با دستور dotnet run اجرا کنیم، تصویر زیر را میتوان در ریشهی سایت، مشاهده کرد که برای مثال لینک discovery endpoint در همان سطر اول آن ذکر شدهاست:
همچنین همانطور که در قسمت قبل نیز ذکر شد، یک IDP حتما باید از طریق پروتکل HTTPS در دسترس قرار گیرد که در پروژههای ASP.NET Core 2.1 این حالت، جزو تنظیمات پیشفرض است.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
- این سیستم مدنظر شما نیست؟ اشکالی ندارد! از صفر خودتان آنرا بنویسید و تنظیم کنید: «اعتبارسنجی مبتنی بر کوکیها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
- در Identity به ازای هر نقش کاربر، یک select به بانک اطلاعاتی وجود ندارد. دقیقا پس از لاگین، اطلاعات User Claims در کوکی شخص به صورت رمزنگاری شده، کش میشوند و در دفعات بعدی/درخواستهای بعدی، از این کوکی خوانده خواهند شد. اما در پروژهی DNT Identity به ازای هر درخواست، طوری تنظیم شده تا وضعیت اعتبار کاربر، بررسی شود و اینکار توسط تنظیم enableImmediateLogout و ValidationInterval = TimeSpan.Zero آن انجام میشود. چرا؟ چون اگر در سمت سرور، کاربری را غیرفعال کردید، آیا باید تا زمان منقضی شدن اعتبار کوکی آن، بتواند از سایت استفاده کند؟ بله. این مورد هزینهی اعتبارسنجی User Claims و در نتیجه، کوئری گرفتن از بانک اطلاعاتی را به ازای هر درخواست، به همراه دارد؛ ولی ضروری است. یعنی این سیستم صرفا هر کوکی را که شامل یکسری User Claims باشد، معتبر نمیداند.
مسیرراهها
SQL Server
آخرین تاریخ بروزرسانی 93/10/21
SQL Server 2005
SQL Server 2008
SQL Server 2012
SQL Serve 2014
SQL Server 2005
SQL Server 2008
- SQL Server 2008: Script Data
- سرویس پک یک SQL Server 2008
- اس کیوال سرور 2008 و عملگرهای C مانند
- انتقال فایلهای دیتابیس اس کیوال سرور 2008
- فشرده سازی اطلاعات در SQL server 2008
- مقایسه امنیت Oracle11g و SQL server 2008 از دید آمار در سال 2009
- آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت اول
- آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت دوم
- آشنایی با قابلیت FileStream اس کیوال سرور 2008 - قسمت سوم
- تهیه گزارش از منسوخ شدههای مورد استفاده در SQL Server 2008
- استفاده از قابلیت Script Data اس کیوال سرور 2008 از طریق برنامه نویسی
- Resource Governor در 2008 SQL Server
- Embed کردن SQL Server Express 2008 در یک برنامه
SQL Server 2012
- Microsoft® SQL Server® 2012
- نحوه تبدیل نگارش SQL Server 2012 RTM مدت دار، به نگارش کامل
- نحوه ایجاد Sequence و استفاده آن در Sql Server 2012
- بررسی الگوهای ایندکسهای Non-Clustered در SQL Server
- آشنایی با Column Store Index در SQL Server 2012
- آشنایی با Contained Databases در SQL Server 2012
- استفاده از Sparse Columns در SQL Server 2012
- تغییر نام پایگاه داده و فایل هایش در SQL Server 2012
- گرفتن خروجی XML از جداول در SQL Server 2012
- گرفتن خروجی JSON از جداول در SQL Server 2012
- آشنایی با FileTable در SQL Server 2012 بخش 1
- آشنایی با FileTable در SQL Server 2012 بخش 2
SQL Serve 2014
- معرفی OLTP درون حافظهای در SQL Server 2014
- ایجاد جداول بهینه سازی شده برای حافظه در SQL Server 2014
- ابزارهای مهاجرت به OLTP درون حافظهای در SQL Server 2014
- ماندگاری با تاخیر در SQL Server 2014
- امکان استفاده از یک هارد SSD بجای RAM در SQL Server 2014
- کاهش حجم لاگ فایلهای اسکیوال سرور 2005 و 2008
- گزارشگیری از تاریخچهی پشتیبانگیریها در اس کیوال سرور
- گزارشگیری از تاریخچهی اجرای کوئریها در SQL Server
- تشخیص کمبود ایندکسها در SQL server
- پیدا کردن لیست SQL serverهای نصب شده در یک شبکه
- چرا نباید از کوئریهای select * استفاده کرد؟
- منسوخ شدهها در نگارشهای جدید SQL server
- پیدا کردن وابستگیهای اشیاء در SQL Server
- به روز رسانی Viewها و رویههای ذخیره شده در SQL server
- نحوه راه اندازی مجدد یک دیتابیس اس کیوال سرور پس از پر شدن هارد دیسک
- مشکل اتصال به اس کیوال سرور 2000 از طریق management studio 2008
- مشکل ی و ک فارسی و عربی در یک دیتابیس اس کیوال سرور
- مونیتور کردن میزان مصرف CPU در اس کیوال سرور
- تنظیمات پیشنهادی حداکثر حافظهی مصرفی اس کیوال سرور
- Microsoft SQL Server 2008 Management Objects
- مونیتور کردن میزان فضای خالی باقیمانده در سرور توسط اس کیوال سرور
- آیا از وضعیت رویههای ذخیره شدهی دیتابیسهای اس کیوال سرور خود خبر دارید؟
- تعیین اعتبار کردن یک عبارت SQL
- تعیین اعتبار کردن یک عبارت SQL - قسمت دوم
- نگهداری ایندکسها در اسکیوال سرور
- حذف سریع تمام رکوردها در SQL server
- بررسی صحت پشتیبانهای تهیه شده در SQL Server
- یافتن لیست اسمبلیهای ارجاعی
- مقایسه ساختاری دو دیتابیس SQL Server
- sp_send_dbmail و ارسال ایمیل فارسی
- محدود کردن دسترسی به اس کیوال سرور بر اساس IP
- مقایسه نتایج الگوریتمهای هش کردن اطلاعات در اس کیوال سرور و دات نت
- تنظیم درجه سازگاری یک دیتابیس اس کیوال سرور
- مقایسه رکوردهای دو جدول
- تهیه بک آپهای خودکار از SQL Server Express
- بازسازی msdb تخریب شده
- مقایسه حساس به حروف کوچک و بزرگ در SQL Server
- مزیتهای استفاده از رویههای ذخیره شده؛ واقعیت یا توهم؟!
- روشهایی برای حذف رکوردهای تکراری
- ذخیره سازی فایلها در دیتابیس یا استفاده از فایل سیستم متداول؟
- به روز رسانی فیلدهای XML در SQL Server
- Optimize for unknown
- FxCop برای SQL Server
- با رویههای ذخیره شده خود، وب سرویس ایجاد کنید
- به روز رسانی فیلدهای XML در SQL Server - قسمت دوم
- مرجعی در مورد نگارشهای مختلف SQL Server
- یافتن تداخلات Collations در SQL Server
- عدم کاهش حجم لاگ فایل SQL Server
- دریافت خطای database is not accessible و نحوهی رفع مشکل
- سرورهای متصل شدهی SQL Server و مبحث تراکنشها
- چک لیست نصب SQL Server
- درک نمودار
- بررسی مقدار دهی اولیه متغیرها در T-SQL
- اگر نصب سرویس پک اس کیوال سرور Fail شد ...
- ایجاد بک آپ برای دیتابیس با استفاده از CMD
- انتخاب Sub Query درون پرانتزها به کمک [+Ctrl+Shift
- روشی جهت یافتن فیلدهای استفاده شده درون Stored Procedure ، Function و View
- آشنایی با Row_Number،Rank،Dense_Rank،NTILE
- بازگردانی پایگاه داده بدون فایل لاگ
- LocalDB چیست؟
- آشنایی با تابع PATINDEX در SQL Server
- اجرای Stored Procedure با چند نوع مقدار برگشتی توسط EF CodeFirst
- آشنایی با SQL Server Data Tools
- مشکل ارتباط با SQL Server در لوکال
- آشنایی با Window Functionها در SQL Server بخش اول
- آشنایی با Window Functionها در SQL Server بخش دوم
- آشنایی با Window Functionها در SQL Server بخش سوم
- آشنایی با Window Functionها در SQL Server بخش چهارم
- استفاده از SQLDom برای آنالیز عبارات T-SQL
- دسته بندی سطرهای جدول
- حذف نمودن کاراکترهای ناخواسته توسط Recursive CTE قسمت اول
- جستجوی کاراکترهای wildcards توسط ماده اختیاری ESCAPE
- ستون محاسباتی (computed column)
- گذری بر مفاهیم relationship
- حذف کاراکترهای ناخواسته توسط Recursive CTE قسمت دوم
- از کار افتادن SQL Server Agent
- بدست آوردن برگهای یک درخت توسط Recursive CTE
- بررسی مساله متداول Top N در نسخههای مختلف SQL Server
- فعال و غیر فعال کردن قیود
- مروری بر طراحی Schema less بانک اطلاعاتی SisoDb
- اجرای یک Script حاوی دستورات Go در سی شارپ
- آشنایی با Window Functionها در SQL Server بخش پنجم
- افزودن یک DataType جدید برای نگهداری تاریخ خورشیدی - 1
- افزودن یک DataType جدید برای نگهداری تاریخ خورشیدی - 2
- افزودن یک DataType جدید برای نگهداری تاریخ خورشیدی - 3
- نحوه انتقال اطلاعات استخراج شده از وب سرویس به SQL Server به کمک SSIS
- Full Text Search و Rank فیلدهای بازیابی شده
- بررسی دستور Truncate Table و Delete
- Identity و مباحث مربوط به آن (قسمت اول) - آشنایی با Identity
- Identity و مباحث مربوطه (قسمت دوم) نحوه بدست آوردن مقادیر Identity
- ویدئوی آموزش مقدمات CodeFirst در قالب یک کلاس آموزشی به همراه مثال
- مفهوم READ_COMMITTED_SNAPSHOT در EF 6
- آشنایی با SQL Server Common Table Expressions - CTE
- NoSQL و مایکروسافت
- نحوه تهیه گزارش در SSRS و انتشار آن روی وب سرور
- مدیریت اطلاعات وابسته به زمان در بانکهای اطلاعاتی رابطهای
- تبدیل اعداد صحیح و اعشاری به حروف در T-SQL با استفاده از Join
- SQL Instance
- خواندن سریع اطلاعات فایل اکسل و ذخیره در بانک SQL
- افزودن اکانت مدیریتی فراموش شده به SQL Server
- اندازه گیری کارآیی پرس و جوها با استفاده از SET STATISTICS TIME
- ساخت کاربر ویندوز توسط SQL Server
- بررسی Transactions و Locks در SQL Server
- معرفی و استفاده از DDL Triggers در SQL Server
- بررسی دو نکته (ترفند) کاربردی در SQL Server
- بررسی ابزار SQL Server Profiler
- اجرای SSIS Package از طریق برنامه کاربردی
- پردازش دادههای جغرافیایی به کمک SQL Server و Entity framework
- استفاده از قابلیت پارتیشن بندی در آرشیو جداول بانکهای اطلاعاتی SQL Server
- بررسی بارگذاری دادهها در انبارهای داده و معرفی الگوهای بکار رفته در آن
- بهبود عملکرد SQL Server Locks در سیستمهای با تعداد تراکنش بالا در Entity Framework
- SQL Antipattern #1
- SQL Antipattern #2
- پیاده سازی عملیات صفحه بندی (paging) در sql server
- فعالسازی Multiple Active Result Sets
- استفاده از SQLDom برای آنالیز عبارات T-SQL، قسمت دوم
- اتصال به SQL از راه دور (Remote) و یا به یک سرور در شبکه
- نحوه تعریف Linked Server و دریافت اطلاعات از سروری دیگر
نظرات اشتراکها
توسعه Asp.net Core و Asp.net Core Identity
من این مثال را بررسی کردم. این مشکلات را دارد:
- به علت استفادهی از AddEntityFrameworkStores، هیچکدام از سرویسهای تعریف شدهی در لایه سرویس استفاده نخواهند شد و از همان پیش فرضهای Identity Core استفاده میشود.
- در Controllers از هیچکدام از سرویسهای برنامه استفاده نشدهاست و از همان پیش فرضها استفاده شدهاست.
- در لایه سرویس (لایهای که از آن عملا استفاده نمیشود)، ApplicationRoleStore و ApplicationUserStore سازندهای را دریافت میکنند که در تنظیمات IoC Container پیاده سازی برای آنها ارائه نشدهاست (و در صورت استفاده عملا کار نخواهند کرد؛ مگر اینکه از همان پیش فرضهای AddEntityFrameworkStores استفاده کنند که عملا این سفارشی سازی را بیاثر میکند).
- به علت استفادهی از AddEntityFrameworkStores، هیچکدام از سرویسهای تعریف شدهی در لایه سرویس استفاده نخواهند شد و از همان پیش فرضهای Identity Core استفاده میشود.
- در Controllers از هیچکدام از سرویسهای برنامه استفاده نشدهاست و از همان پیش فرضها استفاده شدهاست.
- در لایه سرویس (لایهای که از آن عملا استفاده نمیشود)، ApplicationRoleStore و ApplicationUserStore سازندهای را دریافت میکنند که در تنظیمات IoC Container پیاده سازی برای آنها ارائه نشدهاست (و در صورت استفاده عملا کار نخواهند کرد؛ مگر اینکه از همان پیش فرضهای AddEntityFrameworkStores استفاده کنند که عملا این سفارشی سازی را بیاثر میکند).
- این پروژه برای سازگاری با آخرین نگارش موجود، بارها به روز رسانی شده. متن مطلب فوق را تغییر ندادم، ولی کدهای مخزن کد آن کاملا به روز هست.
- برای درک این مورد باید ساختار پروژهی اصلی Identity را بررسی کنید. در یک طرف آن تعدادی کلاس سطح بالا و abstraction هست و در طرف دیگر پوشهای به نام EntityFrameworkCore که پیاده سازی مخصوص EF-Core این abstractionها است. هستند پروژههای دیگری که بجای EntityFrameworkCore از NHibernate و یا MongoDB استفاده کرده باشند.
نظرات مطالب
IdentityServer قسمت اول
- در قسمت اول
به واژههایی مانند «شرکت»، «مرکزی»، «single sign-on»، و «چندین
برنامهی مختلف فقط با یک لاگین» که قرار نیست اطلاعات کاربران خودشان را
داخل بانکهای اطلاعاتی خودشان به صورت مجزایی قرار دهند، بیشتر دقت کنید.
- Identity Server برای انتقال و نگهداری اطلاعات موقتی خودش از ترکیب کوکیهای رمزنگاری شده و همچنین JWT استفاده میکند. مثال کاملی در این مورد در سری جدید «امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x» با تمام جزئیات ممکن، بررسی شدهاست.