ABP默认登录返回错误结果时,不会显示错误次数、锁定时间。为了实现验证错误时返回错误次数、锁定时间,我们需要改造返回接口。
 
1.定位验证错误的地方:
修改部分代码
 1 /// <summary>
2 /// 获取登录结果,如果错误则返回错误信息
3 /// </summary>
4 /// <param name="usernameOrEmailAddress"></param>
5 /// <param name="password"></param>
6 /// <param name="tenancyName"></param>
7 /// <returns></returns>
8 private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(string usernameOrEmailAddress, string password, string tenancyName)
9 {
10 var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
11 switch (loginResult.Result)
12 {
13 case AbpLoginResultType.Success:
14 return loginResult;
15 default:
16 {
17 throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, usernameOrEmailAddress, tenancyName, loginResult.User);
18 }
19 }
20 }
2.修改CreateExceptionForFailedLoginAttempt方法:
 1 public Exception CreateExceptionForFailedLoginAttempt(AbpLoginResultType result, string usernameOrEmailAddress, string tenancyName, User user)
2 {
3 switch (result)
4 {
5 case AbpLoginResultType.Success:
6 return new Exception("Don't call this method with a success result!");
7 case AbpLoginResultType.InvalidUserNameOrEmailAddress:
8 case AbpLoginResultType.InvalidPassword:
9 //return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPassword"));
10 return new UserFriendlyException(L("LoginFailed"), L("InvalidUserNameOrPasswordRemainErrorTimes{0}", 5 - user.AccessFailedCount));
11 case AbpLoginResultType.InvalidTenancyName:
12 return new UserFriendlyException(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName{0}", tenancyName));
13 case AbpLoginResultType.TenantIsNotActive:
14 return new UserFriendlyException(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
15 case AbpLoginResultType.UserIsNotActive:
16 return new UserFriendlyException(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
17 case AbpLoginResultType.UserEmailIsNotConfirmed:
18 return new UserFriendlyException(L("LoginFailed"), L("UserEmailIsNotConfirmedAndCanNotLogin"));
19 case AbpLoginResultType.LockedOut:
20 //todo 此处后期需要改为客户端获取UTC时间后,格式化展示,以符合国际化
21 return new UserFriendlyException(L("LoginFailed"), L("UserLockedOutMessageUntilTime{0}", user.LockoutEndDateUtc?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
22 default: // Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
23 Logger.Warn("Unhandled login fail reason: " + result);
24 return new UserFriendlyException(L("LoginFailed"));
25 }
26 }
 
3.原以为这样就可以使用了,调试时候发现数据库更新了,但是loginResult.User的结果不是最新的。试过各种方式:从UserStore、IRepository<User, long>、DBContext中获取,都是和loginResult.User一致,但是和数据库不一致。
4.后续定位问题,发现执行完
var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);

后,数据库就更新了。为了弄清楚原理只能去解读ABP源码了。

5.一路追查,后来定位到如下TryLockOutAsync方法,在AbpLogInManager类中

protected virtual async Task<bool> TryLockOutAsync(int? tenantId, long userId)
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
var user = await UserManager.FindByIdAsync(userId.ToString()); (await UserManager.AccessFailedAsync(user)).CheckErrors(); var isLockOut = await UserManager.IsLockedOutAsync(user); await UnitOfWorkManager.Current.SaveChangesAsync(); await uow.CompleteAsync(); return isLockOut;
}
}
}
大家可以看到此方法会重新从UserManager中获取user对象,并返回isLockOut。而UserManager.AccessFailedAsync如下:
 1 /// <summary>
2 /// Increments the access failed count for the user as an asynchronous operation.
3 /// If the failed access account is greater than or equal to the configured maximum number of attempts,
4 /// the user will be locked out for the configured lockout time span.
5 /// </summary>
6 /// <param name="user">The user whose failed access count to increment.</param>
7 /// <returns>The <see cref="Task"/> that represents the asynchronous operation, containing the <see cref="IdentityResult"/> of the operation.</returns>
8 public virtual async Task<IdentityResult> AccessFailedAsync(TUser user)
9 {
10 ThrowIfDisposed();
11 var store = GetUserLockoutStore();
12 if (user == null)
13 {
14 throw new ArgumentNullException(nameof(user));
15 }
16
17 // If this puts the user over the threshold for lockout, lock them out and reset the access failed count
18 var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken);
19 if (count < Options.Lockout.MaxFailedAccessAttempts)
20 {
21 return await UpdateUserAsync(user);
22 }
23 Logger.LogWarning(12, "User is locked out.");
24 await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan),
25 CancellationToken);
26 await store.ResetAccessFailedCountAsync(user, CancellationToken);
27 return await UpdateUserAsync(user);
28 }

会执行IncrementAccessFailedCountAsync和UpdateUserAsync。如果成功执行,user会增加一次验证失败的统计并保存到数据库中。

TryLockOutAsync被AbpLogInManager类中的LoginAsyncInternal方法所调用。我们需要改写此方法。
 1 protected virtual async Task<AbpLoginResult<TTenant, TUser>> LoginAsyncInternal(string userNameOrEmailAddress, string plainPassword, string tenancyName, bool shouldLockout)
2 {
3 if (userNameOrEmailAddress.IsNullOrEmpty())
4 {
5 throw new ArgumentNullException(nameof(userNameOrEmailAddress));
6 }
7
8 if (plainPassword.IsNullOrEmpty())
9 {
10 throw new ArgumentNullException(nameof(plainPassword));
11 }
12
13 //Get and check tenant
14 TTenant tenant = null;
15 using (UnitOfWorkManager.Current.SetTenantId(null))
16 {
17 if (!MultiTenancyConfig.IsEnabled)
18 {
19 tenant = await GetDefaultTenantAsync();
20 }
21 else if (!string.IsNullOrWhiteSpace(tenancyName))
22 {
23 tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName);
24 if (tenant == null)
25 {
26 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.InvalidTenancyName);
27 }
28
29 if (!tenant.IsActive)
30 {
31 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.TenantIsNotActive, tenant);
32 }
33 }
34 }
35
36 var tenantId = tenant == null ? (int?)null : tenant.Id;
37 using (UnitOfWorkManager.Current.SetTenantId(tenantId))
38 {
39 await UserManager.InitializeOptionsAsync(tenantId);
40
41 //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before AbpUserStore.FindByNameOrEmailAsync
42 var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSourcesAsync(userNameOrEmailAddress, plainPassword, tenant);
43
44 var user = await UserManager.FindByNameOrEmailAsync(tenantId, userNameOrEmailAddress);
45 if (user == null)
46 {
47 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.InvalidUserNameOrEmailAddress, tenant);
48 }
49
50 if (await UserManager.IsLockedOutAsync(user))
51 {
52 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.LockedOut, tenant, user);
53 }
54
55 if (!loggedInFromExternalSource)
56 {
57 if (!await UserManager.CheckPasswordAsync(user, plainPassword))
58 {
59 if (shouldLockout)
60 {
61 if (await TryLockOutAsync(tenantId, user.Id))
62 {
63 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.LockedOut, tenant, user);
64 }
65 }
66
67 return new AbpLoginResult<TTenant, TUser>(AbpLoginResultType.InvalidPassword, tenant, user);
68 }
69
70 await UserManager.ResetAccessFailedCountAsync(user);
71 }
72
73 return await CreateLoginResultAsync(user, tenant);
74 }
75 }
6.在LogInManager中override LoginAsyncInternal方法,并添加新的TryLockOutAsync方法,传入User引用,在uow成功提交后,赋值AccessFailedCount 和 LockoutEndDateUtc 属性。这样loginResult.User即可保持最新。

public class LogInManager : AbpLogInManager<Tenant, Role, User>
{
public LogInManager(
UserManager userManager,
IMultiTenancyConfig multiTenancyConfig,
IRepository<Tenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager,
ISettingManager settingManager,
IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
IUserManagementConfig userManagementConfig,
IIocResolver iocResolver,
IPasswordHasher<User> passwordHasher,
RoleManager roleManager,
UserClaimsPrincipalFactory claimsPrincipalFactory)
: base(
userManager,
multiTenancyConfig,
tenantRepository,
unitOfWorkManager,
settingManager,
userLoginAttemptRepository,
userManagementConfig,
iocResolver,
passwordHasher,
roleManager,
claimsPrincipalFactory)
{
} protected override async Task<AbpLoginResult<Tenant, User>> LoginAsyncInternal(string userNameOrEmailAddress, string plainPassword, string tenancyName, bool shouldLockout)
{
//return base.LoginAsyncInternal(userNameOrEmailAddress, plainPassword, tenancyName, shouldLockout);
if (userNameOrEmailAddress.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(userNameOrEmailAddress));
} if (plainPassword.IsNullOrEmpty())
{
throw new ArgumentNullException(nameof(plainPassword));
} //Get and check tenant
Tenant tenant = null;
using (UnitOfWorkManager.Current.SetTenantId(null))
{
if (!MultiTenancyConfig.IsEnabled)
{
tenant = await GetDefaultTenantAsync();
}
else if (!string.IsNullOrWhiteSpace(tenancyName))
{
tenant = await TenantRepository.FirstOrDefaultAsync(t => t.TenancyName == tenancyName);
if (tenant == null)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidTenancyName);
} if (!tenant.IsActive)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.TenantIsNotActive, tenant);
}
}
} var tenantId = tenant == null ? (int?)null : tenant.Id;
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
await UserManager.InitializeOptionsAsync(tenantId); //TryLoginFromExternalAuthenticationSources method may create the user, that's why we are calling it before AbpUserStore.FindByNameOrEmailAsync
var loggedInFromExternalSource = await TryLoginFromExternalAuthenticationSourcesAsync(userNameOrEmailAddress, plainPassword, tenant); var user = await UserManager.FindByNameOrEmailAsync(tenantId, userNameOrEmailAddress);
if (user == null)
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidUserNameOrEmailAddress, tenant);
} if (await UserManager.IsLockedOutAsync(user))
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.LockedOut, tenant, user);
} if (!loggedInFromExternalSource)
{
if (!await UserManager.CheckPasswordAsync(user, plainPassword))
{
if (shouldLockout)
{
//此处返回修改后的结果,可能会对数据产生影响
if (await TryLockOutAsync(tenantId, user))
{
return new AbpLoginResult<Tenant, User>(AbpLoginResultType.LockedOut, tenant, user);
}
} return new AbpLoginResult<Tenant, User>(AbpLoginResultType.InvalidPassword, tenant, user);
} await UserManager.ResetAccessFailedCountAsync(user);
} return await CreateLoginResultAsync(user, tenant);
}
} /// <summary>
/// 尝试锁定用户,并更新其状态
/// </summary>
/// <param name="tenantId"></param>
/// <param name="inputUser"></param>
/// <returns></returns>
protected async Task<bool> TryLockOutAsync(int? tenantId, User inputUser)
{
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.Suppress))
{
using (UnitOfWorkManager.Current.SetTenantId(tenantId))
{
var user = await UserManager.FindByIdAsync(inputUser.Id.ToString()); (await UserManager.AccessFailedAsync(user)).CheckErrors(); var isLockOut = await UserManager.IsLockedOutAsync(user); await UnitOfWorkManager.Current.SaveChangesAsync(); await uow.CompleteAsync();
inputUser.AccessFailedCount = user.AccessFailedCount;
inputUser.LockoutEndDateUtc = user.LockoutEndDateUtc;
return isLockOut;
}
}
//return base.TryLockOutAsync(tenantId, userId);
}
}
 

ABP登录返回错误次数、锁定时间的更多相关文章

  1. Shiro+SpringMVC 实现更安全的登录(加密匹配&登录失败超次数锁定帐号)

    原文:http://blog.csdn.net/wlwlwlwl015/article/details/48518003 前言 初学shiro,shiro提供了一系列安全相关的解决方案,根据官方的介绍 ...

  2. vmware vcenter orchestrator configuration提示“用户名密码错误或登录失败超过次数被锁定”

    首次登录,使用默认用户密码登录vmware/vmware vmware vcenter orchestrator configuration提示"用户名密码错误或登录失败超过次数被锁定&qu ...

  3. asp.net限制用户登录错误次数

    很经常在登录一个网站的时候看到,如果你登录的时候输入的账号密码错误超过三次就被锁定,然后等一段时间才能继续登录,最最经常使用的就是银行系统啦~~ 该功能处理流程如下: string uid = Req ...

  4. Linux使用pam_tally2.so模块限制登录失败锁定时间

    关于PAM Linux-PAM (Pluggable Authentication Modules for Linux)可插拔认证模块. https://www.cnblogs.com/klb561/ ...

  5. DiscuzX2.5密码错误次数过多,请 15 分钟后重新登录的修改办法

    source\function function_login.php $return = (!$login || (TIMESTAMP - $login['lastupdate'] > )) ? ...

  6. SpringBoot+SpringSecurity+Thymeleaf认证失败返回错误信息踩坑记录

    Spring boot +Spring Security + Thymeleaf认证失败返回错误信息踩坑记录 步入8102年,现在企业开发追求快速,Springboot以多种优秀特性引领潮流,在众多使 ...

  7. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计     ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)    ...

  8. python_login输入三次错误密码锁定密码_密码不允许为空

    #!/usr/bin/env python #_*_coding:utf-8_*_ #by anthor zhangxiaoyu 2017-01-10 import getpass import os ...

  9. ADO之密码验证--3次错误就锁定『改进』

    这里使用了SqlHelper,简化程序 自己写一个SqlHelper,把数据库的连接等都写到里面去. 首先把连接字符串添加到配置文件里去,右键解决方案-->添加新建项-->选择应用程序配置 ...

  10. Spring Security之多次登录失败后账户锁定功能的实现

    在上一次写的文章中,为大家说到了如何动态的从数据库加载用户.角色.权限信息,从而实现登录验证及授权.在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一 ...

随机推荐

  1. fiddler:The system proxy was changed.Click to reenable capturing

    前情 最近在开发一个老旧项目,由于本地环境已难跑起,于是想通过代理线上代码进行功能开发. 坑位 启动fiddler后,fiddler菜单栏会警告,大概意思是代理被更改了,点击重启fillder代理,但 ...

  2. nvm安装node.js无法使用

    前情 最近在使用某此第三方模块需要依赖不同的node版本,于是想通nvm来管理node版本 坑 网上下载nvm-window的安装包,一步步傻瓜式安装下去,发现nrm无法使用,设置环境变量也没有用,再 ...

  3. 05C++数据类型

    一.单精度实数float 教学视频A 例程1:金字塔的底是正方形,侧面由四个大小相等的等腰三角形构成.试编一程序,输入底和高,输出三角形的面积. #include <iostream> / ...

  4. PostgreSQL 的历史

    title: PostgreSQL 的历史 date: 2024/12/23 updated: 2024/12/23 author: cmdragon excerpt: PostgreSQL 是一款功 ...

  5. postgres

    10.67 su - app  docker pull postgres:12.15  docker run -d --name pgsql12 -p 5432:5432 -e "POSTG ...

  6. Markdown转Beamer

    技术背景 在早期我写过一些文章介绍用RMarkdown写Beamer,还有相应的TinyTex配置.后来Xie Yihui大神发文离开了RMarkdown核心团队,想来中文社区的支持力度和活跃度可能会 ...

  7. re模块:核心函数和方法

    1.compile(pattren,flages=0)   使用任何可选的标记来编译正则表达式的模式然后返回一个正则表达式对象 2.match(pattern,string,flags=0)    尝 ...

  8. lottie-web动画库在HTML5页面中和在vue项目中的两种使用方式

    本文主要介绍lottie-web动画库在HTML5页面中和在vue项目中的两种使用方式. 1.在HTML5页面中的使用方式 具体使用步骤详见下面的代码: <!DOCTYPE html> & ...

  9. IM通讯协议专题学习(六):手把手教你如何在Android上从零使用Protobuf

    本文由sweetying分享,为了更好的阅读体验,有较多的内容修订和排版优化. 1.前言 最近我负责的 LiveChat 客服聊天系统到了自研阶段,任务类似于做一个腾讯云IM这样的通信层SDK.在和后 ...

  10. AICA第6期-学习笔记汇总

    AICA第6期-学习笔记汇总 AICA第六期|预科班课程 1.<跨上AI的战车> 2.<产业中NLP任务的技术选型与落地> 3.<计算机视觉产业落地挑战与应对> 4 ...