بررسی اجمالی از ارائه دهندگان ذخیره سازی سفارشی برای ASP.NET Identity

سه شنبه 30 آذر 1395

ASP.NET Identity یک سیستم قابل توسعه و پلاگین پذیر می باشد که میتوان آن را بدون این که برنامه از اول ساخته شود آن را پیاده سازی کنید و در این مقاله میخواهیم درمورد چگونگی سفارشی سازی این سیستم توضیح دهیم.

بررسی اجمالی از ارائه دهندگان ذخیره سازی سفارشی برای ASP.NET Identity


این بخش شامل مفاهیم مهمی برای ذخیره سازی سفارشی می باشد که در ادامه آن را بررسی می کنیم .

آموزش تصویری نصب و استفاده از Asp.Net Identity 2


مقدمه


به صورت پیش فرض ASP.NET Identity اطلاعات کاربر را در SQL Server ذخیره می کند و از EF Code First برای ایجاد بانک اطلاعاتی استفاده می شود. برای بیشتر برنامه ها این روش میتواند خوب باشد. بااین حال شما ترجیح می دهید از این مکانیزم استفاده کنید یا ساختار متفاوت از  سیستم پیش فرض پیاده سازی شده. در هرصورت، شما میتوان یک ارئه دهنده سفارشی برای مکانیزم ذخیره سازی بنویسید.

درک معماری


ASP.NET Identity شامل کلاس هایی است که Managerها و Storeها را فرخوانی میکند.Managerها کلاس های سطح بالا هستند که برنامه نویس برای انجام عملیات ها از آن استفاده می کند، همچنین میتوان یک کاربر در سیستم ASP.NET Identity ایجاد کند.Storeها کلاس های سطح پایین تری هستند که مشخص می کند Entityها چگونه هستند، از جمله Userها و Roleهایی هستند که ضروری می باشند. Storeها به مکانیزم مداوم نزدیک هستند. اما Managerها از Storeها جدا هستند. به این معنی که میتوان بدون اخلال مکانیزم خود را جایگزین کنید.


​نمودار زیر نشان می دهد که چگونه برنامه های تحت وب با Managerها و Storeها با Data Access Layer تعامل دارند.


برای ایجاد ارئه دهنده ذخیره سازی یا همان (Storage Provider) سفارشی شده در ASP.NET Identity شما باید Data Source، Data Access Layer را ایجاد کنید و همچنین کلاس های Store را برای تعامل با Data Access Layer ایجاد کنید.شما میتوان با استفاده از Manager API ها عملیات داده ای بر روی کاربر انجام دهید اما الان داده ها در سیستم متفاوت ذخیره شده اند.


نیازی نیست کلاس های Manager را سفارشی کنیم چون زمانی که یک نمونه از UserManager یا RoleManager ایجاد می کنید یک کلاس از نوع User فراهم می کنید و یک نمونه از کلاس Store به عنوان argument ثبت می کنید. این روش شما را قادر می سازد  کلاس های سفارشی شده را در ساختار موجود پلاگین کنید. در قسمت 'پیکربندی مجدد برنامه برای استفاده از ارئه دهنده ذخیره سازی (Storage Provider)' چگونگی ایجاد یک نمونه از UserManager و RoleManager برای کلاس های Store را بررسی خواهیم کرد.

درک داده های ذخیره شده
برای پیاده سازی ارئه دهنده ذخیره سازی (Storage Provider) سفارشی، شما باید انواع داده های استفاده شده در ASP.NET Identity را درک کنید و تصمیم بگیرید که میخواهید از چه ویژگی هایی در برنامه خود استفاده کنید.


Users
-کاربران سایتتان را ثبت می کند که شامل UserId, UserName می باشد. اگر کاربر بخواهد در سایت شما
Log In کند  Password آن را هش می کند هم چنین برای ثبت نام های two factor میتوان شامل Email و PhoneNumber نیز باشد و اگر تعداد خطا های هنگام Log In زیاد باشد کاربر را مسدود می کند.

User Claims
-مجموعه ای از گزارش ها (یا Claims) که درمورد کاربران اهراز هویت شده می باشد.

UserLogins

-اطلاعاتی درمورد ارئه دهنده اهراز هویت های خارجی (external authentication) مانند : فیسبوک، زمانی که کاربر Login می کند.

Roles

-گروه های که در سایت شما اهراز هویت می کنند. مانند : ( ادمین، کارمندان )

ایجاد Data Access Layer

در این قسمت فرض بر این که شما با این مکانیزم آشنا هستید و به سراغ استفاده و چگونگی ایجاد Entity ها برای این مکانیزم می رویم.

در این قسمت نمیخواهیم جزئییاتی در مورد چگونگی ایجاد کلاس های Repository یا Data Access  را بررسی کنیم. درعوض تعدادی پیشنهاد درباره تصمیم گیری طراحی که به آن برای کار با ASP.NET Identity نیاز دارید فراهم می کنیم.

زمانی که برای ارئه دهنده ذخیره سازی (Provider Store) سفارشی شده repository طراحی کنید بسیار راحت خواهید بود.به عنوان مثال : اگر از roleها در برنامه خود استفاده نمی کنید. نیازی به ایجاد storage برای Roleها نیست. فناوری و زیرساخت موجود ممکن است نیاز ساختار باشد که با پیاده سازی پیش فرض ASP.NET Identity متفاوت است. در Data Access Layer، شما یک منطق برای کار با ساختار repository فراهم می کنید.

در Data Access Layer، شما یک منطق برای ذخیره داده هایی که  ASP.NET Identiy فراهم کرده است ایجاد کنید. ممکن است Data Access Layer برای ارئه دهنده ذخیره سازی (Storage Provider)​ سفارشی شده شامل کلاس های زیر برای ذخیره اطلاعات User و Role باشد :

Context 

-کپسوله سازی اطلاعات برای اتصال به مکانیزم و اجرا کوئری ها بر روی آن. این کلاس Data Access Layer مرکزی می باشد. نیاز است برای انجام عملیات برروی کلاس ها داده یک نمونه از آن ها ساخته شود. همچنین به نمونه ساختن از این کلاس  کلاس های Store را مقداردهی می کنیم.

User Storage

-ذخیره و بازیابی اطلاعات کاربر(مانند User Name, Password )

Role Storage

-ذخیره بازیابی اطلاعات نقش(مانند role name)

UserClaims Storage

-ذخیره و بازیابی اطلاعات user claim

UserLogins Storage

-ذخیره و بازیابی اطلاعات کاربران Login شده(مانند اهراز هویت های خارجی)

UserRole Storage

-ذخیره و بازیابی نقش های کاربران

فقط کلاس هایی که در برنامه شما نیاز است پیاده سازی کنید.

در کلاس های Data Access، شما   کد هایی در انجام عملیات داده ها برای مکانیزم خود ارئه دادید.برای مثال ، در پیاده سازی MySQL، کلاس UserTable شامل متد برای افزودن رکورد جدید در جدول کاربران در بانک اطلاعاتی می باشد. متغیر _database نمونه از کلاس MySqlDatabase می باشد.

public int Insert(TUser user)
{
    string commandText = @"Insert into Users (UserName, Id, PasswordHash, SecurityStamp,Email,EmailConfirmed,PhoneNumber,PhoneNumberConfirmed, AccessFailedCount,LockoutEnabled,LockoutEndDateUtc,TwoFactorEnabled)
        values (@name, @id, @pwdHash, @SecStamp,@email,@emailconfirmed,@phonenumber,@phonenumberconfirmed,@accesscount,@lockoutenabled,@lockoutenddate,@twofactorenabled)";
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("@name", user.UserName);
    parameters.Add("@id", user.Id);
    parameters.Add("@pwdHash", user.PasswordHash);
    parameters.Add("@SecStamp", user.SecurityStamp);
    parameters.Add("@email", user.Email);
    parameters.Add("@emailconfirmed", user.EmailConfirmed);
    parameters.Add("@phonenumber", user.PhoneNumber);
    parameters.Add("@phonenumberconfirmed", user.PhoneNumberConfirmed);
    parameters.Add("@accesscount", user.AccessFailedCount);
    parameters.Add("@lockoutenabled", user.LockoutEnabled);
    parameters.Add("@lockoutenddate", user.LockoutEndDateUtc);
    parameters.Add("@twofactorenabled", user.TwoFactorEnabled);

    return _database.Execute(commandText, parameters);
}

بعد از این که کلاس های Data Access را ساختید، باید کلاس های Store را ایجاد کنید که متد های داخل data access layer را مشخص کند.

سفارشی کردن کلاس User

زمانی که ارئه دهنده ذخیره سازی (storage provider) را پیاده سازی می کنید، باید یک کلاس User ایجاد کنید که از IdentityUser ارث بری کند این کلاس درون فضای نام Microsoft.Aspnet.Identity.EntityFramework قرار دارد.

نمودار زیر نشان می دهد کلاس IdentityUser باید ایجادشود و interface آن، برای پیاده سازی این کلاس است.

اینترفیس IUser<TKey>  خاصیت هایی را تعریف می کند که در هنگام عملیات درخواست قصد فراخوانی آنها را دارد. این اینترفیس شامل دو خاصیت می باشد - Id و UserName . اینترفیس IUser<TKey> یک پارامتر (TKey) دریافت می کنید که شما را قادر می سازد تا نوع نوع Id را مشخص کنید.

همچنین فریم ورک Identity اینترفیس IUser(بدون پارامتر جنریک) را برای زمانی که میخواهید از مقدار string برای کلید اصلی استفاده کنید ارئه می دهد.

کلاس IdentityUser پیاده سازی شده اینترفیس IUser می باشد و شامل Property ها یا Constructorهایی برای کاربران سایت می باشد. فیلد Id را با استفاده از پارامتر جنریک int قرار داده ایم.

public class IdentityUser : IUser<int>
{
    public IdentityUser() { ... }
    public IdentityUser(string userName) { ... }
    public int Id { get; set; }
    public string UserName { get; set; }
    // can also define optional properties such as:
    //    PasswordHash
    //    SecurityStamp
    //    Claims
    //    Logins
    //    Roles
}

​سفارشی کردن User Store

برای فراهم کردن متدهایی که برروی داده های کاربر عملیاتی را انجام می دهند باید کلاس UserStore را هم ایجاد کنیم. این کلاس معادل  کلاس UserStore<TUser> که در فضای نام Microsoft.Aspnet.Identity.EntityFramework قرار دارد است. در این کلاس، اینترفیس IUserStore<TUser,TKey> و اینترفیس های دلخواه خود را پیاده سازی می کنیم .

تصویر زیر نشان می دهد که کلاس UserStore  با اینترفیس مربوطه خود ایجاد شود.

قالب پروژه پیش فرض که در Visual Studio وجود دارد شامل کد هایی است که فرض می کنیم دارای اینترفیس های دلخواه برای پیاده سازی در UserStore باشد. اگر شما از قالب پیش فرض با user store سفارشی استفاده کنید، باید اینترفیس های دلبخواهتان را در user store پیاده سازی کنید یا قالب کدتان را برای فرخوانی متد های دیگر تغییر ندهید اینترفیس شما پیاده سازی نخواهد شد.

مثال زیر  کلاس userstore را نمایش می دهد.

TUser پارامتری  از نوع user دریافت می کند که معمولا توسط IdentityUser تعریف می شود.

TKey پارامتری از نوع user key دریافت می کند.

public class UserStore : IUserStore<IdentityUser, int>
{
    public UserStore() { ... }
    public UserStore(ExampleStorage database) { ... }
    public Task CreateAsync(IdentityUser user) { ... }
    public Task DeleteAsync(IdentityUser user) { ... }
    public Task<IdentityUser> FindByIdAsync(int userId) { ... }
    public Task<IdentityUser> FindByNameAsync(string userName) { ... }
    public Task UpdateAsync(IdentityUser user) { ... }
    public void Dispose() { ... }
}

در این مثال، constructor یک پارامتر به نام database از نوع ExampleDatabase دریافت می کند تنها تصور کنید چگونه کلاس های Data Access را عبور دهیم. برای مثال، در پیاده سازی MySQL ، این Constructor پارامتر از جنس MySQL دریافت می کند.

در داخل کلاس userstore، از کلاس های data access که برای انجام عملیات استفاده کرده بودید استفاد کنید.برای مثال، در پیاده سازی MySQL، کلاس userstore دارای یک متد به نام CreateAsync می باشد که یک نمونه از user را وارد جدول user در بانک اطلاعاتی می کند. متد Insert برروی شی userTable از همان متدی است که در بخش قبل نشان داده ایم.

public Task CreateAsync(IdentityUser user)
{
    if (user == null) {
        throw new ArgumentNullException("user");
    }

    userTable.Insert(user);

    return Task.FromResult<object>(null);
}

اینترفیس هایی برای پیاده سازی زمانی که users store را سفارشی می کنیم

در عکس زیر توضیح بیشتر درباره توابع تعریف شده در هر اینترفیس را  می دهد . تمامی اینترفیس ها دلبخواه هستند و از IUserStore ارث بری کرده اند .

IUserStore

- اینترفیس IUserStore<TUser, TKey> تنها اینترفیس است که شما باید آن را برای ذخیره سازی کاربر پیاده سازی کنید.

 IUserClaimStore

برای فعال کردن user claim باید اینترفیس IUserClaimStore<TUser, TKey> را در user store پیاده سازی کنید . این اینترفیس شامل متد های حذف، افزودن، و دریافت می باشد.

IUserLoginStore

برای فعال کردن فرایند External Login باید اینترفیس IUserLogin<TUser, TKey> را در user store پیاده سازی کنید. این اینترفیس شامل متد های افزودن، حذف، دریافت و یک متد برای بازیابی اطلاعات کاربر می باشد .

IUserRoleStore

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

IUserPasswordStore

این اینترفیس شامل متد هایی برای دریافت و تنظیم  passwordهای هش شده می باشد . همچنین دارای یک متد می باشد که بررسی می کند آیا کاربر دارای Password می باشد یا خیر.

IUserSecurityStampStore

برای استفاده از SecurityStamp میتوان این اینترفیس را پیاده سازی کنید. SecurityStamp به ما نشان می دهد که چه تغییراتی بر حساب کاربر انجام شده است. زمانی که کاربر رمز عبور خود را تغییر دهد این فرآیند بروزرسانی می شود . این اینترفیس شامل متد هایی برای دریافت و تنظیم  SecurityStamp می باشد.

IUserTwoFactoreStore

این اینترفیس برای اهراز هویت های دو مرحله ای یا همان TowFactore استفاده می شود و شامل متد های دریافت و تنظیم می باشد که این فرایند را برای کاربران فعال می کند.

IUserPhoneNumberStore

برای تنظیم کردن یا دریافت شماره تلفن کاربران و تایید آنها میتوان این اینترفیس را پیاده سازی کنید. همچنین شامل متدهایی برای بررسی کردن شماره تلفن های تایید شده می باشد.

IUserEmailStore

برای تنظیم کردن و دریافت آدرس ایمیل میتوان این اینترفیس را پیاده سازی کنید . و شامل متدی برای تایید ایمیل کاربر می باشد.

IUserLockoutStore

این اینترفیس برای مسدود کردن حساب کاربر می باشد. که شامل متد هایی برای دریافت تعداد بار خطا ، دریافت و تنظیم  حساب های مسدود شده، تاریخ مسدود شدن حساب می باشد.

IQueryableUserStore

این اینترفیس برای انجام کوئری هایی برروی کاربران می باشد که شامل Property هایی برای انجام عملیات برروی کاربر می باشد.

اینترفیس های که در برنامه خود نیاز دارید پیاده سازی کنید، مانند کد زیر که IUserClaimStore و IUserLoginStore و IUserRoleStore و ...

public class UserStore : IUserStore<IdentityUser, int>,
                         IUserClaimStore<IdentityUser, int>,
                         IUserLoginStore<IdentityUser, int>,
                         IUserRoleStore<IdentityUser, int>,
                         IUserPasswordStore<IdentityUser, int>,
                         IUserSecurityStampStore<IdentityUser, int>
{
    // interface implementations not shown
}

IdentityUserClaim, IdentityUserLogin و IdentityUserLoginRole

فضای نام Microsoft.AspNet.Identity.EntityFramework  شامل کلاس هایIdentityUserClaim, IdentityUserLogin,  IdentityUserRole  می باشد. اگر از این امکانات استفاده کنید، ممکن است بخواهید Property های خود نیز به آن اضافه کنید. با این حال گاهی اوقات هنگام انجام عملیات  Entityهای کارآمد برروی حافظه بارگذاری نمی شود(مانند افزودن، حذف کاربراست.) درعوض  کلاس های backend store میتوانند عملیات را به طور مستقیم روی data source اجرا می کند. برای مثال، متد UserStore.GetClaimsAsync() میتواند userClaimTable.FindByUserId(user.Id) برای اجرای کوئری روی جدول فراخوانی کند و لیستی از Claimها را بازگرداند.

public Task<IList<Claim>> GetClaimsAsync(IdentityUser user)
{
    ClaimsIdentity identity = userClaimsTable.FindByUserId(user.Id);
    return Task.FromResult<IList<Claim>>(identity.Claims.ToList());
}

سفارشی سازی کلاس role

زمانی که ارئه دهنده ذخیره سازی(storage provider) را پیاده سازی می کنیم، باید کلاس role که از کلاس IdentityRole ارث بری کند را ایجاد کنید.

تصویر زیر کلاس IdentityRole که پیاده سازی شده اینترفیس IRole<TKey> می باشد را نشان می دهد.

اینترفیس IRole<TKey> شامل Propertyهایی است که RoleManger میتوان عملیات درخواست ها را انجام دهد. این اینترفیس شامل Propertyهای Name و Id است. این اینترفیس شما را قادر می سازد نوع کلید اصلی role را مشخص کیند و این کار به پارامتر جنریک TKey انجام  شود. همچنین این اینترفیس قابلیتی رو فراهم کرده که کلید اصلی از نوع string تعریف کنید.

مثال زیر کلاس IdentityRole را نمایش می دهد که از نوع داده int ، برای کلید اصلی استفاده کرده است. همچین int در پارامتر جنریک قرار دارد.

public class IdentityRole : IRole<int>
{
    public IdentityRole() { ... }
    public IdentityRole(string roleName) { ... }
    public int Id { get; set; }
    public string Name { get; set; }
}

سفارشی کردن Role Store

کلاس RoleStore را برای فراهم کردن متدها برای انجام عملیات برروی داده های role ایجاد کنید. این کلاس از RoleStore<TRole> ارث بری می کند و در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد.

در کلاس RoleStore میتوان اینترفیس های IRoleStore<TRole, TKey> و IQueryableRoleStore<TRole, TKey> را به صورت دلبخواه پیاده سازی کنید.

مثال زیر کلاس RoleStore را نمایش می دهد. پارمتر جنریک TRole , یک کلاس از نوع Role دریافت می کند که معمولا کلاس IdentityRole تعریف می شود. و پارمتر جنریک TKey کلید اصلی role را مشخص می کند.

public class RoleStore : IRoleStore<IdentityRole, int>
{
    public RoleStore() { ... }
    public RoleStore(ExampleStorage database) { ... }
    public Task CreateAsync(IdentityRole role) { ... }
    public Task DeleteAsync(IdentityRole role) { ... }
    public Task<IdentityRole> FindByIdAsync(int roleId) { ... }
    public Task<IdentityRole> FindByNameAsync(string roleName) { ... }
    public Task UpdateAsync(IdentityRole role) { ... }
    public void Dispose() { ... }
}

IRoleStore<TRole>

این اینترفیس برای پیاده سازی  کلاس RoleStore می باشد که شامل متد هایی برای حذف، افزودن، ویرایش، role ها می باشد.

RoleStore<TRole>

برای سفارشی کردن RoleStore، یک کلاس ایجاد می کنیم که پیاده سازی شده اینترفیس IRoleStore می باشد. شما تنها دارید این کلاس را پیاده سازی می کنید. Constructor که یک پارامتر به نام database دریافت می کند از نوع ExampleDatabase است. برای مثال درپیاده سازی MySQL، این Constructor پارامتر هایی که از نوع MySQLDatabase است دریافت می کند.

پیکربندی مجدد برنامه برای استفاده از ارئه دهنده ذخیره سازی (Storage Provider)

1.در پنجره NugetPackgeManager بسته Microsoft ASP.NET Identity EntityFramework را حذف کنید.

سوال: چرا بسته Entity Framework را حذف کردیم؟

اگر شما در قسمت های دیگر به آن نیاز ندارید میتوان آن را حذف کنید.

2.درفایل IdentityModel.cs ، کلاس ApplicationUser را حذف کنید. در برنامه MVC، میتوان فایل IdentityModel را حذف کنید. در برنامه Web Form ، هردو کلاس را حذف کنید.

3.اگر ارائه دهنده ذخیره سازی (Storage Provider)​ در یک پروژه جداگانه قرار دارد رفرنس آن را در برنامه خود اضافه کنید.

4. تمامی رفرنس های Using Microsoft.ASPNet.Identity.EntityFramework را با رفرنسی که ارائه دهنده ذخیره سازی (Storage Provider)​ در آن قرار دارد عوض کنید.

5. در کلاس Startup.Auth.cs ، متد ConfigureAuth را برای استفاده از نمونه ای از Context تغییر دهید.

public void ConfigureAuth(IAppBuilder app)
{
    app.CreatePerOwinContext(ExampleStorageContext.Create);
    app.CreatePerOwinContext(ApplicationUserManager.Create);
    ...

6.در پوشه App_Start فایل IdentityConfig.cs را باز کنید. در کلاس ApplicationUserManager، متد Create را برای بازگرداندن UserStore سفارشی تغییر دهید.

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
{
    var manager = new ApplicationUserManager(new UserStore(context.Get<ExampleStorageContext>()));
    ...
}

7.تمامی رفرنس های ApplicationUser را با IdentityUser عوض کنید.

8.پروژه پیش فرض شامل تعدادی خاصیت در کلاس User می باشد که در اینترفیس IUser تعریف نشده اند. به عنوان مثال : Email, HashPassword, GenericUserIdentityAsync. اگر کلاس User شامل این خاصیت ها نبود، میتوان آن ها را پیاده سازی کنید.

9.اگر یک نمونه از RoleManger ساخته اید، آن را برای کلاس جدید RoleStore تغییر دهید.

var roleManager = new RoleManager<IdentityRole>(new RoleStore(context.Get<ExampleStorageContext>()));

10.پروژه پیش فرض برای کلاس user که  کلید اصلی آن string می باشد طراحی شده است. اگر پروژه شما از نوع دیگری استفاده می کنید میتوان آن را تغییر دهید.

11.اگر به رشته اتصال نیاز دارید آن را در Web.Config اضافه کنید.

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید