اگه این مقاله رو میخونید احتمالا نام Identity به گوشتون خورده و از امکانات اون کمو بیش آگاهید. توی این توضیح سعی کردم عصاره کلام Identity و مبحث احراز هویت رو بطور یکجا تقدیم حضورتون کنم.
در مجموع کار Identity در مرحله اول به وجود آوردن کاربران و شناسه گذاری برای هر کاربر و در مرحله دوم مدیریت دسترسی ها میان کاربران است. (منظور از کاربر هم User و هم Admin )
(دقت کنید اگر این پکیج را نصب میکنید دیگر نیازی به پکیج Microsoft.EntityFrameworkCore نیست و همین پکیج رفرنس های لازم به Microsoft.EntityFrameworkCore را دارا است.)
ابتدا در متد ConfigureServices باید متد AddIdentity را اضافه کنیم.
در متد AddIdentity باید 'IdentityUser' , 'IdentityRole' را بصورت جنریک به این متد پاس دهیم. این متد سرویس های UserManager , RoleManager , SigninManager را در خود دارد و قابلیت های مورد نیاز برای مدیریت کاربران را در اختیار ما قرار می دهد.
کد های درون این متد شامل موارد زیر است که این اعمال را بر عهده دارد :
متد بعدی که به Service.AddIdentity اضافه شده متد 'AddEntityFrameWorkStore' است که در این متد DbContext را بصورت جنریک به آن پاس میدهیم
و بعد از آن متد AddDefaultTokenProviders را به Service.AddIdentity اضافه میکنیم که این دو متد برای کارکرد درست سرویس های Identity لازم است.
در این متد قابلیت های Identity را نیز میتوانید شخصی سازی کنید.
همانطور که از نام option ها مشخص است می توانید محدودیت های پسورد و شروط لاگین در وبسایت را از این طریق کنترل نمایید.
سپس در متد Configure کلاس Startup باید MiddleWare های Identity را اضافه کنیم.
بطور خلاصه UseAuthentication نقش کاربر را مورد بررسی قرار میدهد و متد UseAuthorization دسترسی ها و محدودیت های نقش کاربر را بررسی میکند.
با این ارث بری بطور خودکار پس از اولین Migration جداول مخصوص Identity به پروژه اضافه خواهد شد.
این جداول شامل موارد زیر میباشد.
برای مدیریت کاربران و اعمال مربوط به Register / Login / LogOut بهتر است یک کنترلر مجزا ترجیهاً با نام AccountController بسازیم و کد های مربوط به این قسمت را در این کنترلر قرار دهیم.
در اکشن Register کاری که باید انجام دهیم افزودن یک کاربر با اطلاعاتی که میخواهیم به یک کاربر نسبت دهیم است. این اطلاعات طبق چیزی که ما میخواهیم متغیر است و به همان نسبت کد های این اکشن نیز میتواند تغییرات جزئی داشته باشد. برای مثال در جایی نیاز است شماره موبایل کاربر را دریافت نمایید و صحت آن را تایید کنید پس در اکشن Register باید کد های مربوط به ارسال پیام به شماره کاربر را فراخوانی کنید. این روند میتواند در هر پروژه نسبت به نیاز پروژه تغییر کند.
ابتدا برای اینکه بتوانیم از سرویس های Identity استفاده کنیم باید آن را در کنترلر مورد نظر inject کنیم.برای این کار به این صورت عمل میکنیم.
حالا میتوانیم با استفاده از سرویس
_userManager.CreateAsync(user , Password);
کاربر مورد نظر را به دیتابیس اضافه کنیم. در این سرویس اطلاعات User از جنس IdentityUser و پسورد از جنس string را در ورودی پاس میدهیم.
کاربران در جدول AspNetUser قرار میگیرند.
در اکشن Login کاری که نیاز است انجام شود تایید اطلاعات وارد شده از سوی کاربر و سپس لاگین کردن کاربر در برنامه. برای این کار نیز سرویس هایی وجود دارد که برای استفاده از آن اینگونه عمل میکنیم.
اگر قابلیت های دیگر را نیز فعال کرده باشید مثل تایید ایمیل یا تایید پیامک در این قسمت میتوانید آن را نیز بررسی کنید.
قابلیت IsLockedOut تعداد تلاش های ناموفق کاربر برای لاگین را محدود میکند و در حالت پیشفرض پس از هر 5 بار تلاش ناموفق تا چند دقیقه از تلاش کاربر برای ورود جلوگیری میکند. این موضوع از جنبه جلوگیری از هک شدن توسط ربات ها و بدافزار ها اهمیت دارد.
برای خارج کردن کاربر از برنامه از سرویس
_signInManager.SignOutAsync();
استفاده میکنیم این متد کاربر جاری را که لاگین است لاگ اوت میکند.
بطور کلی برای استفاده از قابلیت های دیگر متد های این سرویس هارا مشاهده کنید و به راحتی با پیدا کردن نام آنها بعنوان کلمه کلیدی میتوانید سرچ کنید و از آن استفاده کنید.
در مجموع در Authorization قصد ما این است که دسترسی های کاربر را طبق نقشی که دارد معین کنیم.
برای این بخش یکسری کانفیگ ها و تنظیمات وجود دارد که به آن میپردازیم.
کدهای بالا مربوط به تنظیمات Cookie در Identity است که آن را در متد ConfigureServices کلاس Startup مینویسیم.
آپشن LoginPath و LogOutPath مسیر Login و LogOt پروژه را مشخص میکند.
این برای زمانی است که کاربر قصد وارد شدن به جایی را دارد که دسترسی لازم را دارا نیست. در این صورت از اکانت خودش خارج شده و به صفحه لاگین هدایت می شود تا دوباره شاید با اکانتی که دسترسی دارد لاگین کند.
آپشن Cookie Name نام کوکی که در مرورگر کاربر ذخیره میشود را مشخص میکند که معمولا نام وبسایت یا برنامه را برای آن قرار میدهند.
اگر نمیدانید Cookie چیست بطور خلاصه وقتی عضو وبسایتی میشوید و به اکانت خود لاگین میکنید آن وبسایت یک شناسه به شما میدهد که درون مرورگرتان ذخیره میشود و این شناسه تاریخ انقضا دارد که به این معنی است تا سررسید تاریخ انقضای آن ، شما با این مرورگر از جانب این وبسایت شناخته شده اید و نیازی به دوباره لاگین کردن ندارید. بعد از گذشت این تاریخ دوباره لاگین میکنید و دوباره کوکی جدید برای شما ذخیره میشود.
مدت زمان کوکی ها نیز با آپشن ExpireTimeSpan مشخص میشود.
این نوع احراز هویت بر اساس مقام کاربر صورت میگیرد بعنوان مثال کاربر با مقام Admin باید بتواند به پنل مدیریت دسترسی داشته باشد اما کاربر با مقام User (صرفا به معنای بازدید کننده از برنامه یا وبسایت) نباید بتواند به پنل مدیریت دسترسی داشته باشد.
در روش های Authentication روش احراز هویت بر اساس Role ساده ترین روش است که روند آن را در اینجا بررسی میکنیم.
ابتدا باید Role های خود را در دیتابیس ثبت کنیم. این عمل توسط ادمین انجام میشود و ما باید سرویس ها و اکشن های مربوط به آن را کد نویسی کنیم.
بسیار ساده است و چیزی جز Add / Delete اطلاعات در دیتابیس نیست.
مقدار Role ها یک string با هر مقداری که شما میخواهید نام مقام مورد نظر قرار دهید مشخص میشود و هر نامی که در دیتابیس ثبت کنید با همان نام ، احراز هویت صورت میگیرد.
پس کاری که انجام میدهیم شامل سه مرحله است:
1 _ ایجاد یک Role(با هر نامی که میخواهیم مثلا Admin)
2 _ نسبت دادن یک Role به User (مثلا user:sajad has role:Admin)
3 _ بررسی Authorization ( چک کردن اینکه فلان User فلان Role را دراد یا نه)
برای اینکار پیشنهاد میشود یک کنترلر مجزا برای مدیریت Role ها توسط ادمین در نظر بگیرید و سپس باید سرویس
RoleManager<IdentityRole> _roleManager;
را در کنترلر خود فراخوانی کنید.
در تصویر زیر نحوه افزودن Role و سرویس مورد نظر برای اینکار را مشاهده میکنید.
اکشن ویرایش و حذف Role را هم به همین صورت میتوانید پیاده سازی کنید.
اطلاعات مربوط به Role در جدول AspNetRole ذخیره میشود.
تا اینجا یک مرحله از سه مرحله گفته شده را پیش رفتیم حالا باید کاربر را به مقام مورد نظر نسبت دهیم.
برای این کار به سرویس
UserManager<IdentityUser> _userManager;
نیاز داریم و با استفاده از متد
_userManager.AddToRoleAsync( User , roleName ); or _userManager.AddToRolesAsync( User , List<roleName> );
در این سرویس ، کاربر و نام Role مورد نظر را به متد پاس میدهیم.
اطلاعات مربوط به اینکه چه کاربری چه نقش هایی را دارد در جدولی با نام AspNetUserRole ذخیره میشود که جدول واسط بین جداول User و Role است. در این جدول UserId و RoleId قرار دارد که مشخص میکند کدام User به کدام Role متصل است.
سرویس متد حذف کردن مقام از یک کاربر نیز به شکل زیر است.
_userManager.RemoveFromRoleAsync( User , roleName ); or _userManager.RemoveFromRolesAsync( User , List<roleName> );
تا حالا دو مرحله از سه مرحله را انجام دادیم تنها کاری که مانده چک کردن اینکه فلان کاربر فلان مقام را دارد یا خیر.
اگر true بود اجازه دسترسی را صادر میکنیم در غیر اینصورت دسترسی مجاز نیست.
برای انجام چنین کاری از Attribute های Identity استفاده میکنیم.
[Authorize( Roles = "Admin")] or ...
در مثال بالا هر کاربری که نقش Admin را داشته باشد اجازه دسترسی دارد.
از این Attribute بالای کنترلر ها یا اکشن هایی که میخواهیم برای آن محدودیت قائل شویم استفاده میکنیم.
حالت های دیگری هم برای Attribute ها وجود دارد که به آن میپردازیم.
[Authorize( Roles = "Admin , Seller")]
در این مثال کاربر باید یا عضو مقام Admin باشد یا عضو مقام Seller
[Authorize( Roles = "Admin")] [Authorize( Roles = "Seller")]
در این مثال کاربر باید هم عضو مقام Admin باشد هم عضو مقام Seller تا بتواند به کنترلر یا اکشن مورد نظر دسترسی داشته باشد. تفاوت حالت اول و دوم برای زمانی است که دو یا چند Role را میخواهید در یک اکشن یا کنترلر بررسی کنید.
[AllowAnonymous]
این Attribute اجازه دسترسی برای همه کاربران را صادر میکند و اگر از آن در کنترلر یا اکشنی استفاده کنید همه کاربران با هر نقشی به آن دسترسی خواهند داشت.
اگر میخواهید لینکی از صفحه را به کاربری که دسترسی به آن ندارد نمایش ندهید از ابتدا سرویس SignInManager را در Layout خود inject کنید سپس با استفاده از متد
@inject SignInManager<IdentityUser> SignInManager @if (User.IsInRole("Admin"))
{ html code....
}
لینک یا المنت مورد نظر را از دید کاربر پنهان کنید.
احراز هویت بر اساس Claim ها میتواند قابلیت های بیشتری نسبت به روش قبلی در اختیار ما قرار دهد. Claim ها نوعی کلید و مقدار ( key , value ) هستند به عنوان مثال
Claim1 = "Car" : "red"
Claim2 = "Car" : "blue"
در اینجا دو Claim داریم که هر دو کلید "Car" اما مقدار متفاوت دارند.
در ادامه با Claim ها بیشتر آشنا خواهید شد.
برای شروع کار با Claim ها ابتدا کلاسی در پروژه میسازیم و نقش هایی که میخواهیم برای آن دسترسی اعمال کنیم را در آن مینویسیم.
در این مثال ما دو نقش Owner و Admin داریم که که وقتی تبدیل به Claim میشود این نام بعنوان کلید یا همان Type هر Claim قرار میگیرد.
برای اینکه نقش های خود را به Claim تبدیل کنیم در کلاسی دیگر که لیستی از Claim های مارا برگشت میدهد آن را قرار میدهیم.
در پارامتر اول ClaimType ها را وارد میکنیم و پارامتر دوم ClaimValue را فعلا خالی قرار میدهیم. در ادامه راجب Value ها و اینکه چرا برای Admin دو Claim قرار دادم توضیح خواهم داد.
بقیه روند مثل Role ها باید Claim ها را به کاربری نسبت داده و پیرو آن احراز هویت را انجام دهیم.
برای اینکار از سرویس
_userManager.AddClaimAsync( User , Claim ); or _userManager.AddClaimsAsync( User , IList<Claim>);
استفاده میکنیم.
از طریق متد RemoveClaimAsync هم میتوانیم کاربری را از Claim مورد نظر Remove کنیم.
اطلاعات مربوط به Claim ها در جدول AspNetUserClaim قرار میگیرد.
برای استفاده از Claim ها بعنوان کلید احراز هویت خودمان باید تنظیماتی را نیز درون Startup اعمال کنیم.
از متد AddAuthorization برای افزودن policy ها استفاده میکنیم. دقت کنید که ما در این نوع احراز هویت ابتدا Claim هارا به policy های ساخته شده پاس میدهیم و در نهایت policy هارا مورد بررسی و عمل احراز هویت قرار میدهیم.
برای مثال policy با نام AdminPanel میسازیم که قرار است دسترسی به پنل ادمین را مدیریت کند. حالا Claim با کلید یا همان تایپ Admin را به این Policy میدهیم.
زمانی که در کنترلر یا اکشن از اتریبیوت Authorize استفاده میکنیم نیز نام Policy مورد نظر را قرار میدهیم.
حالا کاربر برای دسترسی به این Policy باید Claim هایی که درون این Policy در تنظیمات متد AddAuthorization تعریف شده را دارا باشد در غیر اینصورت مجوز دسترسی صادر نمیشود.
[Authorize(Policy = "All")] [Authorize(Policy = "Admin")]
قبلا درمورد Value ها در Claim ها صحبت کردیم حالا میخواهیم از این خاصیت Claim در احراز هویت استفاده کنیم.
ما میتوانیم دو Claim با تایپ یکسان ولی با مقدار متفاوت داشته باشیم برای مثال
Claim 1 = new Claim( "Admin" , "Add" ) Claim 2 = new Claim( "Admin" , "Delete" )
در این مثال قادر به این هستیم که Admin های با مقدار Add را از Admin با مقدار Delete جدا کنیم و ادمین های با دسترسی های متفاوت داشته باشیم.
ادمین با مقدار Add فقط دسترسی به صفحات پنل مدیریت و دکمه یا همان اکشن Add دارد.
ادمین با مقدار Delete فقط دسترسی به صفحات پنل مدیریت و دکمه یا همان اکشن Delete دارد.
برای اعمال این جزئیات در سیستم احراز هویت مقدار Claim های خود را درون کلاس Claims و درون تنظیمات Startup مدیریت کنید.
این مبحث بدلیل پیچیدگی هایی که در توضیح دارد در این مقاله نمیگنجد و من سعی میکنم زمانی دیگر در مقاله ای جدا بصورت حرفه ای و تخصصی تر درمورد آن بنویسم.
ممنون که تا اینجا همراه بودید و از بابت طولانی شدن متن عذرخواهی من را بپذیرید. قصدم این بود آشنایی نسبی با مبحث Identity را به ساده ترین شیوه بیان کنم.
فقط به یاد داشته باشید Identity خیلی بزرگ است و روش های متنوعی در Identity برای احراز هویت وجود دارد که هرکدام مزیت ها و معایب خود را دارد پس تحقیق کنید و بهترین روش را سازگار با پروژه خود انتخاب کنید.