zoukankan      html  css  js  c++  java
  • MVc Identity登陆锁定

    [ASP.NET Identity] OAuth Server 鎖定(Lockout)登入失敗次數太多的帳號

    這個功能看似很簡單,但我一直無法成功鎖定帳號,追根究柢就是不了解運作方式,以下就來分享實作心得

    Lockout 相關成員

    欄位

    帳號失敗次數鎖定,在 AspNetUsers Table 用三個欄位控制

    • LockedEnable:是否啟用鎖定
    • AccessFailedCount:失敗次數
    • LockoutEndDateUtc:鎖定到期時間

    行為

    • ApplicationUserManager.SetLockoutEnabledAsync(user.Id) 方法,控制 LockedEnable 欄位= true | false
    • ApplicationUserManager.AccessFailedAsync(user.Id) 方法,控制 LockoutEndDateUtc 和 AccessFailedCount 欄位。
    當調用一次 AccessFailedAsync(),AccessFailedCount 累加一,超過定義次數,AccessFailedCount 歸零,寫入 LockoutEndDateUtc 時間
    • ApplicationUserManager.SetLockoutEndDateAsync() 方法,控制結束鎖定時間
    • ApplicationUserManager.IsLockedOutAsync(user.Id) ,以 LockoutEndDateUtc 和 LockedEnable 欄位決定是否為 Lockout

    另外,ApplicationSignInManager 也能處理 Lockout

    • ApplicationSignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: true),

    屬性

    還有三個屬性,可以定義,分別是:

    • ApplicationUserManager.DefaultAccountLockoutTimeSpan 屬性,鎖定時間
    • ApplicationUserManager.MaxFailedAccessAttemptsBeforeLockout 屬性,最多失敗次數
    • ApplicationUserManager.UserLockoutEnabledByDefault 屬性,建立帳號時是否啟用鎖定

    Step1.定義 Lockout 屬性

    @Startup.cs

    把控制 Lockout 的屬性放在 Startup.CreateUserManager 方法集中建立

    @AppSetting.cs

    這個類別,提供讀取 Web.Config 的屬性並 Cache 起來,以免一個 Request 請求就讀一次檔案

    public class AppSetting
    {
    	private static TimeSpan? s_defaultAccountLockoutTimeSpan;
    	private static int? s_maxFailedAccessAttemptsBeforeLockout;
    	private static bool? s_userLockoutEnabledByDefault;
    
    	public static TimeSpan DefaultAccountLockoutTimeSpan
    	{
    		get
    		{
    			if (!s_defaultAccountLockoutTimeSpan.HasValue)
    			{
    				double result;
    				if (double.TryParse(ConfigurationManager.AppSettings["DefaultAccountLockoutTimeSpan"], out result))
    				{
    					s_defaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(result);
    				}
    				else
    				{
    					s_defaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(20);
    				}
    			}
    			return s_defaultAccountLockoutTimeSpan.Value;
    		}
    		set { s_defaultAccountLockoutTimeSpan = value; }
    	}
    
    	public static int MaxFailedAccessAttemptsBeforeLockout
    	{
    		get
    		{
    			if (!s_maxFailedAccessAttemptsBeforeLockout.HasValue)
    			{
    				int result;
    
    				if (int.TryParse(ConfigurationManager.AppSettings["MaxFailedAccessAttemptsBeforeLockout"],
    								 out result))
    				{
    					s_maxFailedAccessAttemptsBeforeLockout = result;
    				}
    				else
    				{
    					s_maxFailedAccessAttemptsBeforeLockout = 5;
    				}
    				s_maxFailedAccessAttemptsBeforeLockout = result;
    			}
    			return s_maxFailedAccessAttemptsBeforeLockout.Value;
    		}
    		set { s_maxFailedAccessAttemptsBeforeLockout = value; }
    	}
    
    	public static bool UserLockoutEnabledByDefault
    	{
    		get
    		{
    			if (!s_userLockoutEnabledByDefault.HasValue)
    			{
    				bool result;
    				if (bool.TryParse(ConfigurationManager.AppSettings["UserLockoutEnabledByDefault"], out result))
    				{
    					s_userLockoutEnabledByDefault = result;
    				}
    				else
    				{
    					s_userLockoutEnabledByDefault = true;
    				}
    
    				s_userLockoutEnabledByDefault = result;
    			}
    			return s_userLockoutEnabledByDefault.Value;
    		}
    		set { s_userLockoutEnabledByDefault = value; }
    	}
    }

     

    Step2.撰寫鎖定邏輯

    @AuthorizationServerProvider.cs

    在取得 Token 的 GrantResourceOwnerCredentials 方法裡面,控制帳號鎖定邏輯

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
     
        var user = await userManager.FindByNameAsync(context.UserName);
        if (user == null)
        {
            var message = "Invalid credentials. Please try again.";
            context.SetError("invalid_grant", message);
            return;
        }
    
        var validCredentials = await userManager.FindAsync(context.UserName, context.Password);
        var enableLockout = await userManager.GetLockoutEnabledAsync(user.Id);
    
        if (await userManager.IsLockedOutAsync(user.Id))
        {
            var message = string.Format(
                "Your account has been locked out for {0} minutes due to multiple failed login attempts.",
                AppSetting.DefaultAccountLockoutTimeSpan.TotalMinutes);
            ;
            context.SetError("invalid_grant", message);
            return;
        }
    
        if (enableLockout & validCredentials == null)
        {
            string message;
            await userManager.AccessFailedAsync(user.Id);
    
            if (await userManager.IsLockedOutAsync(user.Id))
            {
                message =
                    string.Format(
                        "Your account has been locked out for {0} minutes due to multiple failed login attempts.",
                        AppSetting.DefaultAccountLockoutTimeSpan.TotalMinutes);
            }
            else
            {
                var accessFailedCount = await userManager.GetAccessFailedCountAsync(user.Id);
                var attemptsLeft = AppSetting.MaxFailedAccessAttemptsBeforeLockout -
                                   accessFailedCount;
                message =
                    string.Format(
                        "Invalid credentials. You have {0} more attempt(s) before your account gets locked out.",
                        attemptsLeft);
            }
    
            context.SetError("invalid_grant", message);
            return;
        }
        if (validCredentials == null)
        {
    
            var message = "Invalid credentials. Please try again.";
            context.SetError("invalid_grant", message);
            return;
        }
        await userManager.ResetAccessFailedCountAsync(user.Id);
        var oAuthIdentity = await userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
        var properties = CreateProperties(user.UserName);
    
    
        var oAuthTicket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(oAuthTicket);
    }

    Step3.撰寫測試程式碼

    可以在測試程式碼裡直接注入 Lockout 屬性

    [ClassInitialize]
    public static void Initialize(TestContext testContext)
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ApplicationDbContext>());
        AppSetting.UserLockoutEnabledByDefault = true;
        AppSetting.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        AppSetting.MaxFailedAccessAttemptsBeforeLockout = 3;
    }

    在測試程式碼,我模擬登入失敗超過三次

    [TestMethod]
    public async Task Login_Fail_3_Lockout_3Test()
    {
        await RegisterAsync();
    
        Password = "Pass@w0rd2~";
        var token1 = await LoginAsync();
        token1.ErrorDescription.Should()
              .Be("Invalid credentials. You have 2 more attempt(s) before your account gets locked out.");
        Password = "Pass@w0rd2~";
        var token2 = await LoginAsync();
        token2.ErrorDescription.Should()
              .Be("Invalid credentials. You have 1 more attempt(s) before your account gets locked out.");
    
        Password = "Pass@w0rd2~";
        var token3 = await LoginAsync();
        token3.ErrorDescription.Should()
              .Be("Your account has been locked out for 5 minutes due to multiple failed login attempts.");
    }
    假如你一直無法在 Production 裡面正確的使用 Lockout 請查看資料庫確認該帳號的 LockoutEnabled 有被打開。
     
    原文:
    https://dotblogs.com.tw/yc421206/2016/08/03/asp_net_identity_oauth_user_lockout
  • 相关阅读:
    学习使用linux下tags文件
    uboot常用命令详解
    U-boot中TFTP 解释
    eth0: ERROR while getting interface flags: No such device 没有eth0 有其他的eth
    取消挂载 umount 时出现的 “Device is busy”
    Linux的log日志功能
    oracle的安装
    SOA架构设计分析
    立方体模型
    质量属性的六个常见属性场景(淘宝网)
  • 原文地址:https://www.cnblogs.com/x-poior/p/7891886.html
Copyright © 2011-2022 走看看