2017年4月1日 星期六

[研究] 替 AP.NET Identity v2.0 身分驗證增加密碼歷史紀錄功能

[研究] 替 AP.NET Identity v2.0 身分驗證增加密碼歷史紀錄功能

2017-04-01

Visual Studio 2017 Enterprise
.NET Framework 4.6.2
ASP.NET Web Application ( .NET Framework ) 專案
ASP.NET 4.6.2 範本

密碼歷史 ( Password History ) 主要避免使用者使用重複的密碼,故會要求一定次數內不可重複。

續這篇

[研究] 建立帶 AP.NET Identity v2.0 身分驗證的 WebForm 專案
http://shaurong.blogspot.com/2017/04/apnet-identity-v20-webform.html

參考這篇
(該篇是 Visual Studio 2013 + ASP.NET 4.5 Web application + MVC,敝人這篇是 WebForm,不保證能用)

Implementing custom password policy using ASP.NET Identity
https://blogs.msdn.microsoft.com/webdev/2014/01/06/implementing-custom-password-policy-using-asp-net-identity/
Policy 1: Change the default password length requirement from 6 characters to 10 characters.
Policy 2: Passwords should have at least one special character and one numeral
Policy 3: The user cannot reuse the last five previous passwords

把 App_Start 目錄的 IdentityConfig.cs 改為


using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using WebApplication1.Models;
using System.Linq;
using System.Text.RegularExpressions;

namespace WebApplication1
{
    public class EmailService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // 將您的電子郵件服務外掛到這裡以傳送電子郵件。
            return Task.FromResult(0);
        }
    }

    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // 插入此處的 SMS 服務以傳送簡訊。
            return Task.FromResult(0);
        }
    }

    // 設定在此應用程式中使用的應用程式使用者管理員。UserManager 會在 ASP.NET Identity 中定義,並由應用程式使用。
    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>()));
            // 設定使用者名稱的驗證邏輯
            manager.UserValidator = new UserValidator<ApplicationUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            };

            // 設定密碼的驗證邏輯
            manager.PasswordValidator = new PasswordValidator
            {
                RequiredLength = 6,
                RequireNonLetterOrDigit = true,
                RequireDigit = true,
                RequireLowercase = true,
                RequireUppercase = true,
            };

            // 註冊雙因素驗證提供者。此應用程式使用手機和電子郵件做為接收驗證碼以驗證使用者的步驟。
            // 您可以在這裡寫下自己的提供者和外掛程式。
            manager.RegisterTwoFactorProvider("電話密碼", new PhoneNumberTokenProvider<ApplicationUser>
            {
                MessageFormat = "您的安全密碼為 {0}"
            });
            manager.RegisterTwoFactorProvider("電子郵件密碼", new EmailTokenProvider<ApplicationUser>
            {
                Subject = "安全密碼",
                BodyFormat = "您的安全密碼為 {0}"
            });

            // 設定使用者鎖定預設
            manager.UserLockoutEnabledByDefault = true;
            manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
            manager.MaxFailedAccessAttemptsBeforeLockout = 5;

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

        private const int PASSWORD_HISTORY_LIMIT = 5;

        public ApplicationUserManager() : base(new ApplicationUserStore(new ApplicationDbContext()))
        {
            PasswordValidator = new CustomPasswordValidator(10);
        }
        public override async Task<IdentityResult> ChangePasswordAsync(string userId, string currentPassword, string newPassword)
        {
            if (await IsPreviousPassword(userId, newPassword))
            {
                return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
            }
            var result = await base.ChangePasswordAsync(userId, currentPassword, newPassword);
            if (result.Succeeded)
            {
                var store = Store as ApplicationUserStore;
                await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
            }
            return result;
        }
        public override async Task<IdentityResult> ResetPasswordAsync(string userId, string token, string newPassword)
        {
            if (await IsPreviousPassword(userId, newPassword))
            {
                return await Task.FromResult(IdentityResult.Failed("Cannot reuse old password"));
            }
            var result = await base.ResetPasswordAsync(userId, token, newPassword);
            if (result.Succeeded)
            {
                var store = Store as ApplicationUserStore;
                await store.AddToPreviousPasswordsAsync(await FindByIdAsync(userId), PasswordHasher.HashPassword(newPassword));
            }
            return result;
        }
        private async Task<bool> IsPreviousPassword(string userId, string newPassword)
        {
            var user = await FindByIdAsync(userId);
            if (user.PreviousUserPasswords.OrderByDescending(x => x.CreateDate).
            Select(x => x.PasswordHash).Take(PASSWORD_HISTORY_LIMIT).Where(x => PasswordHasher.VerifyHashedPassword(x, newPassword) != PasswordVerificationResult.Failed
            ).Any())
            {
                return true;
            }
            return false;
        }
    }

    public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
    {
        public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
            base(userManager, authenticationManager)
        { }

        public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
        {
            return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
        }

        public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
        {
            return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
        }
    }
    public class CustomPasswordValidator : IIdentityValidator<string>
    {
        public int RequiredLength { get; set; }
        public CustomPasswordValidator(int length)
        {
            RequiredLength = length;
        }
        public Task<IdentityResult> ValidateAsync(string item)
        {
            if (String.IsNullOrEmpty(item) || item.Length < RequiredLength)
            {
                return Task.FromResult(IdentityResult.Failed(String.Format("Password should be of length {0}", RequiredLength)));
            }
            string pattern = @"^(?=.*[0-9])(?=.*[!@#$%^&*])[0-9a-zA-Z!@#$%^&*0-9]{10,}$";
            if (!Regex.IsMatch(item, pattern))
            {
                return Task.FromResult(IdentityResult.Failed("Password should have one numeral and one special character"));
            }
            return Task.FromResult(IdentityResult.Success);
        }
    }
}



實際測試有問題



失敗,待研究

(待續)

相關

[研究] 建立帶 AP.NET Identity v2.0 身分驗證的 WebForm 專案
http://shaurong.blogspot.com/2017/04/apnet-identity-v20-webform.html

[研究] ASP.NET Identity v2.2.1 相依套件與 packages.config 階層架構
http://shaurong.blogspot.tw/2017/04/aspnet-identity-v221-packagesconfig.html

[研究] ASP.NET Web Application 專案 WebForm 範本 (VS2015+.NET 4.6.2) 的 packages.config 套件階層架構
http://shaurong.blogspot.com/2017/02/aspnet-web-application-webform.html

[研究] ASP.NET Web Application 專案空白範本 (VS2015+.NET 4.6.2) 的 packages.config 內容
http://shaurong.blogspot.com/2017/01/aspnet-web-application-vs2015net-462.html

[研究] ASP.NET Web Application 專案 WebForm 範本 (VS2015+.NET 4.6.2) 的 packages.config 內容
http://shaurong.blogspot.com/2017/01/webform-vs2015net-462-packagesconfig.html

ASP.NET Identity
https://docs.microsoft.com/en-us/aspnet/identity/index

Implementing custom password policy using ASP.NET Identity
https://blogs.msdn.microsoft.com/webdev/2014/01/06/implementing-custom-password-policy-using-asp-net-identity/
Policy 1: Change the default password length requirement from 6 characters to 10 characters.
Policy 2: Passwords should have at least one special character and one numeral
Policy 3: The user cannot reuse the last five previous passwords

[ASP.NET Identity] OAuth Server 鎖定(Lockout)登入失敗次數太多的帳號
https://dotblogs.com.tw/yc421206/2016/08/03/asp_net_identity_oauth_user_lockout

MVC使用ASP.NET Identity 2.0實現用戶身份安全相關功能,比如通過短信或郵件發送安全碼,賬戶鎖定等
http://www.cnblogs.com/darrenji/p/3761690.html

How to access all ASP.NET Identity 2.0 PasswordValidator properties?
http://stackoverflow.com/questions/24589148/how-to-access-all-asp-net-identity-2-0-passwordvalidator-properties

[WEB API] Identity 寄Email驗證帳號和密碼規則
https://dotblogs.com.tw/kinanson/2015/05/13/151271

Implementing Password History using a Custom Membership Provider
https://ronanmoriarty.wordpress.com/2012/03/14/implementing-password-history-using-a-custom-membership-provider/




沒有留言:

張貼留言