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

转载于:https://www.cnblogs.com/x-poior/p/7891886.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值