2016-08-03

[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

MVc Identity登陆锁定的更多相关文章

  1. Python作业之三次登陆锁定用户

    作业之三次登陆锁定用户 作业要求如下: 1. 输入用户名和密码 2. 认证成功提示欢迎信息 3. 认证失败三次锁定用户 具体代码如下: 方法1: import os#导入os模块 if os.path ...

  2. ASP.NET MVC Identity 兩個多個連接字符串問題解決一例

    按照ASP.NET MVC Identity建立了一個用戶權限管理模塊,由于還要加自己已有的數據庫,所以建立了一個實體模型,建立了之后,發現登錄不了: 一直顯示“Login in failed for ...

  3. MVC绕过登陆界面验证时HttpContext.Current.User.Identity.Name取值为空问题解决方法

    Global.asax界面添加如下方法: void FormsAuthentication_Authenticate(object sender, FormsAuthenticationEventAr ...

  4. MVC SSO登陆 的麻烦事~

    前段时间用MVC + Redis 做session搞了个简单的单点登录Web站.真是日了狗的问题多. 今天正好睡不着,做个备忘笔记>_< 实现方法很简单,无非就是从重载个Controlle ...

  5. MVC用户登陆验证及权限检查(Form认证)

    1.配置Web.conf,使用Form认证方式   <system.web>     <authentication mode="None" />      ...

  6. MVC基本登陆与验证码功能实现

    一.基本登陆实现与验证码功能实现,该功能是和spring.net功能集合使用的,因为后面要用到验证是否处于登陆状态 1. 先构建一个登陆页面 @{ Layout = null; } <!DOCT ...

  7. .NET MVC中登陆授权过滤器的使用

    1.写个类LoginAuthorityAttribute,继承自AuthorizeAttribute using System; using System.Collections.Generic; u ...

  8. [Python3.x]多次登陆锁定用户

    要求:输入用户名,密码认证成功显示欢迎信息输入错误三次后锁定用户Readme: 1.account.txt是存放用户id及密码的文件 2.account_loc.txt是存放被锁定的用户id的文档,默 ...

  9. ASP.NET MVC Identity 添加角色

    using Microsoft.AspNet.Identity; public ActionResult AddRole(String name){ using (var roleManager = ...

随机推荐

  1. javascript单线程那些事

    首先,说下为什么 JavaScript 是单线程? 总所周知,JavaScript是以单线程的方式运行的.说到线程就自然联想到进程.那它们有什么联系呢? 进程和线程都是操作系统的概念.进程是应用程序的 ...

  2. bug提单规范

    一.提单模板 标题:[项目组][模块][子模块][发生原因]问题简要描述描述:[预置条件] 有就写清楚,没有就写无[操作步骤]1.XXXXX2.XXXXXX3.XXXXX[实际结果] XXXXX[预期 ...

  3. 自行编译mwan加入openwrt里

    参考源文:http://www.right.com.cn/forum/thread-124449-1-1.html 本例以 opoenwrt 12.09正式版为例,原软件来自openwrt 英文论坛: ...

  4. Webservice之axis

    根据wsdl的url,使用axis1.4生成客户端,并且对webservice进行调用   1.到www.apache.org上去下载axis-bin-1_4.zip,如要关联源代码就把axis-sr ...

  5. django使用restframework实现安全的api

    参考地址:https://github.com/tomchristie/django-rest-framework/ 一般如果在批量修改多的时候,不建议使用,一般在get请求,或者修改单条数据的时候使 ...

  6. c++之enum的好处与 define 的区别

    转载自 https://blog.csdn.net/zhh464626057/article/details/41038933 什么时候需要用到enum呢?就是变量的数值在几个范围之间.red,blu ...

  7. 吴裕雄 19-Mysql 连接的使用

    JOIN 按照功能大致分为如下三类:INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录.LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录.RIGHT ...

  8. 学习BOS物流项目第十天

    1 教学计划 1.演示权限demo 2.权限概述 a. 认证 b. 授权 3.常见的权限控制方式 a.  url拦截权限控制 b.  方法注解权限控制 4.创建权限数据模型 a.  权限表 b.  角 ...

  9. jquery 获取子元素的限制jquery

    今天练习jqueryAPI发现一个问题就是子元素如果采用nth-child,元素不同就获取不到,因此一个父元素下的子元素标签必须相同,如果不同第一个元素可以用这个方法实现,但是如果第二元素及以后如果出 ...

  10. java.security.MessageDigest (1)

    我们知道,编程中数据的传输,保存,为了考虑安全性的问题,需要将数据进行加密.我们拿数据库做例子.如果一个用户注册系统的数据库,没有对用户的信息进行保存,如,我去页面注册,输入"Vicky&q ...