درک مفاهیم اصلی ASP.NET MVC and Identity 2.0

دوشنبه 27 مرداد 1393

در این مقاله سعی داریم تا مقدمات ASP.NET MVC and Identity 2.0 را دریابیم با component های اصلی سیستم و در کل با مولفه های جدیداشنا شویم

درک مفاهیم اصلی ASP.NET MVC and Identity 2.0

در بیستم مارچ سال 2014 تیم ASP.NET نسخه دوم Identity Frame work را منتشر کردند.

انتشار جدید همرا با  تعدادی از مولفه های جدیدی همراه بود که از مدت ها قبل انتظار داشتیم و از نظر امنیت بسیار بالا و قابلیت دسترسی به همه نوع application،ASP.NETمورد توجه قرار گرفت.

Identity framework مولفه های پیشرفته ای مانند شبکه اجتماعی log-in  و تعاریف کاربری گسترده را معرفی کرده است.

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

انتشار جدید مولفه های زیر را معرفی کرده است :

1- تعریف حساب کاربری گسترده شده شامل Email  و اطلاعات تماس

2- دو فاکتور اجازه دسترسی توسط ایمیل و SMS که از نظر کاربرد شبیه به آن چیزی است که Google و Microsoft استفاده می کنند.

3- تایید حساب توسط ایمیل

4- مدیریت کردن کاربران و نقش ها

5- قفل کردن حساب  در پاسخ به تلاش های اشتباه برای log-in

Security Token Provider - 6 برای گسترش security token کاربر در پاسخ به تغییرات در تنظیمات امنیتی

7- پشتیبانی پیشرفته از login  های اجتماعی

8- ادغام آسان درخواست ها بر اساس مجوز

Identity 2.0 یک فرایند تغییر به منظور بهتر شدن از نسخه اورجینال که سال پیش معرفی شده بود را توسط تعداد بیشمار مولفه های جدید که کمی پیچیدگی به همراه دارد را به نمایش می گذارد.

اگر شما مانند من اخیرا قدم در مسیر Identity framework گذاشته اید،اماده باشید در حالی که نیازی نیست تا برای بهتر شدن از ابتدای چیزی شروع کنید که قبلا وجود نداشته اما باید چیزهای بسیاری یاد بگیرید.

در این مقاله سعی داریم تا به اطراف نگاهی بیندازیم،با component های اصلی سیستم اشنا شویم و در کل با مولفه های جدید آشنا شویم

ما هنوز سعی نداریم تا  زیاد وارد جزییات شویم.به این مقاله به دید یک تور آشنایی نگاه کنید.

در حالی که با کدهای زیادی روبرو هستیم اما  هنوز  نیازی نیست تا کاملا جزییاتش  را درک کنیم.

فقط با مفاهیم کلی  که component های اصلی  کجا  قرار گرفته اند و اینکه چگونه ساخت یافته اند  اشنا شوید.

 Identity 2.0 تغییرات Breaking را معرفی می کند:

Identity 2.0 وارد مکانی برای application های نوشته شده توسط نسخه یک نمی شود.

امکانات بیشتری در جهت تغییرات مهم  مورد نیاز در معماری و طریقی که Identity API در application استفاده می شود وجود دارد.

بروز رسانی application ، ASP.NE از Identity 1.0 به نسخه دوم آن نیازمند کدهای جدیدی است که این خود فراتر از محدوده این مقاله است.

در ضمن آگاه باشید که حرکت از Identity 1.0 به نسخه دوم ساده نیست.

گرفتن مثال ها از Nuget:

بر طبق این مقاله تمپلیت  قابل دسترس مستقیمی در پروژه ASP.NET MVC  با استفاده از Identity 2.0 وجود ندارد.

برای این روند شما نیازمندید تا کتابخانه های پروژه مثال را به یک پروژه خالی  ASP.NET MVC وارد کنید.

در ابتدا یک پروژه جدید ASP.NET ایجاد کنید و تمپلیت Empty را از میان

گزینه های نمایش داده شده انتخاب کنید.

به محض اینکه یک پروژه Empty خلق کردید،شما می توانید نمونه پروژه Identity 2.0 را از Nuget با استفاده از وارد کردن کد زیر در Packag Manager Console داشته باشید.

PM>Install-Package Microsoft.AspNet.Identity.Samples -Pre 

وقتی عملیات نصب انجام شد، شما باید  folder structure را در Solution Explorer که کاملا شبیه به یک پروژه استاندارد MVC است ببینید.

Nuget تمام چیزهای مورد نیاز برای ایجاد یک پروژه کامل ASP.NET MVC را شامل Models, Views, Controllers و بسیاری از component ها را که برای اجرا شدن application مورد نیاز است را دارا می باشد.

اگر چه در نگاه اول component های پروژه بسیار آشنا به نظر می ایند اما  با یک نگاه دقیق تغییرات اساسی  و همچنین پیچیدگی های اضافه شده  نمایان می شود .

پیکر بندی Identity 2.0 :

به عقیده من یکی از نقاط قوت نسخه اصلی Identity framework سادگی ان بود.سادگی  Identity version 1.0 استفاده از آن را بسیار آسان و متقابلا قابل درک کرده بود.

برای گرفتن ایده ما یک نگاه سریع به تنظیماتی  می اندازیم که در زمان شروع application  ،اجرا می شود و ان را با کد قابل مقایسه application ی که  توسط Identity Version 1.0  استفاده میشد مقایسه می کنیم.

ما در هر دو نوع از پروژه در روت پروژه می توانیم  فایل کلاسی  با نام Startup.cs پیدا کنیم.

در این فایل کلاسی به نام Startup تعریف شده است و متد ConfigureAuth

() صدا زده شده است، اما درواقع دیده نمی شود و این به این خاطر است که برخی از کدهای کلاس

Startup در partial class   که در فولدر App_Start   است تعریف شده است .

فایل کد Startup.Auth.cs نامیده شده است اما اگر ان را باز کنیم تعریف partial class استاندارد را که شامل متد ConfigureAuth() پیدا می کنیم.

در پروژه ای  که از نسخه  1.0   Identity Frameworkاستفاده کرده کدهای استاندارد برای متد 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();
    }
}

در مقایسه  وقتی متد  ConfigureAuth() را در پروژه مان با استفاده از identity Version 2.0 می بینیم کدهای بیشتری اضافه شده است.

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();
    }
}

در بالا اولین چیزی که توجه ما را جلب می کند فراخوانی های متعدد ازapp.CreatePerOwinContext است که  ما  Call back هایش  را ثبت می کنیم  تا برای ایجاد نمونه هایی از نوع خاص  به وسیله  نوع ارگومان ها فراخوانی شوند.حالا نمونه های ایجاد شده قادرند تا از متد context.Get() استفاده کنند.

در کل چیزی که به ما گفته می شود این است که برای پروژه مثالمان که توسط تیم  Identity 2.0  مطرح شده Owin الان قسمتی از application ماست .در واقع نمی توان با اطمینان گفت که Owin برای Identity 2.0  ضروری است اما می توان گفت برای پروژه مثال ما اینگونه است.

ما همچنین می توانیم فراخوانی های دیگری را نیز در بدنه متد ConfigureAuth ببینیم که دو فاکتور اجازه دسترسی را قرار داده اند و  همچنین  کدهای پیکربندی اجازه دسترسی کوکی که در نسخه قبلی دیده نمی شد.

قبل از پرداختن به IdentityConfig.cs بهتر است به ApplicationUser class که در فولدر Models  تعریف شده نگاهی بیندازیم.

ApplicationUser Class in Identity 2.0:

اگر قبلا application ی با نسخه قبلی Identity framework ایجاد کرده اید شاید در موقعیتی قرار گرفته اید که هسته کلاس 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; }
}

نسخه دوم کلاس Identitty 2.0

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;
    }
}

ما می بینیم که ApplicationUser به عنوان sub-class، IdentityUser مشخص شده است.بنابراین اگر به دنبال تعریف IdentityUser برویم میبینیم که IdentityUser همان طور که در dentity 2.0 framework تعریف شده خودش sub-class ،IdentityUser<TKey, TLogin, TRole, TClaim> است.

IdentityUser Implementation from Identity 2.0:
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; }
}

همان طور که در بالا می بینید تمام property ها مرتبط با اجازه دسترسی و امنیت است و به نیازهای تجاری ما برای اطلاعات کاربر مربوط نیست.بنابراین فیلدهای Email و PhoneNumberمسیر طولانی را در جهت کوچک کردن نیاز برای شخصی سازی کلاس ApplicationUser طی می کنند.

ورژن جدید IdentityUser نوعی از ارگومان ها را برای اضافه کردن انعطاف پذیری بیشتر قرار میدهد.

مثلا در نسخه قبل برای تعریف متغیر Id از نوع string  بود اما در نسخه جدید در اینجا نوع ارگومان   Tkeyبه ما اجازه می دهد تا نوع  فیلد Id را تعیین کنیم .

تعریف متغیر Id:

public virtual TKey Id { get; set; }

تعریف متغیر Roles:

public virtual ICollection<TRole> Roles { get; } 

ما می بینیم که نوع TRole در زمان کامپایل بازگذاشته شده است .اگر به محدودیت نوع در این تعریف نگاه کنیم میبینیم که TRole به نوع IdentityUserRole<TKey> محدود شده که با نسخه یک چندان تفاوتی ندارد.ان چیزی که متفاوت است و  نقطه عطف است  تعریف IdentityUserRole است.

در نسخه اول تعریف 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; }
} 

ببینید چه اتفاقی افتاد؟در اولی شامل referenceهایی به شی IdentityRole  است.

در نسخه دوم فقط شامل مقدار Id است.در ادامه ما نگاهی  می اندازیم به انعطاف پذیری که توسط IdentityUser صورت می گیرد.در نطر بگیرید اگرچه تعریف کلاس پیچیده می شود اما در عوض بسیار انعطاف پذیر خواهد شد.

پیکربندی Components and Helpers در Identity 2.0:

زمانی  که متد ConfigAuth() از  کلاس Startup  جایی هست که پیکربندی زمان اجرا برای شناسایی اتفاق می افتد.

ما در واقع از component  های تعریف شده در فایل IdentityConfig.cs استفاده می کنیم تا  اینکه چگونه بیشتر مولفه های   Identity 2.0    در application  رفتار می کنند را  پیکر بندی کنیم.

اگر ما محتوای فایل IdentityConfig.cs را بررسی کنیم متوجه می شویم که تعداد زیادی کلاس وجود دارد.

در اینجا ما هر کلاس را به طور مستقل بررسی می کنیم و این در حالی است که همه انها دارای location مشترکی در فایل  پروژه مان هستند.

تمام این کلاس ها در فضای نام  ApplicationName.Models تعریف نشده است.

Application User Manager and Application Role Manager:

The Identity 2.0 Application User Manager Class:
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);
    }
}

عملیاتی که انجام می شود شامل اضافه کردن کاربر جدید،اضافه کردن کاربر به نقش پاک کردن کاربر از نقش است.

ApplicationUserManager  از کلاس UserManager<ApplicationUser> گرفته شده است.بنابراین تمام کارایی های تعریف شده در UserManager در ApplicationUserManager نیز وجود دارد

یک متد  استاتیک Create() تعریف شده که یک نمونه از ApplicationUserManager را برمی گرداند.

در این متد است که بیشتر تنظیمات پیکربندی کاربرتان مشخص می شود .

درون متد Create() فراخوانی به context.Get<ApplicationDBContext>() صورت می گیرد.

فراخونی context.Get<ApplicationDbContext>() عملیات call back را انجام

می دهد.

اگر با دقت نگاه کنیم  شما می توانید ببینید که در متد creat قبل از اینکه یک نمونه از ApplicationUserManager به فراخواننده برگردد  اجازه دسترسی کاربر و تنظیمات مدیریتی و پیش فرض ها  در متد creat ست می شود.

تقریبا بسیاری از تنظیمات قابل درک است.

The Application Role Manager Class:
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;
    }
}

همانند ApplicationUserManager می توانید ببینید که ApplicationRoleManager از RoleManager<IdentityRole> برگرفته شده است و تمام functionality ان را داراست.

و همین طور می بینید که متد استاتیک Create() یک نمونه از کلاس را برمی گرداند.

و همین طور می بینید که متد استاتیک Create() یک نمونه از کلاس را برمی گرداند.

سرویس ایمیل و اس ام اس برای اعتبار سنجی حساب:

در فایل IdentityConfig.cs دو سرویس کلاس هستند:

EmailService -1

2- SmsService

The ASP.NET Identity Email Service Class:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        return Task.FromResult(0);
    }
}
The ASP.NET Identity SmsService Class:
public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        return Task.FromResult(0);
    }
}
تنظیمات Email Service و SMS Service در متد Create، ApplicationUserManager:
 

برای ایجاد یک نمونه پروژه  Identity،تیم Identity یک کلاس helper  که در فایل    IdentityConfig.cs پیدا می شد اضافه کرده است.

بگذارید نگاهی به کلاس SignInHelper بیندازیم ما قرار نیست  تا در اینجا وارد جزییات شویم .

The Sign-In Helper Class:
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;
    }
}

متدهای قابل دسترس در کلاس SignInHelper بعضی از مولفه های جدید را در Identity 2.0 معرفی می کند.ما در آن متد اشنای SignInAsync() را می بینیم.

ما می توانیم به متد Login در AccountController به این دید که چگونه اجازه دسترسی در Identity 2.0 مدیریت می شود  نگاهی بیندازیم .

متد Login در AccountController در با استفاده از Identity 2.0:

[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // This doesn'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);
    }
}

اگر ما نگاه سریعی به این بیندازیم که چگونه عملیات log-in در پروژه MVC با استفاده از Identity 1.0 مدیریت می شود .ما مستقیما باید به متد AccountController.Login برویم و کدهای زیر را می بینیم.

متد Login در AccountController در با استفاده از Identity 1.0:

[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);
}

طبق متد بالا به کلاس UserManager شبیه به کدی که در SignInHelper دیدیم می رویم.ما همچنین متد SignInAsync را که  مستقیما در AccountController تعریف شده بود فراخوانی می کنیم.

متد SignInAsync در AccountController در با استفاده از Identity 1.0:

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:

اگر شما با ASP.NET MVC کار کرده باشید حتما با ApplicationDbContext اشنایی دارید میدانید که

آن یک کلاس اجرایی پیش فرض Entity Framework است که application شما، Identity-related data. را دسترسی دارد و ذخیره می کند.

در این پروژه تیم کمی متفاوت با استاندارد های در یک پروژه ASP با استفاده از Identity 1.0 رفتار کرده است .در این مثال ما نگاهی به فایل IdentityModels.cs می اندازیم و در نتیجه کلاس تعریف شده ApplicationDbContext را پیدا می کنیم.

کلاس ApplicationDbContext در پروژه مثال Identity 2.0:
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();
    }

}

کد بالا دو متد استاتیک Create() و ApplicationDbContext() را ست می کند که این دو دیتابیس را مقداردهی می کند،دومین متد چک می کند که مقدار دهی دیتابیس در کلاس ApplicationDbInitializer قرار داده شده است.

کلاس ApplicationDbInitializer  در فایل IdentityConfig.cs:
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);
        }
    }
}

نتیجه گیری:

در این مقاله ما نگاهی به بعضی از مولفه های و پیکر بندی ها در پروژه ASP.NET MVC  با استفاده از Identity 2.0 framework انداختیم  اما هنوز مسایل بسیاری وجود دارد که  ما به ان نپرداختیم.

جعفری

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

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

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